Pass issuer and signer to softCAS options.

Remove commented code and initialize CAS properly.
Minor fixes in CloudCAS.
pull/367/head
Mariano Cano 4 years ago
parent c8d9cb0a1d
commit aad8f9e582

@ -148,18 +148,6 @@ func (a *Authority) init() error {
}
}
// Initialize the X.509 CA Service if it has not been set in the options
if a.x509CAService == nil {
var options casapi.Options
if a.config.CAS != nil {
options = *a.config.CAS
}
a.x509CAService, err = cas.New(context.Background(), options)
if err != nil {
return nil
}
}
// Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
@ -206,15 +194,51 @@ func (a *Authority) init() error {
if err != nil {
return err
}
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
a.x509Issuer = crt
// Read signer only is the CAS is the default one.
if a.config.CAS.HasType(casapi.SoftCAS) {
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
a.x509Signer = signer
}
}
// Initialize the X.509 CA Service if it has not been set in the options
if a.x509CAService == nil {
var options casapi.Options
if a.config.CAS != nil {
options = *a.config.CAS
}
// Set issuer and signer for default CAS.
if options.HasType(casapi.SoftCAS) {
crt, err := pemutil.ReadCertificate(a.config.IntermediateCert)
if err != nil {
return err
}
signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
options.Issuer = crt
options.Signer = signer
}
a.x509CAService, err = cas.New(context.Background(), options)
if err != nil {
return err
return nil
}
a.x509Signer = signer
a.x509Issuer = crt
}
// Decrypt and load SSH keys

@ -187,7 +187,7 @@ func (c *Config) Validate() error {
case c.IntermediateCert == "":
return errors.New("crt cannot be empty")
case c.IntermediateKey == "":
case c.IntermediateKey == "" && c.CAS.HasType(cas.SoftCAS):
return errors.New("key cannot be empty")
case len(c.DNSNames) == 0:

@ -145,32 +145,24 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
}
}
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(-1 * signOpts.Backdate))
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(signOpts.Backdate))
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: leaf,
Issuer: a.x509Issuer,
Signer: a.x509Signer,
Lifetime: lifetime,
Backdate: signOpts.Backdate,
})
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...)
}
serverCert := resp.Certificate
// serverCert, err := x509util.CreateCertificate(leaf, a.x509Issuer, csr.PublicKey, a.x509Signer)
// if err != nil {
// return nil, errs.Wrap(http.StatusInternalServerError, err,
// "authority.Sign; error creating certificate", opts...)
// }
if err = a.db.StoreCertificate(serverCert); err != nil {
if err = a.db.StoreCertificate(resp.Certificate); err != nil {
if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error storing certificate in db", opts...)
}
}
return []*x509.Certificate{serverCert, a.x509Issuer}, nil
return append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...), nil
}
// Renew creates a new Certificate identical to the old certificate, except
@ -200,13 +192,12 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
// Durations
backdate := a.config.AuthorityConfig.Backdate.Duration
duration := oldCert.NotAfter.Sub(oldCert.NotBefore)
now := time.Now().UTC()
lifetime := duration - backdate
// Create new certificate from previous values.
// Issuer, NotBefore, NotAfter and SubjectKeyId will be set by the CAS.
newCert := &x509.Certificate{
Issuer: a.x509Issuer.Subject,
Subject: oldCert.Subject,
NotBefore: now.Add(-1 * backdate),
NotAfter: now.Add(duration - backdate),
KeyUsage: oldCert.KeyUsage,
UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions,
ExtKeyUsage: oldCert.ExtKeyUsage,
@ -241,10 +232,14 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
}
// Copy all extensions except:
// 1. Authority Key Identifier - This one might be different if we rotate the intermediate certificate
// and it will cause a TLS bad certificate error.
// 2. Subject Key Identifier, if rekey - For rekey, SubjectKeyIdentifier extension will be calculated
// for the new public key by NewLeafProfilewithTemplate()
//
// 1. Authority Key Identifier - This one might be different if we rotate
// the intermediate certificate and it will cause a TLS bad certificate
// error.
//
// 2. Subject Key Identifier, if rekey - For rekey, SubjectKeyIdentifier
// extension will be calculated for the new public key by
// x509util.CreateCertificate()
for _, ext := range oldCert.Extensions {
if ext.Id.Equal(oidAuthorityKeyIdentifier) {
continue
@ -256,18 +251,22 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext)
}
serverCert, err := x509util.CreateCertificate(newCert, a.x509Issuer, newCert.PublicKey, a.x509Signer)
resp, err := a.x509CAService.RenewCertificate(&casapi.RenewCertificateRequest{
Template: newCert,
Lifetime: lifetime,
Backdate: backdate,
})
if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...)
}
if err = a.db.StoreCertificate(serverCert); err != nil {
if err = a.db.StoreCertificate(resp.Certificate); err != nil {
if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; error storing certificate in db", opts...)
}
}
return []*x509.Certificate{serverCert, a.x509Issuer}, nil
return append([]*x509.Certificate{resp.Certificate}, resp.CertificateChain...), nil
}
// RevokeOptions are the options for the Revoke API.
@ -403,30 +402,36 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
certTpl.NotBefore = now.Add(-1 * time.Minute)
certTpl.NotAfter = now.Add(24 * time.Hour)
cert, err := x509util.CreateCertificate(certTpl, a.x509Issuer, cr.PublicKey, a.x509Signer)
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: certTpl,
Lifetime: 24 * time.Hour,
Backdate: 1 * time.Minute,
})
if err != nil {
return fatal(err)
}
// Generate PEM blocks to create tls.Certificate
crtPEM := pem.EncodeToMemory(&pem.Block{
pemBlocks := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
Bytes: resp.Certificate.Raw,
})
intermediatePEM, err := pemutil.Serialize(a.x509Issuer)
if err != nil {
return fatal(err)
for _, crt := range resp.CertificateChain {
pemBlocks = append(pemBlocks, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crt.Raw,
})...)
}
keyPEM, err := pemutil.Serialize(priv)
if err != nil {
return fatal(err)
}
tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM))
tlsCrt, err := tls.X509KeyPair(pemBlocks, pem.EncodeToMemory(keyPEM))
if err != nil {
return fatal(err)
}
// Set leaf certificate
tlsCrt.Leaf = cert
tlsCrt.Leaf = resp.Certificate
return &tlsCrt, nil
}

@ -8,6 +8,11 @@ import (
"github.com/pkg/errors"
)
var (
oidStepRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
oidStepCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(oidStepRoot, 2)...)
)
// CertificateAuthorityExtension is type used to encode the certificate
// authority extension.
type CertificateAuthorityExtension struct {

@ -1,7 +1,8 @@
package apiv1
import (
"strings"
"crypto"
"crypto/x509"
"github.com/pkg/errors"
)
@ -14,27 +15,36 @@ type Options struct {
// Path to the credentials file used in CloudCAS
CredentialsFile string `json:"credentialsFile"`
// CertificateAuthority reference. In CloudCAS the format is
// `projects/*/locations/*/certificateAuthorities/*`.
Certificateauthority string `json:"certificateAuthority"`
// Issuer and signer are the issuer certificate and signer used in SoftCAS.
// They are configured in ca.json crt and key properties.
Issuer *x509.Certificate `json:"-"`
Signer crypto.Signer `json:"-"`
}
// Validate checks the fields in Options.
func (o *Options) Validate() error {
var typ Type
if o == nil {
return nil
typ = Type(SoftCAS)
} else {
typ = Type(o.Type)
}
switch Type(strings.ToLower(o.Type)) {
case DefaultCAS, SoftCAS, CloudCAS:
default:
return errors.Errorf("unsupported kms type %s", o.Type)
// Check that the type can be loaded.
if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok {
return errors.Errorf("unsupported cas type %s", typ)
}
return nil
}
// HasType returns if the options have the given type.
func (o *Options) HasType(t Type) bool {
if o == nil {
return SoftCAS == t.String()
return t.String() == SoftCAS
}
return Type(o.Type).String() == t.String()
}

@ -5,7 +5,9 @@ import (
"sync"
)
var registry = new(sync.Map)
var (
registry = new(sync.Map)
)
// CertificateAuthorityServiceNewFunc is the type that represents the method to initialize a new
// CertificateAuthorityService.
@ -13,12 +15,12 @@ type CertificateAuthorityServiceNewFunc func(ctx context.Context, opts Options)
// Register adds to the registry a method to create a KeyManager of type t.
func Register(t Type, fn CertificateAuthorityServiceNewFunc) {
registry.Store(t, fn)
registry.Store(t.String(), fn)
}
// LoadCertificateAuthorityServiceNewFunc returns the function initialize a KayManager.
func LoadCertificateAuthorityServiceNewFunc(t Type) (CertificateAuthorityServiceNewFunc, bool) {
v, ok := registry.Load(t)
v, ok := registry.Load(t.String())
if !ok {
return nil, false
}

@ -1,15 +1,12 @@
package apiv1
import (
"crypto"
"crypto/x509"
"time"
)
type CreateCertificateRequest struct {
Template *x509.Certificate
Issuer *x509.Certificate
Signer crypto.Signer
Lifetime time.Duration
Backdate time.Duration
RequestID string
@ -21,8 +18,6 @@ type CreateCertificateResponse struct {
type RenewCertificateRequest struct {
Template *x509.Certificate
Issuer *x509.Certificate
Signer crypto.Signer
Lifetime time.Duration
Backdate time.Duration
RequestID string

@ -1,15 +1,9 @@
package apiv1
import (
"encoding/asn1"
"strings"
)
var (
oidStepRoot = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64}
oidStepCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(oidStepRoot, 2)...)
)
// CertificateAuthorityService is the interface implemented to support external
// certificate authorities.
type CertificateAuthorityService interface {
@ -18,27 +12,24 @@ type CertificateAuthorityService interface {
RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error)
}
// Type represents the KMS type used.
// Type represents the CAS type used.
type Type string
const (
// DefaultCAS is a CertificateAuthorityService using software.
DefaultCAS = ""
// SoftCAS is a CertificateAuthorityService using software.
SoftCAS = "SoftCAS"
SoftCAS = "softcas"
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS.
CloudCAS = "CloudCAS"
CloudCAS = "cloudcas"
)
// String returns the given type as a string. All the letters will be lowercase.
// String returns a string from the type. It will always return the lower case
// version of the Type, as we need a standard type to compare and use as the
// registry key.
func (t Type) String() string {
if t == "" {
return SoftCAS
}
for _, s := range []string{SoftCAS, CloudCAS} {
if strings.EqualFold(s, string(t)) {
return s
}
}
return string(t)
return strings.ToLower(string(t))
}

@ -45,6 +45,10 @@ type caClient interface{}
// New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS.
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
if opts.Certificateauthority == "" {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty")
}
var cloudOpts []option.ClientOption
if opts.CredentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
@ -57,7 +61,7 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
return &CloudCAS{
client: client,
certificateAuthority: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/Smallstep-Test-Intermediate-CA",
certificateAuthority: opts.Certificateauthority,
}, nil
}
@ -87,9 +91,9 @@ func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv
func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
switch {
case req.Template == nil:
return nil, errors.New("renewCertificate `template` cannot be nil")
return nil, errors.New("renewCertificateRequest `template` cannot be nil")
case req.Lifetime == 0:
return nil, errors.New("renewCertificate `lifetime` cannot be 0")
return nil, errors.New("renewCertificateRequest `lifetime` cannot be 0")
}
cert, chain, err := c.createCertificate(req.Template, req.Lifetime, req.RequestID)
@ -106,7 +110,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.
// RevokeCertificate a certificate using Google Cloud CAS.
func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
if req.Certificate == nil {
return nil, errors.New("revokeCertificate `certificate` cannot be nil")
return nil, errors.New("revokeCertificateRequest `certificate` cannot be nil")
}
ext, ok := apiv1.FindCertificateAuthorityExtension(req.Certificate)

@ -2,8 +2,11 @@ package softcas
import (
"context"
"crypto"
"crypto/x509"
"errors"
"fmt"
"time"
"github.com/smallstep/certificates/cas/apiv1"
"go.step.sm/crypto/x509util"
@ -15,19 +18,47 @@ func init() {
})
}
// SoftCAS implements a Certificate Authority Service using Golang crypto.
// This is the default CAS used in step-ca.
type SoftCAS struct{}
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 {
Issuer *x509.Certificate
Signer crypto.Signer
}
// New creates a new CertificateAuthorityService implementation using Golang
// New creates a new CertificateAuthorityService implementation using Golang or KMS
// crypto.
func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) {
return &SoftCAS{}, nil
switch {
case opts.Issuer == nil:
return nil, errors.New("softCAS 'issuer' cannot be nil")
case opts.Signer == nil:
return nil, errors.New("softCAS 'signer' cannot be nil")
}
return &SoftCAS{
Issuer: opts.Issuer,
Signer: opts.Signer,
}, nil
}
// CreateCertificate signs a new certificate using Golang crypto.
// CreateCertificate signs a new certificate using Golang or KMS crypto.
func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
cert, err := x509util.CreateCertificate(req.Template, req.Issuer, req.Template.PublicKey, req.Signer)
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.Issuer.Subject
cert, err := x509util.CreateCertificate(req.Template, c.Issuer, req.Template.PublicKey, c.Signer)
if err != nil {
return nil, err
}
@ -35,13 +66,36 @@ func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1
return &apiv1.CreateCertificateResponse{
Certificate: cert,
CertificateChain: []*x509.Certificate{
req.Issuer,
c.Issuer,
},
}, nil
}
// RenewCertificate signs the given certificate template using Golang or KMS crypto.
func (c *SoftCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
return nil, fmt.Errorf("not implemented")
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.Issuer.Subject
cert, err := x509util.CreateCertificate(req.Template, c.Issuer, req.Template.PublicKey, c.Signer)
if err != nil {
return nil, err
}
return &apiv1.RenewCertificateResponse{
Certificate: cert,
CertificateChain: []*x509.Certificate{
c.Issuer,
},
}, nil
}
// RevokeCertificate revokes the given certificate in step-ca.

Loading…
Cancel
Save