From 180263249a4495e9b15d020562db68b17dc5b1e7 Mon Sep 17 00:00:00 2001
From: Mariano Cano <mariano@smallstep.com>
Date: Mon, 12 Aug 2019 16:11:28 -0700
Subject: [PATCH] Add provisioner code used to sign certificates.

---
 provisioners/step.go | 153 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)
 create mode 100644 provisioners/step.go

diff --git a/provisioners/step.go b/provisioners/step.go
new file mode 100644
index 0000000..847f9f5
--- /dev/null
+++ b/provisioners/step.go
@@ -0,0 +1,153 @@
+package provisioners
+
+import (
+	"bytes"
+	"context"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"sync"
+
+	certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
+	capi "github.com/smallstep/certificates/api"
+	"github.com/smallstep/certificates/ca"
+	api "github.com/smallstep/step-issuer/api/v1beta1"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var collection = new(sync.Map)
+
+// Step implements a Step JWK provisioners in charge of signing certificate
+// requests using step certificates.
+type Step struct {
+	uid         types.UID
+	provisioner *ca.Provisioner
+}
+
+// New returns a new Step provisioner, configured with the information in the
+// given issuer.
+func New(iss *api.StepIssuer, password []byte) (*Step, error) {
+	var options []ca.ClientOption
+	if len(iss.Spec.CABundle) > 0 {
+		options = append(options, ca.WithCABundle(iss.Spec.CABundle))
+	}
+	provisioner, err := ca.NewProvisioner(iss.Spec.Provisioner.Name, iss.Spec.Provisioner.KeyID, iss.Spec.URL, password, options...)
+	if err != nil {
+		return nil, err
+	}
+	return &Step{
+		provisioner: provisioner,
+	}, nil
+}
+
+// Load returns a Step provisioner by NamespacedName.
+func Load(namespacedName types.NamespacedName) (*Step, bool) {
+	v, ok := collection.Load(namespacedName)
+	if !ok {
+		return nil, ok
+	}
+	p, ok := v.(*Step)
+	return p, ok
+}
+
+// Store adds a new provisioner to the collection by NamespacedName.
+func Store(namespacedName types.NamespacedName, provisioner *Step) {
+	collection.Store(namespacedName, provisioner)
+}
+
+// Sign sends the certificate requests to the Step CA and returns the signed
+// certificate.
+func (s *Step) Sign(ctx context.Context, cr *certmanager.CertificateRequest) ([]byte, []byte, error) {
+	// Get root certificate(s)
+	roots, err := s.provisioner.Roots()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Encode root certificates
+	var caPem []byte
+	for _, root := range roots.Certificates {
+		b, err := encodeX509(root.Certificate)
+		if err != nil {
+			return nil, nil, err
+		}
+		caPem = append(caPem, b...)
+	}
+
+	// decode and check certificate request
+	csr, err := decodeCSR(cr.Spec.CSRPEM)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var sans []string
+	for _, dns := range csr.DNSNames {
+		sans = append(sans, dns)
+	}
+	for _, ip := range csr.IPAddresses {
+		sans = append(sans, ip.String())
+	}
+
+	token, err := s.provisioner.Token(csr.Subject.CommonName, sans...)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var notAfter capi.TimeDuration
+	if cr.Spec.Duration != nil {
+		notAfter.SetDuration(cr.Spec.Duration.Duration)
+	}
+
+	resp, err := s.provisioner.Sign(&capi.SignRequest{
+		CsrPEM: capi.CertificateRequest{
+			CertificateRequest: csr,
+		},
+		OTT:      token,
+		NotAfter: notAfter,
+	})
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Encode server certificate with the intermediate
+	certPem, err := encodeX509(resp.ServerPEM.Certificate)
+	if err != nil {
+		return nil, nil, err
+	}
+	chainPem, err := encodeX509(resp.CaPEM.Certificate)
+	if err != nil {
+		return nil, nil, err
+	}
+	certPem = append(certPem, chainPem...)
+
+	return certPem, caPem, nil
+}
+
+// decodeCSR decodes a certificate request in PEM format and returns the
+func decodeCSR(data []byte) (*x509.CertificateRequest, error) {
+	block, rest := pem.Decode(data)
+	if block == nil || len(rest) > 0 {
+		return nil, fmt.Errorf("unexpected CSR PEM on sign request")
+	}
+	if block.Type != "CERTIFICATE REQUEST" {
+		return nil, fmt.Errorf("PEM is not a certificate request")
+	}
+	csr, err := x509.ParseCertificateRequest(block.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing certificate request: %v", err)
+	}
+	if err := csr.CheckSignature(); err != nil {
+		return nil, fmt.Errorf("error checking certificate request signature: %v", err)
+	}
+	return csr, nil
+}
+
+// encodeX509 will encode a *x509.Certificate into PEM format.
+func encodeX509(cert *x509.Certificate) ([]byte, error) {
+	caPem := bytes.NewBuffer([]byte{})
+	err := pem.Encode(caPem, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
+	if err != nil {
+		return nil, err
+	}
+	return caPem.Bytes(), nil
+}
-- 
GitLab