forked from TrueCloudLab/certificates
225 lines
7.1 KiB
Go
225 lines
7.1 KiB
Go
package softcas
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/certificates/cas/apiv1"
|
|
"github.com/smallstep/certificates/kms"
|
|
kmsapi "github.com/smallstep/certificates/kms/apiv1"
|
|
"go.step.sm/crypto/x509util"
|
|
)
|
|
|
|
func init() {
|
|
apiv1.Register(apiv1.SoftCAS, func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) {
|
|
return New(ctx, opts)
|
|
})
|
|
}
|
|
|
|
var now = func() time.Time {
|
|
return time.Now()
|
|
}
|
|
|
|
// SoftCAS implements a Certificate Authority Service using Golang or KMS
|
|
// crypto. This is the default CAS used in step-ca.
|
|
type SoftCAS struct {
|
|
CertificateChain []*x509.Certificate
|
|
Signer crypto.Signer
|
|
KeyManager kms.KeyManager
|
|
}
|
|
|
|
// New creates a new CertificateAuthorityService implementation using Golang or KMS
|
|
// crypto.
|
|
func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) {
|
|
if !opts.IsCreator {
|
|
switch {
|
|
case len(opts.CertificateChain) == 0:
|
|
return nil, errors.New("softCAS 'CertificateChain' cannot be nil")
|
|
case opts.Signer == nil:
|
|
return nil, errors.New("softCAS 'signer' cannot be nil")
|
|
}
|
|
}
|
|
return &SoftCAS{
|
|
CertificateChain: opts.CertificateChain,
|
|
Signer: opts.Signer,
|
|
KeyManager: opts.KeyManager,
|
|
}, nil
|
|
}
|
|
|
|
// CreateCertificate signs a new certificate using Golang or KMS crypto.
|
|
func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
|
|
switch {
|
|
case req.Template == nil:
|
|
return nil, errors.New("createCertificateRequest `template` cannot be nil")
|
|
case req.Lifetime == 0:
|
|
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
|
}
|
|
|
|
t := now()
|
|
// Provisioners can also set specific values.
|
|
if req.Template.NotBefore.IsZero() {
|
|
req.Template.NotBefore = t.Add(-1 * req.Backdate)
|
|
}
|
|
if req.Template.NotAfter.IsZero() {
|
|
req.Template.NotAfter = t.Add(req.Lifetime)
|
|
}
|
|
req.Template.Issuer = c.CertificateChain[0].Subject
|
|
|
|
cert, err := createCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &apiv1.CreateCertificateResponse{
|
|
Certificate: cert,
|
|
CertificateChain: c.CertificateChain,
|
|
}, nil
|
|
}
|
|
|
|
// RenewCertificate signs the given certificate template using Golang or KMS crypto.
|
|
func (c *SoftCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
|
|
switch {
|
|
case req.Template == nil:
|
|
return nil, errors.New("createCertificateRequest `template` cannot be nil")
|
|
case req.Lifetime == 0:
|
|
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
|
}
|
|
|
|
t := now()
|
|
req.Template.NotBefore = t.Add(-1 * req.Backdate)
|
|
req.Template.NotAfter = t.Add(req.Lifetime)
|
|
req.Template.Issuer = c.CertificateChain[0].Subject
|
|
|
|
cert, err := createCertificate(req.Template, c.CertificateChain[0], req.Template.PublicKey, c.Signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &apiv1.RenewCertificateResponse{
|
|
Certificate: cert,
|
|
CertificateChain: c.CertificateChain,
|
|
}, nil
|
|
}
|
|
|
|
// RevokeCertificate revokes the given certificate in step-ca. In SoftCAS this
|
|
// operation is a no-op as the actual revoke will happen when we store the entry
|
|
// in the db.
|
|
func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
|
|
return &apiv1.RevokeCertificateResponse{
|
|
Certificate: req.Certificate,
|
|
CertificateChain: c.CertificateChain,
|
|
}, nil
|
|
}
|
|
|
|
// CreateCertificateAuthority creates a root or an intermediate certificate.
|
|
func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) {
|
|
switch {
|
|
case req.Template == nil:
|
|
return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil")
|
|
case req.Lifetime == 0:
|
|
return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0")
|
|
case req.Type == apiv1.IntermediateCA && req.Parent == nil:
|
|
return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil")
|
|
case req.Type == apiv1.IntermediateCA && req.Parent.Certificate == nil:
|
|
return nil, errors.New("createCertificateAuthorityRequest `parent.template` cannot be nil")
|
|
case req.Type == apiv1.IntermediateCA && req.Parent.Signer == nil:
|
|
return nil, errors.New("createCertificateAuthorityRequest `parent.signer` cannot be nil")
|
|
}
|
|
|
|
key, err := c.createKey(req.CreateKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signer, err := c.createSigner(&key.CreateSignerRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
t := now()
|
|
if req.Template.NotBefore.IsZero() {
|
|
req.Template.NotBefore = t.Add(-1 * req.Backdate)
|
|
}
|
|
if req.Template.NotAfter.IsZero() {
|
|
req.Template.NotAfter = t.Add(req.Lifetime)
|
|
}
|
|
|
|
var cert *x509.Certificate
|
|
switch req.Type {
|
|
case apiv1.RootCA:
|
|
cert, err = createCertificate(req.Template, req.Template, signer.Public(), signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case apiv1.IntermediateCA:
|
|
cert, err = createCertificate(req.Template, req.Parent.Certificate, signer.Public(), req.Parent.Signer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type)
|
|
}
|
|
|
|
// Add the parent
|
|
var chain []*x509.Certificate
|
|
if req.Parent != nil {
|
|
chain = append(chain, req.Parent.Certificate)
|
|
chain = append(chain, req.Parent.CertificateChain...)
|
|
}
|
|
|
|
return &apiv1.CreateCertificateAuthorityResponse{
|
|
Name: cert.Subject.CommonName,
|
|
Certificate: cert,
|
|
CertificateChain: chain,
|
|
PublicKey: key.PublicKey,
|
|
PrivateKey: key.PrivateKey,
|
|
Signer: signer,
|
|
}, nil
|
|
}
|
|
|
|
// initializeKeyManager initiazes the default key manager if was not given.
|
|
func (c *SoftCAS) initializeKeyManager() (err error) {
|
|
if c.KeyManager == nil {
|
|
c.KeyManager, err = kms.New(context.Background(), kmsapi.Options{
|
|
Type: string(kmsapi.DefaultKMS),
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// createKey uses the configured kms to create a key.
|
|
func (c *SoftCAS) createKey(req *kmsapi.CreateKeyRequest) (*kmsapi.CreateKeyResponse, error) {
|
|
if err := c.initializeKeyManager(); err != nil {
|
|
return nil, err
|
|
}
|
|
if req == nil {
|
|
req = &kmsapi.CreateKeyRequest{
|
|
SignatureAlgorithm: kmsapi.ECDSAWithSHA256,
|
|
}
|
|
}
|
|
return c.KeyManager.CreateKey(req)
|
|
}
|
|
|
|
// createSigner uses the configured kms to create a singer
|
|
func (c *SoftCAS) createSigner(req *kmsapi.CreateSignerRequest) (crypto.Signer, error) {
|
|
if err := c.initializeKeyManager(); err != nil {
|
|
return nil, err
|
|
}
|
|
return c.KeyManager.CreateSigner(req)
|
|
}
|
|
|
|
// createCertificate sets the SignatureAlgorithm of the template if necessary
|
|
// and calls x509util.CreateCertificate.
|
|
func createCertificate(template, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) {
|
|
// Signers can specify the signature algorithm. This is especially important
|
|
// when x509.CreateCertificate attempts to validate a RSAPSS signature.
|
|
if template.SignatureAlgorithm == 0 {
|
|
if sa, ok := signer.(apiv1.SignatureAlgorithmGetter); ok {
|
|
template.SignatureAlgorithm = sa.SignatureAlgorithm()
|
|
}
|
|
}
|
|
return x509util.CreateCertificate(template, parent, pub, signer)
|
|
}
|