forked from TrueCloudLab/certificates
Add initial implementation of StepCAS.
StepCAS allows to configure step-ca as an RA using another step-ca as the main CA.
This commit is contained in:
parent
3b9eed003d
commit
a6115e29c2
9 changed files with 428 additions and 17 deletions
|
@ -189,7 +189,6 @@ func (c *Config) Validate() error {
|
||||||
|
|
||||||
// Options holds the RA/CAS configuration.
|
// Options holds the RA/CAS configuration.
|
||||||
ra := c.AuthorityConfig.Options
|
ra := c.AuthorityConfig.Options
|
||||||
|
|
||||||
// The default RA/CAS requires root, crt and key.
|
// The default RA/CAS requires root, crt and key.
|
||||||
if ra.Is(cas.SoftCAS) {
|
if ra.Is(cas.SoftCAS) {
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -148,6 +148,7 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Sign
|
||||||
lifetime := leaf.NotAfter.Sub(leaf.NotBefore.Add(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,
|
||||||
|
CSR: csr,
|
||||||
Lifetime: lifetime,
|
Lifetime: lifetime,
|
||||||
Backdate: signOpts.Backdate,
|
Backdate: signOpts.Backdate,
|
||||||
})
|
})
|
||||||
|
@ -367,9 +368,10 @@ func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error
|
||||||
// CAS operation, note that SoftCAS (default) is a noop.
|
// CAS operation, note that SoftCAS (default) is a noop.
|
||||||
// The revoke happens when this is stored in the db.
|
// The revoke happens when this is stored in the db.
|
||||||
_, err = a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{
|
_, err = a.x509CAService.RevokeCertificate(&casapi.RevokeCertificateRequest{
|
||||||
Certificate: revokedCert,
|
Certificate: revokedCert,
|
||||||
Reason: rci.Reason,
|
SerialNumber: rci.Serial,
|
||||||
ReasonCode: rci.ReasonCode,
|
Reason: rci.Reason,
|
||||||
|
ReasonCode: rci.ReasonCode,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
|
return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...)
|
||||||
|
@ -427,6 +429,7 @@ func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) {
|
||||||
|
|
||||||
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
|
resp, err := a.x509CAService.CreateCertificate(&casapi.CreateCertificateRequest{
|
||||||
Template: certTpl,
|
Template: certTpl,
|
||||||
|
CSR: cr,
|
||||||
Lifetime: 24 * time.Hour,
|
Lifetime: 24 * time.Hour,
|
||||||
Backdate: 1 * time.Minute,
|
Backdate: 1 * time.Minute,
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,17 +14,29 @@ type Options struct {
|
||||||
// The type of the CAS to use.
|
// The type of the CAS to use.
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
||||||
// Path to the credentials file used in CloudCAS
|
// CertificateAuthority reference:
|
||||||
CredentialsFile string `json:"credentialsFile"`
|
// In StepCAS the values is the CA url, e.g. "https://ca.smallstep.com:9000".
|
||||||
|
// In CloudCAS the format is "projects/*/locations/*/certificateAuthorities/*".
|
||||||
|
CertificateAuthority string `json:"certificateAuthority,omitempty"`
|
||||||
|
|
||||||
// CertificateAuthority reference. In CloudCAS the format is
|
// CertificateAuthorityFingerprint is the root fingerprint used to
|
||||||
// `projects/*/locations/*/certificateAuthorities/*`.
|
// authenticate the connection to the CA when using StepCAS.
|
||||||
CertificateAuthority string `json:"certificateAuthority"`
|
CertificateAuthorityFingerprint string `json:"certificateAuthorityFingerprint,omitempty"`
|
||||||
|
|
||||||
// Certificate and signer are the issuer certificate,along with any other bundled certificates to be returned in the chain for consumers, and signer used in SoftCAS.
|
// CertificateIssuer contains the configuration used in StepCAS.
|
||||||
// They are configured in ca.json crt and key properties.
|
CertificateIssuer *CertificateIssuer `json:"certificateIssuer,omitempty"`
|
||||||
CertificateChain []*x509.Certificate
|
|
||||||
Signer crypto.Signer `json:"-"`
|
// Path to the credentials file used in CloudCAS. If not defined the default
|
||||||
|
// authentication mechanism provided by Google SDK will be used. See
|
||||||
|
// https://cloud.google.com/docs/authentication.
|
||||||
|
CredentialsFile string `json:"credentialsFile,omitempty"`
|
||||||
|
|
||||||
|
// Certificate and signer are the issuer certificate, along with any other
|
||||||
|
// bundled certificates to be returned in the chain for consumers, and
|
||||||
|
// signer used in SoftCAS. They are configured in ca.json crt and key
|
||||||
|
// properties.
|
||||||
|
CertificateChain []*x509.Certificate `json:"-"`
|
||||||
|
Signer crypto.Signer `json:"-"`
|
||||||
|
|
||||||
// IsCreator is set to true when we're creating a certificate authority. Is
|
// IsCreator is set to true when we're creating a certificate authority. Is
|
||||||
// used to skip some validations when initializing a CertificateAuthority.
|
// used to skip some validations when initializing a CertificateAuthority.
|
||||||
|
@ -39,6 +51,15 @@ type Options struct {
|
||||||
Location string `json:"-"`
|
Location string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CertificateIssuer contains the properties used to use the StepCAS certificate
|
||||||
|
// authority service.
|
||||||
|
type CertificateIssuer struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Provisioner string `json:"provisioner,omitempty"`
|
||||||
|
Certificate string `json:"crt,omitempty"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
var typ Type
|
||||||
|
|
|
@ -53,6 +53,7 @@ const (
|
||||||
// CreateCertificateRequest is the request used to sign a new certificate.
|
// CreateCertificateRequest is the request used to sign a new certificate.
|
||||||
type CreateCertificateRequest struct {
|
type CreateCertificateRequest struct {
|
||||||
Template *x509.Certificate
|
Template *x509.Certificate
|
||||||
|
CSR *x509.CertificateRequest
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
Backdate time.Duration
|
Backdate time.Duration
|
||||||
RequestID string
|
RequestID string
|
||||||
|
@ -67,6 +68,7 @@ type CreateCertificateResponse struct {
|
||||||
// RenewCertificateRequest is the request used to re-sign a certificate.
|
// RenewCertificateRequest is the request used to re-sign a certificate.
|
||||||
type RenewCertificateRequest struct {
|
type RenewCertificateRequest struct {
|
||||||
Template *x509.Certificate
|
Template *x509.Certificate
|
||||||
|
CSR *x509.CertificateRequest
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
Backdate time.Duration
|
Backdate time.Duration
|
||||||
RequestID string
|
RequestID string
|
||||||
|
@ -80,10 +82,11 @@ type RenewCertificateResponse struct {
|
||||||
|
|
||||||
// RevokeCertificateRequest is the request used to revoke a certificate.
|
// RevokeCertificateRequest is the request used to revoke a certificate.
|
||||||
type RevokeCertificateRequest struct {
|
type RevokeCertificateRequest struct {
|
||||||
Certificate *x509.Certificate
|
Certificate *x509.Certificate
|
||||||
Reason string
|
SerialNumber string
|
||||||
ReasonCode int
|
Reason string
|
||||||
RequestID string
|
ReasonCode int
|
||||||
|
RequestID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeCertificateResponse is the response to a revoke certificate request.
|
// RevokeCertificateResponse is the response to a revoke certificate request.
|
||||||
|
|
|
@ -35,6 +35,8 @@ const (
|
||||||
SoftCAS = "softcas"
|
SoftCAS = "softcas"
|
||||||
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS.
|
// CloudCAS is a CertificateAuthorityService using Google Cloud CAS.
|
||||||
CloudCAS = "cloudcas"
|
CloudCAS = "cloudcas"
|
||||||
|
// StepCAS is a CertificateAuthorityService using another step-ca instance.
|
||||||
|
StepCAS = "stepcas"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a string from the type. It will always return the lower case
|
// String returns a string from the type. It will always return the lower case
|
||||||
|
|
40
cas/stepcas/issuer.go
Normal file
40
cas/stepcas/issuer.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package stepcas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateCertificateIssuer validates the configuration of the certificate
|
||||||
|
// issuer.
|
||||||
|
func validateCertificateIssuer(iss *apiv1.CertificateIssuer) error {
|
||||||
|
switch {
|
||||||
|
case iss == nil:
|
||||||
|
return errors.New("stepCAS 'certificateIssuer' cannot be nil")
|
||||||
|
case iss.Type == "":
|
||||||
|
return errors.New("stepCAS `certificateIssuer.type` cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch strings.ToLower(iss.Type) {
|
||||||
|
case "x5c":
|
||||||
|
return validateX5CIssuer(iss)
|
||||||
|
default:
|
||||||
|
return errors.Errorf("stepCAS `certificateIssuer.type` %s is not supported", iss.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateX5CIssuer validates the configuration of x5c issuer.
|
||||||
|
func validateX5CIssuer(iss *apiv1.CertificateIssuer) error {
|
||||||
|
switch {
|
||||||
|
case iss.Certificate == "":
|
||||||
|
return errors.New("stepCAS `certificateIssuer.crt` cannot be empty")
|
||||||
|
case iss.Key == "":
|
||||||
|
return errors.New("stepCAS `certificateIssuer.key` cannot be empty")
|
||||||
|
case iss.Provisioner == "":
|
||||||
|
return errors.New("stepCAS `certificateIssuer.provisioner` cannot be empty")
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
189
cas/stepcas/stepcas.go
Normal file
189
cas/stepcas/stepcas.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package stepcas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/api"
|
||||||
|
"github.com/smallstep/certificates/ca"
|
||||||
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
apiv1.Register(apiv1.StepCAS, func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) {
|
||||||
|
return New(ctx, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepCAS implements the cas.CertificateAuthorityService interface using
|
||||||
|
// another step-ca instance.
|
||||||
|
type StepCAS struct {
|
||||||
|
x5c *x5cIssuer
|
||||||
|
client *ca.Client
|
||||||
|
fingerprint string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new CertificateAuthorityService implementation using another
|
||||||
|
// step-ca instance.
|
||||||
|
func New(ctx context.Context, opts apiv1.Options) (*StepCAS, error) {
|
||||||
|
switch {
|
||||||
|
case opts.CertificateAuthority == "":
|
||||||
|
return nil, errors.New("stepCAS 'certificateAuthority' cannot be empty")
|
||||||
|
case opts.CertificateAuthorityFingerprint == "":
|
||||||
|
return nil, errors.New("stepCAS 'certificateAuthorityFingerprint' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
caURL, err := url.Parse(opts.CertificateAuthority)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "stepCAS `certificateAuthority` is not valid")
|
||||||
|
}
|
||||||
|
if err := validateCertificateIssuer(opts.CertificateIssuer); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create client.
|
||||||
|
client, err := ca.NewClient(opts.CertificateAuthority, ca.WithRootSHA256(opts.CertificateAuthorityFingerprint))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// X5C is the only one supported at the moment.
|
||||||
|
x5c, err := newX5CIssuer(caURL, opts.CertificateIssuer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StepCAS{
|
||||||
|
x5c: x5c,
|
||||||
|
client: client,
|
||||||
|
fingerprint: opts.CertificateAuthorityFingerprint,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
|
||||||
|
switch {
|
||||||
|
case req.CSR == nil:
|
||||||
|
return nil, errors.New("createCertificateRequest `template` cannot be nil")
|
||||||
|
case req.Lifetime == 0:
|
||||||
|
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := s.signToken(req.CSR.Subject.CommonName, req.CSR.DNSNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Sign(&api.SignRequest{
|
||||||
|
CsrPEM: api.CertificateRequest{CertificateRequest: req.CSR},
|
||||||
|
OTT: token,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain []*x509.Certificate
|
||||||
|
cert := resp.CertChainPEM[0].Certificate
|
||||||
|
for _, c := range resp.CertChainPEM[1:] {
|
||||||
|
chain = append(chain, c.Certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.CreateCertificateResponse{
|
||||||
|
Certificate: cert,
|
||||||
|
CertificateChain: chain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) {
|
||||||
|
switch {
|
||||||
|
case req.CSR == nil:
|
||||||
|
return nil, errors.New("createCertificateRequest `template` cannot be nil")
|
||||||
|
case req.Lifetime == 0:
|
||||||
|
return nil, errors.New("createCertificateRequest `lifetime` cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := s.signToken(req.CSR.Subject.CommonName, req.CSR.DNSNames)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Sign(&api.SignRequest{
|
||||||
|
CsrPEM: api.CertificateRequest{CertificateRequest: req.CSR},
|
||||||
|
OTT: token,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var chain []*x509.Certificate
|
||||||
|
cert := resp.CertChainPEM[0].Certificate
|
||||||
|
for _, c := range resp.CertChainPEM[1:] {
|
||||||
|
chain = append(chain, c.Certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.RenewCertificateResponse{
|
||||||
|
Certificate: cert,
|
||||||
|
CertificateChain: chain,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
|
||||||
|
switch {
|
||||||
|
case req.SerialNumber == "" && req.Certificate == nil:
|
||||||
|
return nil, errors.New("revokeCertificateRequest `serialNumber` or `certificate` are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
serialNumber := req.SerialNumber
|
||||||
|
if req.Certificate != nil {
|
||||||
|
serialNumber = req.Certificate.SerialNumber.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := s.revokeToken(serialNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.client.Revoke(&api.RevokeRequest{
|
||||||
|
Serial: serialNumber,
|
||||||
|
ReasonCode: req.ReasonCode,
|
||||||
|
Reason: req.Reason,
|
||||||
|
OTT: token,
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiv1.RevokeCertificateResponse{
|
||||||
|
Certificate: req.Certificate,
|
||||||
|
CertificateChain: nil,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificateAuthority returns the root certificate of the certificate
|
||||||
|
// authority using the configured fingerprint.
|
||||||
|
func (s *StepCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
|
||||||
|
resp, err := s.client.Root(s.fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &apiv1.GetCertificateAuthorityResponse{
|
||||||
|
RootCertificate: resp.RootPEM.Certificate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepCAS) signToken(subject string, sans []string) (string, error) {
|
||||||
|
if s.x5c != nil {
|
||||||
|
return s.x5c.SignToken(subject, sans)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("stepCAS does not have any provisioner configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepCAS) revokeToken(subject string) (string, error) {
|
||||||
|
if s.x5c != nil {
|
||||||
|
return s.x5c.RevokeToken(subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("stepCAS does not have any provisioner configured")
|
||||||
|
}
|
153
cas/stepcas/x5c_issuer.go
Normal file
153
cas/stepcas/x5c_issuer.go
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
package stepcas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/cas/apiv1"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
|
"go.step.sm/crypto/pemutil"
|
||||||
|
"go.step.sm/crypto/randutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultValidity = 5 * time.Minute
|
||||||
|
|
||||||
|
type x5cIssuer struct {
|
||||||
|
caURL *url.URL
|
||||||
|
certFile string
|
||||||
|
keyFile string
|
||||||
|
issuer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newX5CIssuer create a new x5c token issuer. The given configuration should be
|
||||||
|
// already validate.
|
||||||
|
func newX5CIssuer(caURL *url.URL, cfg *apiv1.CertificateIssuer) (*x5cIssuer, error) {
|
||||||
|
_, err := newX5CSigner(cfg.Certificate, cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &x5cIssuer{
|
||||||
|
caURL: caURL,
|
||||||
|
certFile: cfg.Certificate,
|
||||||
|
keyFile: cfg.Key,
|
||||||
|
issuer: cfg.Provisioner,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *x5cIssuer) SignToken(subject string, sans []string) (string, error) {
|
||||||
|
aud := i.caURL.ResolveReference(&url.URL{
|
||||||
|
Path: "/1.0/sign",
|
||||||
|
Fragment: "x5c/" + i.issuer,
|
||||||
|
}).String()
|
||||||
|
|
||||||
|
return i.createToken(aud, subject, sans)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *x5cIssuer) RevokeToken(subject string) (string, error) {
|
||||||
|
aud := i.caURL.ResolveReference(&url.URL{
|
||||||
|
Path: "/1.0/revoke",
|
||||||
|
Fragment: "x5c/" + i.issuer,
|
||||||
|
}).String()
|
||||||
|
|
||||||
|
return i.createToken(aud, subject, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *x5cIssuer) createToken(aud, sub string, sans []string) (string, error) {
|
||||||
|
signer, err := newX5CSigner(i.certFile, i.keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := randutil.Hex(64) // 256 bits
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := defaultClaims(i.issuer, sub, aud, id)
|
||||||
|
builder := jose.Signed(signer).Claims(claims)
|
||||||
|
if len(sans) > 0 {
|
||||||
|
builder = builder.Claims(map[string]interface{}{
|
||||||
|
"sans": sans,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tok, err := builder.CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "error signing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultClaims(iss, sub, aud, id string) jose.Claims {
|
||||||
|
now := time.Now()
|
||||||
|
return jose.Claims{
|
||||||
|
ID: id,
|
||||||
|
Issuer: iss,
|
||||||
|
Subject: sub,
|
||||||
|
Audience: jose.Audience{aud},
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(defaultValidity)),
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
IssuedAt: jose.NewNumericDate(now),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newX5CSigner(certFile, keyFile string) (jose.Signer, error) {
|
||||||
|
key, err := pemutil.Read(keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signer, ok := key.(crypto.Signer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("key is not a crypto.Signer")
|
||||||
|
}
|
||||||
|
kid, err := jose.Thumbprint(&jose.JSONWebKey{Key: signer.Public()})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs, err := jose.ValidateX5C(certFile, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error validating x5c certificate chain and key")
|
||||||
|
}
|
||||||
|
|
||||||
|
so := new(jose.SignerOptions)
|
||||||
|
so.WithType("JWT")
|
||||||
|
so.WithHeader("kid", kid)
|
||||||
|
so.WithHeader("x5c", certs)
|
||||||
|
return newJoseSigner(signer, so)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJoseSigner(key crypto.Signer, so *jose.SignerOptions) (jose.Signer, error) {
|
||||||
|
var alg jose.SignatureAlgorithm
|
||||||
|
switch k := key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
switch k.Curve.Params().Name {
|
||||||
|
case "P-256":
|
||||||
|
alg = jose.ES256
|
||||||
|
case "P-384":
|
||||||
|
alg = jose.ES384
|
||||||
|
case "P-521":
|
||||||
|
alg = jose.ES512
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("unsupported elliptic curve %s", k.Curve.Params().Name)
|
||||||
|
}
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
alg = jose.EdDSA
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
alg = jose.DefaultRSASigAlgorithm
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("unsupported key type %T", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: key}, so)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating jose.Signer")
|
||||||
|
}
|
||||||
|
return signer, nil
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ import (
|
||||||
// Enabled cas interfaces.
|
// Enabled cas interfaces.
|
||||||
_ "github.com/smallstep/certificates/cas/cloudcas"
|
_ "github.com/smallstep/certificates/cas/cloudcas"
|
||||||
_ "github.com/smallstep/certificates/cas/softcas"
|
_ "github.com/smallstep/certificates/cas/softcas"
|
||||||
|
_ "github.com/smallstep/certificates/cas/stepcas"
|
||||||
)
|
)
|
||||||
|
|
||||||
// commit and buildTime are filled in during build by the Makefile
|
// commit and buildTime are filled in during build by the Makefile
|
||||||
|
|
Loading…
Add table
Reference in a new issue