Merge pull request #3985 from JoshVanL/parse-certificate-chain-ca

Parse certificate chain CA Issuer
This commit is contained in:
jetstack-bot 2021-05-13 13:23:14 +01:00 committed by GitHub
commit 96ea5e51d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 220 additions and 198 deletions

View File

@ -24,6 +24,7 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"reflect"
"testing"
@ -111,6 +112,30 @@ func TestSign(t *testing.T) {
}),
)
rootPK, err := pki.GenerateECPrivateKey(256)
if err != nil {
t.Fatal()
}
rootTmpl := &x509.Certificate{
Version: 3,
BasicConstraintsValid: true,
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: "root",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
PublicKey: rootPK.Public(),
IsCA: true,
}
_, rootCert, err := pki.SignCertificate(rootTmpl, rootTmpl, rootPK.Public(), rootPK)
if err != nil {
t.Fatal(err)
}
sk, err := pki.GenerateRSAPrivateKey(2048)
if err != nil {
t.Fatal(err)
@ -158,7 +183,7 @@ func TestSign(t *testing.T) {
t.Errorf("error generating template: %v", err)
}
certPEM, _, err := pki.SignCSRTemplate([]*x509.Certificate{template}, sk, template)
certBundle, err := pki.SignCSRTemplate([]*x509.Certificate{rootCert}, rootPK, template)
if err != nil {
t.Fatal(err)
}
@ -173,7 +198,7 @@ func TestSign(t *testing.T) {
if err != nil {
t.Fatal(err)
}
certPEM2, _, err := pki.SignCSRTemplate([]*x509.Certificate{template}, sk, template2)
cert2Bundle, err := pki.SignCSRTemplate([]*x509.Certificate{rootCert}, rootPK, template2)
if err != nil {
t.Fatal(err)
}
@ -561,7 +586,7 @@ func TestSign(t *testing.T) {
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
gen.SetOrderState(cmacme.Valid),
gen.SetOrderCertificate(certPEM2),
gen.SetOrderCertificate(cert2Bundle.ChainPEM),
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewDeleteAction(
@ -581,7 +606,7 @@ func TestSign(t *testing.T) {
},
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
gen.SetOrderState(cmacme.Valid),
gen.SetOrderCertificate(certPEM),
gen.SetOrderCertificate(certBundle.ChainPEM),
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
@ -596,7 +621,7 @@ func TestSign(t *testing.T) {
Message: "Certificate fetched from issuer successfully",
LastTransitionTime: &metaFixedClockStart,
}),
gen.SetCertificateRequestCertificate(certPEM),
gen.SetCertificateRequestCertificate(certBundle.ChainPEM),
),
)),
},

View File

@ -18,6 +18,7 @@ package ca
import (
"context"
"crypto"
"crypto/x509"
"fmt"
@ -41,6 +42,7 @@ const (
)
type templateGenerator func(*cmapi.CertificateRequest) (*x509.Certificate, error)
type signingFn func([]*x509.Certificate, crypto.Signer, *x509.Certificate) (pki.PEMBundle, error)
type CA struct {
issuerOptions controllerpkg.IssuerOptions
@ -50,6 +52,7 @@ type CA struct {
// Used for testing to get reproducible resulting certificates
templateGenerator templateGenerator
signingFn signingFn
}
func init() {
@ -67,6 +70,7 @@ func NewCA(ctx *controllerpkg.Context) *CA {
secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder),
templateGenerator: pki.GenerateTemplateFromCertificateRequest,
signingFn: pki.SignCSRTemplate,
}
}
@ -80,7 +84,7 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c
resourceNamespace := c.issuerOptions.ResourceNamespace(issuerObj)
// get a copy of the CA certificate named on the Issuer
caCerts, caKey, err := kube.SecretTLSKeyPair(ctx, c.secretsLister, resourceNamespace, issuerObj.GetSpec().CA.SecretName)
caCerts, caKey, err := kube.SecretTLSKeyPairAndCA(ctx, c.secretsLister, resourceNamespace, issuerObj.GetSpec().CA.SecretName)
if k8sErrors.IsNotFound(err) {
message := fmt.Sprintf("Referenced secret %s/%s not found", resourceNamespace, secretName)
@ -117,7 +121,7 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c
template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints
template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers
certPEM, caPEM, err := pki.SignCSRTemplate(caCerts, caKey, template)
bundle, err := c.signingFn(caCerts, caKey, template)
if err != nil {
message := "Error signing certificate"
c.reporter.Failed(cr, err, "SigningError", message)
@ -128,7 +132,7 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c
log.V(logf.DebugLevel).Info("certificate issued")
return &issuerpkg.IssueResponse{
Certificate: certPEM,
CA: caPEM,
Certificate: bundle.ChainPEM,
CA: bundle.CAPEM,
}, nil
}

View File

@ -17,11 +17,10 @@ limitations under the License.
package ca
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
@ -59,19 +58,18 @@ var (
fixedClock = fakeclock.NewFakeClock(fixedClockStart)
)
func generateCSR(t *testing.T, secretKey crypto.Signer) []byte {
func generateCSR(t *testing.T, secretKey crypto.Signer, sigAlg x509.SignatureAlgorithm) []byte {
asn1Subj, _ := asn1.Marshal(pkix.Name{
CommonName: "test",
}.ToRDNSequence())
template := x509.CertificateRequest{
RawSubject: asn1Subj,
SignatureAlgorithm: x509.SHA256WithRSA,
SignatureAlgorithm: sigAlg,
}
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}
csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
@ -79,27 +77,27 @@ func generateCSR(t *testing.T, secretKey crypto.Signer) []byte {
return csr
}
func generateSelfSignedCertFromCR(t *testing.T, cr *cmapi.CertificateRequest, key crypto.Signer,
duration time.Duration) (*x509.Certificate, []byte) {
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
if err != nil {
t.Errorf("error generating template: %v", err)
func generateSelfSignedCACert(t *testing.T, key crypto.Signer, name string) (*x509.Certificate, []byte) {
tmpl := &x509.Certificate{
Version: 3,
BasicConstraintsValid: true,
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: name,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
PublicKey: key.Public(),
IsCA: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
pem, cert, err := pki.SignCertificate(tmpl, tmpl, key.Public(), key)
if err != nil {
t.Errorf("error signing cert: %v", err)
t.FailNow()
t.Fatal(err)
}
pemByteBuffer := bytes.NewBuffer([]byte{})
err = pem.Encode(pemByteBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
t.Errorf("failed to encode cert: %v", err)
t.FailNow()
}
return template, pemByteBuffer.Bytes()
return cert, pem
}
func TestSign(t *testing.T) {
@ -113,19 +111,24 @@ func TestSign(t *testing.T) {
}),
)
// Build root RSA CA
skRSA, err := pki.GenerateRSAPrivateKey(2048)
rootPK, err := pki.GenerateECPrivateKey(256)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}
rootPKPEM, err := pki.EncodeECPrivateKey(rootPK)
if err != nil {
t.Fatal(err)
}
skRSAPEM := pki.EncodePKCS1PrivateKey(skRSA)
rsaCSR := generateCSR(t, skRSA)
testpk, err := pki.GenerateECPrivateKey(256)
if err != nil {
t.Fatal(err)
}
testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256)
baseCRNotApproved := gen.CertificateRequest("test-cr",
gen.SetCertificateRequestIsCA(true),
gen.SetCertificateRequestCSR(rsaCSR),
gen.SetCertificateRequestCSR(testCSR),
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
Name: baseIssuer.DeepCopy().Name,
Group: certmanager.GroupName,
@ -153,15 +156,15 @@ func TestSign(t *testing.T) {
)
// generate a self signed root ca valid for 60d
_, rsaPEMCert := generateSelfSignedCertFromCR(t, baseCR, skRSA, time.Hour*24*60)
rootCert, rootCertPEM := generateSelfSignedCACert(t, rootPK, "root")
rsaCASecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "root-ca-secret",
Namespace: gen.DefaultTestNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: skRSAPEM,
corev1.TLSCertKey: rsaPEMCert,
corev1.TLSPrivateKeyKey: rootPKPEM,
corev1.TLSCertKey: rootCertPEM,
},
}
@ -170,13 +173,11 @@ func TestSign(t *testing.T) {
template, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}
certPEM, _, err := pki.SignCSRTemplate([]*x509.Certificate{template}, skRSA, template)
certBundle, err := pki.SignCSRTemplate([]*x509.Certificate{rootCert}, rootPK, template)
if err != nil {
t.Error(err)
t.FailNow()
t.Fatal(err)
}
tests := map[string]testT{
@ -373,6 +374,9 @@ func TestSign(t *testing.T) {
return template, nil
},
signingFn: func(_ []*x509.Certificate, _ crypto.Signer, _ *x509.Certificate) (pki.PEMBundle, error) {
return pki.PEMBundle{CAPEM: certBundle.CAPEM, ChainPEM: certBundle.ChainPEM}, nil
},
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{rsaCASecret},
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
@ -392,8 +396,8 @@ func TestSign(t *testing.T) {
Message: "Certificate fetched from issuer successfully",
LastTransitionTime: &metaFixedClockStart,
}),
gen.SetCertificateRequestCA(rsaPEMCert),
gen.SetCertificateRequestCertificate(certPEM),
gen.SetCertificateRequestCertificate(certBundle.ChainPEM),
gen.SetCertificateRequestCA(rootCertPEM),
),
)),
},
@ -414,6 +418,7 @@ type testT struct {
builder *testpkg.Builder
certificateRequest *cmapi.CertificateRequest
templateGenerator templateGenerator
signingFn signingFn
expectedErr bool
@ -434,15 +439,15 @@ func runTest(t *testing.T, test testT) {
if test.templateGenerator != nil {
ca.templateGenerator = test.templateGenerator
}
if test.signingFn != nil {
ca.signingFn = test.signingFn
}
controller := certificaterequests.New(apiutil.IssuerCA, ca)
_, _, err := controller.Register(test.builder.Context)
if err != nil {
t.Errorf("controller.Register failed: %v", err)
}
controller.Register(test.builder.Context)
test.builder.Start()
err = controller.Sync(context.Background(), test.certificateRequest)
err := controller.Sync(context.Background(), test.certificateRequest)
if err != nil && !test.expectedErr {
t.Errorf("expected to not get an error, but got: %v", err)
}
@ -454,9 +459,18 @@ func runTest(t *testing.T, test testT) {
}
func TestCA_Sign(t *testing.T) {
rsaPair, err := pki.GenerateRSAPrivateKey(2048)
require.NoError(t, err)
rsaCSR := generateCSR(t, rsaPair)
rootPK, err := pki.GenerateECPrivateKey(256)
if err != nil {
t.Fatal(err)
}
rootCert, _ := generateSelfSignedCACert(t, rootPK, "root")
// Build test CSR
testpk, err := pki.GenerateECPrivateKey(256)
if err != nil {
t.Fatal(err)
}
testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256)
tests := map[string]struct {
givenCASecret *corev1.Secret
@ -466,11 +480,12 @@ func TestCA_Sign(t *testing.T) {
wantErr string
}{
"when the CertificateRequest has the duration field set, it should appear as notAfter on the signed ca": {
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))),
givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{
SecretName: "secret-1",
})),
givenCR: gen.CertificateRequest("cr-1",
gen.SetCertificateRequestCSR(rsaCSR),
gen.SetCertificateRequestCSR(testCSR),
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
Name: "issuer-1",
Group: certmanager.GroupName,
@ -480,12 +495,6 @@ func TestCA_Sign(t *testing.T) {
Duration: 30 * time.Minute,
}),
),
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rsaPair,
&x509.Certificate{
SerialNumber: big.NewInt(1234),
IsCA: true,
},
))),
assertSignedCert: func(t *testing.T, got *x509.Certificate) {
// Although there is less than 1µs between the time.Now
// call made by the certificate template func (in the "pki"
@ -512,11 +521,12 @@ func TestCA_Sign(t *testing.T) {
},
},
"when the CertificateRequest has the isCA field set, it should appear on the signed ca": {
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))),
givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{
SecretName: "secret-1",
})),
givenCR: gen.CertificateRequest("cr-1",
gen.SetCertificateRequestCSR(rsaCSR),
gen.SetCertificateRequestCSR(testCSR),
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
Name: "issuer-1",
Group: certmanager.GroupName,
@ -524,59 +534,43 @@ func TestCA_Sign(t *testing.T) {
}),
gen.SetCertificateRequestIsCA(true),
),
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rsaPair,
&x509.Certificate{
SerialNumber: big.NewInt(1234),
IsCA: true,
},
))),
assertSignedCert: func(t *testing.T, got *x509.Certificate) {
assert.Equal(t, true, got.IsCA)
},
},
"when the Issuer has ocspServers set, it should appear on the signed ca": {
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))),
givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{
SecretName: "secret-1",
OCSPServers: []string{"http://ocsp-v3.example.org"},
})),
givenCR: gen.CertificateRequest("cr-1",
gen.SetCertificateRequestCSR(rsaCSR),
gen.SetCertificateRequestCSR(testCSR),
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
Name: "issuer-1",
Group: certmanager.GroupName,
Kind: "Issuer",
}),
),
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rsaPair,
&x509.Certificate{
SerialNumber: big.NewInt(1234),
IsCA: true,
},
))),
assertSignedCert: func(t *testing.T, got *x509.Certificate) {
assert.Equal(t, []string{"http://ocsp-v3.example.org"}, got.OCSPServer)
},
},
"when the Issuer has crlDistributionPoints set, it should appear on the signed ca ": {
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))),
givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{
SecretName: "secret-1",
CRLDistributionPoints: []string{"http://www.example.com/crl/test.crl"},
})),
givenCR: gen.CertificateRequest("cr-1",
gen.SetCertificateRequestIsCA(true),
gen.SetCertificateRequestCSR(rsaCSR),
gen.SetCertificateRequestCSR(testCSR),
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
Name: "issuer-1",
Group: certmanager.GroupName,
Kind: "Issuer",
}),
),
givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rsaPair,
&x509.Certificate{
SerialNumber: big.NewInt(1234),
IsCA: true,
},
))),
assertSignedCert: func(t *testing.T, gotCA *x509.Certificate) {
assert.Equal(t, []string{"http://www.example.com/crl/test.crl"}, gotCA.CRLDistributionPoints)
},
@ -597,6 +591,7 @@ func TestCA_Sign(t *testing.T) {
testlisters.SetFakeSecretNamespaceListerGet(test.givenCASecret, nil),
),
templateGenerator: pki.GenerateTemplateFromCertificateRequest,
signingFn: pki.SignCSRTemplate,
}
gotIssueResp, gotErr := c.Sign(context.Background(), test.givenCR, test.givenCAIssuer)
@ -617,12 +612,12 @@ func TestCA_Sign(t *testing.T) {
// Returns a map that is meant to be used for creating a certificate Secret
// that contains the fields "tls.crt" and "tls.key".
func secretDataFor(t *testing.T, caKey *rsa.PrivateKey, caCrt *x509.Certificate) (secretData map[string][]byte) {
func secretDataFor(t *testing.T, caKey *ecdsa.PrivateKey, caCrt *x509.Certificate) (secretData map[string][]byte) {
rootCADER, err := x509.CreateCertificate(rand.Reader, caCrt, caCrt, caKey.Public(), caKey)
require.NoError(t, err)
caCrt, err = x509.ParseCertificate(rootCADER)
require.NoError(t, err)
caKeyPEM, err := pki.EncodePKCS8PrivateKey(caKey)
caKeyPEM, err := pki.EncodeECPrivateKey(caKey)
require.NoError(t, err)
caCrtPEM, err := pki.EncodeX509(caCrt)
require.NoError(t, err)

View File

@ -6,6 +6,7 @@ go_library(
importpath = "github.com/jetstack/cert-manager/pkg/util/kube",
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/meta/v1:go_default_library",
"//pkg/util/errors:go_default_library",
"//pkg/util/pki:go_default_library",
"@io_k8s_api//core/v1:go_default_library",

View File

@ -25,6 +25,7 @@ import (
corev1 "k8s.io/api/core/v1"
corelisters "k8s.io/client-go/listers/core/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/jetstack/cert-manager/pkg/util/errors"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
@ -88,6 +89,32 @@ func SecretTLSCertChain(ctx context.Context, secretLister corelisters.SecretList
return cert, nil
}
// SecretTLSKeyPairAndCA returns the X.509 certificate chain and private key of
// the leaf certificate contained in the target Secret. If the ca.crt field exists
// on the Secret, it is parsed and added to the end of the certificate chain.
func SecretTLSKeyPairAndCA(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) {
certs, key, err := SecretTLSKeyPair(ctx, secretLister, namespace, name)
if err != nil {
return nil, nil, err
}
secret, err := secretLister.Secrets(namespace).Get(name)
if err != nil {
return nil, nil, err
}
caBytes, ok := secret.Data[cmmeta.TLSCAKey]
if !ok || len(caBytes) == 0 {
return certs, key, nil
}
ca, err := pki.DecodeX509CertificateBytes(caBytes)
if err != nil {
return nil, key, errors.NewInvalidData(err.Error())
}
return append(certs, ca), key, nil
}
func SecretTLSKeyPair(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) {
secret, err := secretLister.Secrets(namespace).Get(name)
if err != nil {

View File

@ -418,35 +418,25 @@ func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, p
// SignCSRTemplate signs a certificate template usually based upon a CSR. This
// function expects all fields to be present in the certificate template,
// including it's public key.
// It returns the certificate data followed by the CA data, encoded in PEM format.
func SignCSRTemplate(caCerts []*x509.Certificate, caKey crypto.Signer, template *x509.Certificate) ([]byte, []byte, error) {
// It returns the PEM bundle containing certificate data and the CA data, encoded in PEM format.
func SignCSRTemplate(caCerts []*x509.Certificate, caKey crypto.Signer, template *x509.Certificate) (PEMBundle, error) {
if len(caCerts) == 0 {
return nil, nil, errors.New("no CA certificates given to sign CSR template")
return PEMBundle{}, errors.New("no CA certificates given to sign CSR template")
}
issuingCACert := caCerts[0]
certPem, _, err := SignCertificate(template, issuingCACert, template.PublicKey, caKey)
_, cert, err := SignCertificate(template, issuingCACert, template.PublicKey, caKey)
if err != nil {
return nil, nil, err
return PEMBundle{}, err
}
chainPem, err := EncodeX509Chain(caCerts)
bundle, err := ParseSingleCertificateChain(append(caCerts, cert))
if err != nil {
return nil, nil, err
return PEMBundle{}, err
}
certPem = append(certPem, chainPem...)
// encode the CA certificate to be bundled in the output
caCert := caCerts[len(caCerts)-1]
caPem, err := EncodeX509(caCert)
if err != nil {
return nil, nil, err
}
return certPem, caPem, nil
return bundle, nil
}
// EncodeCSR calls x509.CreateCertificateRequest to sign the given CSR template.

View File

@ -25,6 +25,7 @@ import (
"math/big"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -564,63 +565,46 @@ func TestSignCSRTemplate(t *testing.T) {
// for that, we construct a chain of four certificates:
// a root CA, two intermediate CA, and a leaf certificate.
// We start with a root CA:
rootPrivKey, err := GenerateRSAPrivateKey(MinRSAKeySize)
require.NoError(t, err)
rootTemplate := &x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: "testing-root",
},
PublicKey: rootPrivKey.Public(),
IsCA: true,
}
rootPem, rootCert, err := SignCertificate(rootTemplate, rootTemplate, rootTemplate.PublicKey, rootPrivKey)
require.NoError(t, err)
mustCreatePair := func(issuerCert *x509.Certificate, issuerPK crypto.Signer, name string, isCA bool) ([]byte, *x509.Certificate, *x509.Certificate, crypto.Signer) {
pk, err := GenerateECPrivateKey(256)
require.NoError(t, err)
tmpl := &x509.Certificate{
Version: 3,
BasicConstraintsValid: true,
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: name,
},
PublicKeyAlgorithm: x509.ECDSA,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Minute),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
PublicKey: pk.Public(),
IsCA: isCA,
}
// Then, we want an intermediate CA:
inter1PrivKey, err := GenerateRSAPrivateKey(MinRSAKeySize)
require.NoError(t, err)
inter1Template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "testing-intermediate-1",
},
PublicKey: inter1PrivKey.Public(),
IsCA: true,
}
inter1Pem, inter1Cert, err := SignCertificate(inter1Template, rootCert, inter1Template.PublicKey, rootPrivKey)
require.NoError(t, err)
if isCA {
tmpl.KeyUsage |= x509.KeyUsageCertSign
}
// Then, we want another intermediate CA:
inter2PrivKey, err := GenerateRSAPrivateKey(MinRSAKeySize)
require.NoError(t, err)
inter2Template := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{
CommonName: "testing-intermediate-2",
},
PublicKey: inter2PrivKey.Public(),
IsCA: true,
}
inter2Pem, inter2Cert, err := SignCertificate(inter2Template, inter1Cert, inter2Template.PublicKey, inter1PrivKey)
require.NoError(t, err)
if issuerCert == nil {
issuerCert = tmpl
}
if issuerPK == nil {
issuerPK = pk
}
// And finally, we also want a leaf certificate:
leafPrivKey, err := GenerateRSAPrivateKey(MinRSAKeySize)
require.NoError(t, err)
leafTemplate := &x509.Certificate{
SerialNumber: big.NewInt(3),
Subject: pkix.Name{
CommonName: "testing-leaf",
},
PublicKey: leafPrivKey.Public(),
pem, cert, err := SignCertificate(tmpl, issuerCert, tmpl.PublicKey, issuerPK)
require.NoError(t, err)
return pem, cert, tmpl, pk
}
leafPem, _, err := SignCertificate(leafTemplate, inter2Cert, leafTemplate.PublicKey, inter2PrivKey)
require.NoError(t, err)
tests := []struct {
name string
rootPEM, rootCert, rootTmpl, rootPK := mustCreatePair(nil, nil, "root", true)
int1PEM, int1Cert, int1Tmpl, int1PK := mustCreatePair(rootCert, rootPK, "int1", true)
int2PEM, int2Cert, int2Tmpl, int2PK := mustCreatePair(int1Cert, int1PK, "int2", true)
leafPEM, _, leafTmpl, _ := mustCreatePair(int2Cert, int2PK, "leaf", false)
tests := map[string]struct {
caCerts []*x509.Certificate
caKey crypto.Signer
template *x509.Certificate
@ -628,68 +612,64 @@ func TestSignCSRTemplate(t *testing.T) {
expectedCaCertPem []byte
wantErr bool
}{
{
name: "Sign intermediate 1 template",
"Sign intermediate 1 template": {
caCerts: []*x509.Certificate{rootCert},
caKey: rootPrivKey,
template: inter1Template,
expectedCertPem: inter1Pem,
expectedCaCertPem: rootPem,
caKey: rootPK,
template: int1Tmpl,
expectedCertPem: int1PEM,
expectedCaCertPem: rootPEM,
wantErr: false,
},
{
name: "Sign intermediate 2 template",
caCerts: []*x509.Certificate{inter1Cert, rootCert},
caKey: inter1PrivKey,
template: inter2Template,
expectedCertPem: append(inter2Pem, inter1Pem...),
expectedCaCertPem: rootPem,
"Sign intermediate 2 template": {
caCerts: []*x509.Certificate{int1Cert, rootCert},
caKey: int1PK,
template: int2Tmpl,
expectedCertPem: append(int2PEM, int1PEM...),
expectedCaCertPem: rootPEM,
wantErr: false,
},
{
name: "Sign leaf template",
caCerts: []*x509.Certificate{inter2Cert, inter1Cert, rootCert},
caKey: inter2PrivKey,
template: leafTemplate,
expectedCertPem: append(append(leafPem, inter1Pem...), inter2Pem...),
expectedCaCertPem: rootPem,
"Sign leaf template": {
caCerts: []*x509.Certificate{int2Cert, int1Cert, rootCert},
caKey: int2PK,
template: leafTmpl,
expectedCertPem: append(append(leafPEM, int1PEM...), int2PEM...),
expectedCaCertPem: rootPEM,
wantErr: false,
},
{
name: "Sign leaf template no root",
caCerts: []*x509.Certificate{inter2Cert, inter1Cert},
caKey: inter2PrivKey,
template: leafTemplate,
expectedCertPem: append(leafPem, inter2Pem...),
expectedCaCertPem: inter1Pem,
"Sign leaf template no root": {
caCerts: []*x509.Certificate{int2Cert, int1Cert},
caKey: int2PK,
template: leafTmpl,
expectedCertPem: append(leafPEM, int2PEM...),
expectedCaCertPem: int1PEM,
wantErr: false,
},
{
name: "Error on no CA",
caKey: rootPrivKey,
template: rootTemplate,
"Error on no CA": {
caKey: rootPK,
template: rootTmpl,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actualCertPem, actualCaCertPem, err := SignCSRTemplate(tt.caCerts, tt.caKey, tt.template)
if (err != nil) != tt.wantErr {
t.Errorf("TestSignCSRTemplate() error = %v, wantErr %v", err, tt.wantErr)
for name, test := range tests {
t.Run(name, func(t *testing.T) {
actualBundle, err := SignCSRTemplate(test.caCerts, test.caKey, test.template)
if (err != nil) != test.wantErr {
t.Errorf("TestSignCSRTemplate() error = %v, wantErr %v", err, test.wantErr)
return
}
if !bytes.Equal(tt.expectedCertPem, actualCertPem) {
if !bytes.Equal(test.expectedCertPem, actualBundle.ChainPEM) {
// To help us identify where the mismatch is, we decode turn the
// into strings and do a textual diff.
expected, _ := DecodeX509CertificateBytes(tt.expectedCertPem)
actual, _ := DecodeX509CertificateBytes(actualCertPem)
expected, _ := DecodeX509CertificateBytes(test.expectedCertPem)
actual, _ := DecodeX509CertificateBytes(actualBundle.ChainPEM)
assert.Equal(t, expected.Subject.String(), actual.Subject.String())
}
if !bytes.Equal(tt.expectedCaCertPem, actualCaCertPem) {
if !bytes.Equal(test.expectedCaCertPem, actualBundle.CAPEM) {
// To help us identify where the mismatch is, we decode turn the
// into strings and do a textual diff.
expected, _ := DecodeX509CertificateBytes(tt.expectedCaCertPem)
actual, _ := DecodeX509CertificateBytes(actualCaCertPem)
expected, _ := DecodeX509CertificateBytes(test.expectedCaCertPem)
actual, _ := DecodeX509CertificateBytes(actualBundle.CAPEM)
assert.Equal(t, expected.Subject.String(), actual.Subject.String())
}
})