Pass issuer and signer to softCAS options.

Remove commented code and initialize CAS properly.
Minor fixes in CloudCAS.
This commit is contained in:
Mariano Cano 2020-09-10 19:09:46 -07:00
parent c8d9cb0a1d
commit aad8f9e582
10 changed files with 191 additions and 101 deletions

View file

@ -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. // 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.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil { if a.db == nil {
@ -206,6 +194,10 @@ func (a *Authority) init() error {
if err != nil { if err != nil {
return err return err
} }
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{ signer, err := a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey, SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password), Password: []byte(a.config.Password),
@ -214,7 +206,39 @@ func (a *Authority) init() error {
return err return err
} }
a.x509Signer = signer a.x509Signer = signer
a.x509Issuer = crt }
}
// 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 nil
}
} }
// Decrypt and load SSH keys // Decrypt and load SSH keys

View file

@ -187,7 +187,7 @@ func (c *Config) Validate() error {
case c.IntermediateCert == "": case c.IntermediateCert == "":
return errors.New("crt cannot be empty") 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") return errors.New("key cannot be empty")
case len(c.DNSNames) == 0: case len(c.DNSNames) == 0:

View file

@ -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{ resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
Template: leaf, Template: leaf,
Issuer: a.x509Issuer,
Signer: a.x509Signer,
Lifetime: lifetime, Lifetime: lifetime,
Backdate: signOpts.Backdate,
}) })
if err != nil { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) 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 = a.db.StoreCertificate(resp.Certificate); err != nil {
// 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 != db.ErrNotImplemented { if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, return nil, errs.Wrap(http.StatusInternalServerError, err,
"authority.Sign; error storing certificate in db", opts...) "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 // 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 // Durations
backdate := a.config.AuthorityConfig.Backdate.Duration backdate := a.config.AuthorityConfig.Backdate.Duration
duration := oldCert.NotAfter.Sub(oldCert.NotBefore) 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{ newCert := &x509.Certificate{
Issuer: a.x509Issuer.Subject,
Subject: oldCert.Subject, Subject: oldCert.Subject,
NotBefore: now.Add(-1 * backdate),
NotAfter: now.Add(duration - backdate),
KeyUsage: oldCert.KeyUsage, KeyUsage: oldCert.KeyUsage,
UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions, UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions,
ExtKeyUsage: oldCert.ExtKeyUsage, ExtKeyUsage: oldCert.ExtKeyUsage,
@ -241,10 +232,14 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
} }
// Copy all extensions except: // 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. // 1. Authority Key Identifier - This one might be different if we rotate
// 2. Subject Key Identifier, if rekey - For rekey, SubjectKeyIdentifier extension will be calculated // the intermediate certificate and it will cause a TLS bad certificate
// for the new public key by NewLeafProfilewithTemplate() // 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 { for _, ext := range oldCert.Extensions {
if ext.Id.Equal(oidAuthorityKeyIdentifier) { if ext.Id.Equal(oidAuthorityKeyIdentifier) {
continue continue
@ -256,18 +251,22 @@ func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x5
newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext) 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 { if err != nil {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) 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 { if err != db.ErrNotImplemented {
return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; error storing certificate in db", opts...) 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. // 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.NotBefore = now.Add(-1 * time.Minute)
certTpl.NotAfter = now.Add(24 * time.Hour) 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 { if err != nil {
return fatal(err) return fatal(err)
} }
// Generate PEM blocks to create tls.Certificate // Generate PEM blocks to create tls.Certificate
crtPEM := pem.EncodeToMemory(&pem.Block{ pemBlocks := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE", Type: "CERTIFICATE",
Bytes: cert.Raw, Bytes: resp.Certificate.Raw,
}) })
intermediatePEM, err := pemutil.Serialize(a.x509Issuer) for _, crt := range resp.CertificateChain {
if err != nil { pemBlocks = append(pemBlocks, pem.EncodeToMemory(&pem.Block{
return fatal(err) Type: "CERTIFICATE",
Bytes: crt.Raw,
})...)
} }
keyPEM, err := pemutil.Serialize(priv) keyPEM, err := pemutil.Serialize(priv)
if err != nil { if err != nil {
return fatal(err) 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 { if err != nil {
return fatal(err) return fatal(err)
} }
// Set leaf certificate // Set leaf certificate
tlsCrt.Leaf = cert tlsCrt.Leaf = resp.Certificate
return &tlsCrt, nil return &tlsCrt, nil
} }

View file

@ -8,6 +8,11 @@ import (
"github.com/pkg/errors" "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 // CertificateAuthorityExtension is type used to encode the certificate
// authority extension. // authority extension.
type CertificateAuthorityExtension struct { type CertificateAuthorityExtension struct {

View file

@ -1,7 +1,8 @@
package apiv1 package apiv1
import ( import (
"strings" "crypto"
"crypto/x509"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -14,27 +15,36 @@ type Options struct {
// Path to the credentials file used in CloudCAS // Path to the credentials file used in CloudCAS
CredentialsFile string `json:"credentialsFile"` 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. // Validate checks the fields in Options.
func (o *Options) Validate() error { func (o *Options) Validate() error {
var typ Type
if o == nil { if o == nil {
return nil typ = Type(SoftCAS)
} else {
typ = Type(o.Type)
} }
// Check that the type can be loaded.
switch Type(strings.ToLower(o.Type)) { if _, ok := LoadCertificateAuthorityServiceNewFunc(typ); !ok {
case DefaultCAS, SoftCAS, CloudCAS: return errors.Errorf("unsupported cas type %s", typ)
default:
return errors.Errorf("unsupported kms type %s", o.Type)
} }
return nil return nil
} }
// HasType returns if the options have the given type. // HasType returns if the options have the given type.
func (o *Options) HasType(t Type) bool { func (o *Options) HasType(t Type) bool {
if o == nil { if o == nil {
return SoftCAS == t.String() return t.String() == SoftCAS
} }
return Type(o.Type).String() == t.String() return Type(o.Type).String() == t.String()
} }

View file

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

View file

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

View file

@ -1,15 +1,9 @@
package apiv1 package apiv1
import ( import (
"encoding/asn1"
"strings" "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 // CertificateAuthorityService is the interface implemented to support external
// certificate authorities. // certificate authorities.
type CertificateAuthorityService interface { type CertificateAuthorityService interface {
@ -18,27 +12,24 @@ type CertificateAuthorityService interface {
RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error) RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error)
} }
// Type represents the KMS type used. // Type represents the CAS type used.
type Type string type Type string
const ( const (
// DefaultCAS is a CertificateAuthorityService using software. // DefaultCAS is a CertificateAuthorityService using software.
DefaultCAS = "" DefaultCAS = ""
// SoftCAS is a CertificateAuthorityService using software. // SoftCAS is a CertificateAuthorityService using software.
SoftCAS = "SoftCAS" SoftCAS = "softcas"
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS. // 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 { func (t Type) String() string {
if t == "" { if t == "" {
return SoftCAS return SoftCAS
} }
for _, s := range []string{SoftCAS, CloudCAS} { return strings.ToLower(string(t))
if strings.EqualFold(s, string(t)) {
return s
}
}
return string(t)
} }

View file

@ -45,6 +45,10 @@ type caClient interface{}
// New creates a new CertificateAuthorityService implementation using Google // New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS. // Cloud CAS.
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { 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 var cloudOpts []option.ClientOption
if opts.CredentialsFile != "" { if opts.CredentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(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{ return &CloudCAS{
client: client, client: client,
certificateAuthority: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/Smallstep-Test-Intermediate-CA", certificateAuthority: opts.Certificateauthority,
}, nil }, nil
} }
@ -87,9 +91,9 @@ func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv
func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
switch { switch {
case req.Template == nil: 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: 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) 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. // RevokeCertificate a certificate using Google Cloud CAS.
func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
if req.Certificate == nil { 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) ext, ok := apiv1.FindCertificateAuthorityExtension(req.Certificate)

View file

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