diff --git a/pkg/issuer/acme/issue.go b/pkg/issuer/acme/issue.go index 5222ce1b3..b00c5e2d4 100644 --- a/pkg/issuer/acme/issue.go +++ b/pkg/issuer/acme/issue.go @@ -3,8 +3,6 @@ package acme import ( "bytes" "context" - "crypto/rand" - "crypto/x509" "encoding/pem" "fmt" @@ -62,15 +60,20 @@ func (a *Acme) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate) } // generate a csr - template := pki.GenerateCSR(commonName, altNames...) - csr, err := x509.CreateCertificateRequest(rand.Reader, template, key) + template, err := pki.GenerateCSR(a.issuer, crt) + if err != nil { + // TODO: this should probably be classed as a permanant failure + return nil, nil, err + } + + derBytes, err := pki.EncodeCSR(template, key) if err != nil { crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueError, fmt.Sprintf("Failed to generate certificate request: %v", err), false) - return nil, nil, fmt.Errorf("error creating certificate request: %s", err) + return nil, nil, err } // obtain a certificate from the acme server - certSlice, err := cl.FinalizeOrder(ctx, order.FinalizeURL, csr) + certSlice, err := cl.FinalizeOrder(ctx, order.FinalizeURL, derBytes) if err != nil { // this handles an edge case where a certificate ends out with an order // that is in an invalid state. diff --git a/pkg/issuer/ca/issue.go b/pkg/issuer/ca/issue.go index 1df8e0d2e..96c06ec6b 100644 --- a/pkg/issuer/ca/issue.go +++ b/pkg/issuer/ca/issue.go @@ -1,15 +1,8 @@ package ca import ( - "bytes" "context" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" - "math/big" - "time" k8sErrors "k8s.io/apimachinery/pkg/api/errors" @@ -31,12 +24,6 @@ const ( messageCertIssued = "Certificate issued successfully" ) -const ( - // certificateDuration of 1 year - certificateDuration = time.Hour * 24 * 365 - defaultOrganization = "cert-manager" -) - func (c *CA) Issue(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []byte, error) { signeeKey, err := kube.SecretTLSKey(c.secretsLister, crt.Namespace, crt.Spec.SecretName) @@ -80,78 +67,15 @@ func (c *CA) obtainCertificate(crt *v1alpha1.Certificate, signeeKey interface{}) return nil, fmt.Errorf("error getting issuer private key: %s", err.Error()) } - crtPem, _, err := signCertificate(crt, signerCert, signeeKey, signerKey) + template, err := pki.GenerateTemplate(c.issuer, crt, nil) + if err != nil { + return nil, err + } + + crtPem, _, err := pki.SignCertificate(template, signerCert, signeeKey, signerKey) if err != nil { return nil, err } return crtPem, nil } - -func createCertificateTemplate(publicKey interface{}, commonName string, altNames ...string) (*x509.Certificate, error) { - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, fmt.Errorf("failed to generate serial number: %s", err.Error()) - } - if len(commonName) == 0 && len(altNames) > 0 { - commonName = altNames[0] - } - cert := &x509.Certificate{ - Version: 3, - BasicConstraintsValid: true, - SerialNumber: serialNumber, - SignatureAlgorithm: x509.SHA256WithRSA, - PublicKey: publicKey, - Subject: pkix.Name{ - Organization: []string{defaultOrganization}, - CommonName: commonName, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(certificateDuration), - // see http://golang.org/pkg/crypto/x509/#KeyUsage - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - DNSNames: altNames, - } - return cert, nil -} - -// signCertificate returns a signed x509.Certificate object for the given -// *v1alpha1.Certificate crt. -// publicKey is the public key of the signee, and signerKey is the private -// key of the signer. -func signCertificate(crt *v1alpha1.Certificate, issuerCert *x509.Certificate, publicKey interface{}, signerKey interface{}) ([]byte, *x509.Certificate, error) { - cn := pki.CommonNameForCertificate(crt) - dnsNames := pki.DNSNamesForCertificate(crt) - - template, err := createCertificateTemplate(publicKey, cn, dnsNames...) - if err != nil { - return nil, nil, fmt.Errorf("error creating x509 certificate template: %s", err.Error()) - } - - derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey) - - if err != nil { - return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error()) - } - - cert, err := pki.DecodeDERCertificateBytes(derBytes) - - if err != nil { - return nil, nil, fmt.Errorf("error decoding DER certificate bytes: %s", err.Error()) - } - - pemBytes := bytes.NewBuffer([]byte{}) - err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - if err != nil { - return nil, nil, fmt.Errorf("error encoding certificate PEM: %s", err.Error()) - } - - // bundle the CA - err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: issuerCert.Raw}) - if err != nil { - return nil, nil, fmt.Errorf("error encoding issuer cetificate PEM: %s", err.Error()) - } - - return pemBytes.Bytes(), cert, err -} diff --git a/pkg/issuer/vault/issue.go b/pkg/issuer/vault/issue.go index 301216b21..6f6730202 100644 --- a/pkg/issuer/vault/issue.go +++ b/pkg/issuer/vault/issue.go @@ -3,9 +3,6 @@ package vault import ( "bytes" "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" "encoding/pem" "fmt" "path" @@ -36,8 +33,6 @@ const ( ) const ( - defaultOrganization = "cert-manager" - keyBitSize = 2048 ) @@ -68,45 +63,28 @@ func (v *Vault) obtainCertificate(ctx context.Context, crt *v1alpha1.Certificate return nil, nil, fmt.Errorf("error getting certificate private key: %s", err.Error()) } - commonName := crt.Spec.CommonName - altNames := crt.Spec.DNSNames - if len(commonName) == 0 && len(altNames) == 0 { - return nil, nil, fmt.Errorf("no domains specified on certificate") - } - - crtPem, err := v.signCertificate(crt, signeeKey) + template, err := pki.GenerateCSR(v.issuer, crt) if err != nil { return nil, nil, err } - return pki.EncodePKCS1PrivateKey(signeeKey), crtPem, nil -} - -// signCertificate returns a signed x509.Certificate object for the given -// *v1alpha1.Certificate crt. -func (v *Vault) signCertificate(crt *v1alpha1.Certificate, key *rsa.PrivateKey) ([]byte, error) { - commonName := pki.CommonNameForCertificate(crt) - altNames := pki.DNSNamesForCertificate(crt) - - if len(commonName) == 0 && len(altNames) > 0 { - commonName = altNames[0] - } - - template := pki.GenerateCSR(commonName, altNames...) - template.Subject.Organization = []string{defaultOrganization} - - derBytes, err := x509.CreateCertificateRequest(rand.Reader, template, key) + derBytes, err := pki.EncodeCSR(template, signeeKey) if err != nil { - return nil, fmt.Errorf("error creating x509 certificate: %s", err.Error()) + return nil, nil, err } pemRequestBuf := &bytes.Buffer{} err = pem.Encode(pemRequestBuf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: derBytes}) if err != nil { - return nil, fmt.Errorf("error encoding certificate request: %s", err.Error()) + return nil, nil, fmt.Errorf("error encoding certificate request: %s", err.Error()) } - return v.requestVaultCert(commonName, altNames, pemRequestBuf.String()) + crtBytes, err := v.requestVaultCert(template.Subject.CommonName, template.DNSNames, pemRequestBuf.Bytes()) + if err != nil { + return nil, nil, err + } + + return pki.EncodePKCS1PrivateKey(signeeKey), crtBytes, nil } func (v *Vault) initVaultClient() (*vault.Client, error) { @@ -183,7 +161,7 @@ func (v *Vault) requestTokenWithAppRoleRef(client *vault.Client, appRole *v1alph return token, nil } -func (v *Vault) requestVaultCert(commonName string, altNames []string, csr string) ([]byte, error) { +func (v *Vault) requestVaultCert(commonName string, altNames []string, csr []byte) ([]byte, error) { client, err := v.initVaultClient() if err != nil { return nil, err @@ -195,7 +173,7 @@ func (v *Vault) requestVaultCert(commonName string, altNames []string, csr strin "common_name": commonName, "alt_names": strings.Join(altNames, ","), "ttl": defaultCertificateDuration.String(), - "csr": csr, + "csr": string(csr), "exclude_cn_from_sans": "true", } diff --git a/pkg/util/pki/csr.go b/pkg/util/pki/csr.go index bc8f75af2..50dce0362 100644 --- a/pkg/util/pki/csr.go +++ b/pkg/util/pki/csr.go @@ -1,8 +1,14 @@ package pki import ( + "bytes" + "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1" "github.com/jetstack/cert-manager/pkg/util" @@ -31,12 +37,106 @@ func DNSNamesForCertificate(crt *v1alpha1.Certificate) []string { return crt.Spec.DNSNames } -func GenerateCSR(commonName string, altNames ...string) *x509.CertificateRequest { - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: commonName, - }, - DNSNames: altNames, +var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) + +// TODO: allow this to be configurable +const defaultOrganization = "cert-manager" + +const defaultSignatureAlgorithm = x509.SHA256WithRSA + +// default certification duration is 1 year +const defaultNotAfter = time.Hour * 24 * 365 + +func GenerateCSR(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate) (*x509.CertificateRequest, error) { + commonName := CommonNameForCertificate(crt) + dnsNames := DNSNamesForCertificate(crt) + if len(commonName) == 0 && len(dnsNames) == 0 { + return nil, fmt.Errorf("no domains specified on certificate") } - return &template + + return &x509.CertificateRequest{ + Version: 3, + SignatureAlgorithm: defaultSignatureAlgorithm, + Subject: pkix.Name{ + Organization: []string{defaultOrganization}, + CommonName: commonName, + }, + DNSNames: dnsNames, + // TODO: work out how best to handle extensions/key usages here + ExtraExtensions: []pkix.Extension{}, + }, nil +} + +// GenerateTemplate will create a x509.Certificate for the given Certificate resource. +// This should create a Certificate template that is equivalent to the CertificateRequest +// generated by GenerateCSR. +// The PublicKey field must be populated by the caller. +func GenerateTemplate(issuer v1alpha1.GenericIssuer, crt *v1alpha1.Certificate, serialNo *big.Int) (*x509.Certificate, error) { + commonName := CommonNameForCertificate(crt) + dnsNames := DNSNamesForCertificate(crt) + if len(commonName) == 0 && len(dnsNames) == 0 { + return nil, fmt.Errorf("no domains specified on certificate") + } + + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, fmt.Errorf("failed to generate serial number: %s", err.Error()) + } + + return &x509.Certificate{ + Version: 3, + BasicConstraintsValid: true, + SerialNumber: serialNumber, + SignatureAlgorithm: defaultSignatureAlgorithm, + Subject: pkix.Name{ + Organization: []string{defaultOrganization}, + CommonName: commonName, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(defaultNotAfter), + // see http://golang.org/pkg/crypto/x509/#KeyUsage + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + DNSNames: dnsNames, + }, nil +} + +// SignCertificate returns a signed x509.Certificate object for the given +// *v1alpha1.Certificate crt. +// publicKey is the public key of the signee, and signerKey is the private +// key of the signer. +func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey interface{}, signerKey interface{}) ([]byte, *x509.Certificate, error) { + derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey) + + if err != nil { + return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error()) + } + + cert, err := DecodeDERCertificateBytes(derBytes) + + if err != nil { + return nil, nil, fmt.Errorf("error decoding DER certificate bytes: %s", err.Error()) + } + + pemBytes := bytes.NewBuffer([]byte{}) + err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if err != nil { + return nil, nil, fmt.Errorf("error encoding certificate PEM: %s", err.Error()) + } + + // bundle the CA + err = pem.Encode(pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: issuerCert.Raw}) + if err != nil { + return nil, nil, fmt.Errorf("error encoding issuer cetificate PEM: %s", err.Error()) + } + + return pemBytes.Bytes(), cert, err +} + +func EncodeCSR(template *x509.CertificateRequest, key interface{}) ([]byte, error) { + derBytes, err := x509.CreateCertificateRequest(rand.Reader, template, key) + if err != nil { + return nil, fmt.Errorf("error creating x509 certificate: %s", err.Error()) + } + + return derBytes, nil }