forked from TrueCloudLab/certificates
162 lines
5 KiB
Go
162 lines
5 KiB
Go
package provisioner
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/cli/crypto/x509util"
|
|
"github.com/smallstep/cli/jose"
|
|
)
|
|
|
|
// jwtPayload extends jwt.Claims with step attributes.
|
|
type jwtPayload struct {
|
|
jose.Claims
|
|
SANs []string `json:"sans,omitempty"`
|
|
}
|
|
|
|
// JWT is the default provisioner, an entity that can sign tokens necessary for
|
|
// signature requests.
|
|
type JWT struct {
|
|
Name string `json:"name,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Key *jose.JSONWebKey `json:"key,omitempty"`
|
|
EncryptedKey string `json:"encryptedKey,omitempty"`
|
|
Claims *Claims `json:"claims,omitempty"`
|
|
}
|
|
|
|
// GetID returns the provisioner unique identifier. The name and credential id
|
|
// should uniquely identify any JWT provisioner.
|
|
func (p *JWT) GetID() string {
|
|
return p.Name + ":" + p.Key.KeyID
|
|
}
|
|
|
|
// GetName returns the name of the provisioner
|
|
func (p *JWT) GetName() string {
|
|
return p.Name
|
|
}
|
|
|
|
// GetType returns the type of provisioner.
|
|
func (p *JWT) GetType() Type {
|
|
return TypeJWK
|
|
}
|
|
|
|
// GetEncryptedKey returns the base provisioner encrypted key if it's defined.
|
|
func (p *JWT) GetEncryptedKey() (string, string, bool) {
|
|
return p.Key.KeyID, p.EncryptedKey, len(p.EncryptedKey) > 0
|
|
}
|
|
|
|
// Init initializes and validates a the fields of Provisioner type.
|
|
func (p *JWT) Init(global *Claims) (err error) {
|
|
switch {
|
|
case p.Name == "":
|
|
return errors.New("provisioner name cannot be empty")
|
|
|
|
case p.Type == "":
|
|
return errors.New("provisioner type cannot be empty")
|
|
|
|
case p.Key == nil:
|
|
return errors.New("provisioner key cannot be empty")
|
|
}
|
|
p.Claims, err = p.Claims.Init(global)
|
|
return err
|
|
}
|
|
|
|
func (p *JWT) Authorize(token string) ([]SignOption, error) {
|
|
jwt, err := jose.ParseSigned(token)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error parsing token")
|
|
}
|
|
|
|
var claims jwtPayload
|
|
if err = jwt.Claims(p.Key, &claims); err != nil {
|
|
return nil, errors.Wrap(err, "error parsing claims")
|
|
}
|
|
|
|
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
|
// more than a few minutes.
|
|
if err = claims.ValidateWithLeeway(jose.Expected{
|
|
Issuer: p.Name,
|
|
}, time.Minute); err != nil {
|
|
return nil, errors.Wrapf(err, "invalid token")
|
|
}
|
|
|
|
// Do not accept tokens issued before the start of the ca.
|
|
// This check is meant as a stopgap solution to the current lack of a persistence layer.
|
|
// if a.config.AuthorityConfig != nil && !a.config.AuthorityConfig.DisableIssuedAtCheck {
|
|
// if claims.IssuedAt > 0 && claims.IssuedAt.Time().Before(a.startTime) {
|
|
// return nil, &apiError{errors.New("token issued before the bootstrap of certificate authority"),
|
|
// http.StatusUnauthorized, errContext}
|
|
// }
|
|
// }
|
|
|
|
// if !matchesAudience(claims.Audience, a.audiences) {
|
|
// return nil, &apiError{errors.New("authorize: token audience invalid"), http.StatusUnauthorized,
|
|
// errContext}
|
|
// }
|
|
|
|
if claims.Subject == "" {
|
|
return nil, errors.New("token subject cannot be empty")
|
|
}
|
|
|
|
// NOTE: This is for backwards compatibility with older versions of cli
|
|
// and certificates. Older versions added the token subject as the only SAN
|
|
// in a CSR by default.
|
|
if len(claims.SANs) == 0 {
|
|
claims.SANs = []string{claims.Subject}
|
|
}
|
|
|
|
dnsNames, ips := x509util.SplitSANs(claims.SANs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signOps := []SignOption{
|
|
commonNameValidator(claims.Subject),
|
|
dnsNamesValidator(dnsNames),
|
|
ipAddressesValidator(ips),
|
|
// profileWithOption(x509util.WithNotBeforeAfterDuration(so.NotBefore, so.NotAfter, p.Claims.DefaultTLSCertDuration())),
|
|
newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID),
|
|
newValidityValidator(p.Claims.MinTLSCertDuration(), p.Claims.MaxTLSCertDuration()),
|
|
}
|
|
|
|
// Store the token to protect against reuse.
|
|
// if _, ok := a.ottMap.LoadOrStore(claims.ID, &idUsed{
|
|
// UsedAt: time.Now().Unix(),
|
|
// Subject: claims.Subject,
|
|
// }); ok {
|
|
// return nil, &apiError{errors.Errorf("token already used"), http.StatusUnauthorized,
|
|
// errContext}
|
|
// }
|
|
|
|
return signOps, nil
|
|
}
|
|
|
|
// AuthorizeRenewal returns an error if the renewal is disabled.
|
|
func (p *JWT) AuthorizeRenewal(cert *x509.Certificate) error {
|
|
if p.Claims.IsDisableRenewal() {
|
|
return errors.Errorf("renew is disabled for provisioner %s", p.GetID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AuthorizeRevoke returns an error if the provisioner does not have rights to
|
|
// revoke the certificate with serial number in the `sub` property.
|
|
func (p *JWT) AuthorizeRevoke(token string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
// // getTLSApps returns a list of modifiers and validators that will be applied to
|
|
// // the certificate.
|
|
// func (p *JWT) getTLSApps(so SignOptions) ([]x509util.WithOption, []certClaim, error) {
|
|
// c := p.Claims
|
|
// return []x509util.WithOption{
|
|
// x509util.WithNotBeforeAfterDuration(so.NotBefore, so.NotAfter, c.DefaultTLSCertDuration()),
|
|
// withProvisionerOID(p.Name, p.Key.KeyID),
|
|
// }, []certClaim{
|
|
// &certTemporalClaim{
|
|
// min: c.MinTLSCertDuration(),
|
|
// max: c.MaxTLSCertDuration(),
|
|
// },
|
|
// }, nil
|
|
// }
|