Create common GenerateCSR and GenerateTemplate methods for creating Certificate/CertificateRequest

This commit is contained in:
James Munnelly 2018-06-08 15:11:48 +01:00
parent 4a5fe3823e
commit 1fd8cdf13e
4 changed files with 134 additions and 129 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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",
}

View File

@ -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
}