Convert x509util.WithOptions to new modifiers.

This commit is contained in:
Mariano Cano 2020-07-07 19:00:06 -07:00
parent dcb962bdde
commit 9032018cf2

View file

@ -6,63 +6,54 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/json"
"net" "net"
"net/url" "net/url"
"reflect" "reflect"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/certificates/x509util"
"golang.org/x/crypto/ed25519" "golang.org/x/crypto/ed25519"
) )
// Options contains the options that can be passed to the Sign method. Backdate // Options contains the options that can be passed to the Sign method. Backdate
// is automatically filled and can only be configured in the CA. // is automatically filled and can only be configured in the CA.
type Options struct { type Options struct {
NotAfter TimeDuration `json:"notAfter"` NotAfter TimeDuration `json:"notAfter"`
NotBefore TimeDuration `json:"notBefore"` NotBefore TimeDuration `json:"notBefore"`
Backdate time.Duration `json:"-"` UserData json.RawMessage `json:"data"`
Backdate time.Duration `json:"-"`
} }
// SignOption is the interface used to collect all extra options used in the // SignOption is the interface used to collect all extra options used in the
// Sign method. // Sign method.
type SignOption interface{} type SignOption interface{}
// CertificateValidator is the interface used to validate a X.509 certificate. // CertificateValidator is an interface used to validate a given X.509 certificate.
type CertificateValidator interface { type CertificateValidator interface {
SignOption Valid(cert *x509.Certificate, opts Options) error
Valid(cert *x509.Certificate, o Options) error
} }
// CertificateRequestValidator is the interface used to validate a X.509 // CertificateRequestValidator is an interface used to validate a given X.509 certificate request.
// certificate request.
type CertificateRequestValidator interface { type CertificateRequestValidator interface {
SignOption Valid(cr *x509.CertificateRequest) error
Valid(req *x509.CertificateRequest) error
} }
// ProfileModifier is the interface used to add custom options to the profile // CertificateModifier is an interface used to modify a given X.509 certificate.
// constructor. The options are used to modify the final certificate. // Types implementing this interface will be validated with a
type ProfileModifier interface { // CertificateValidator.
SignOption type CertificateModifier interface {
Option(o Options) x509util.WithOption Modify(cert *x509.Certificate, opts Options) error
} }
// CertificateEnforcer is the interface used to modify a certificate after // CertificateEnforcer is an interface used to modify a given X.509 certificate.
// validation. // Types implemented this interface will NOT be validated with a
// CertificateValidator.
type CertificateEnforcer interface { type CertificateEnforcer interface {
SignOption
Enforce(cert *x509.Certificate) error Enforce(cert *x509.Certificate) error
} }
// profileWithOption is a wrapper against x509util.WithOption to conform the
// interface.
type profileWithOption x509util.WithOption
func (v profileWithOption) Option(Options) x509util.WithOption {
return x509util.WithOption(v)
}
// emailOnlyIdentity is a CertificateRequestValidator that checks that the only // emailOnlyIdentity is a CertificateRequestValidator that checks that the only
// SAN provided is the given email address. // SAN provided is the given email address.
type emailOnlyIdentity string type emailOnlyIdentity string
@ -254,27 +245,26 @@ func (eee ExtraExtsEnforcer) Enforce(cert *x509.Certificate) error {
return nil return nil
} }
// profileDefaultDuration is a wrapper against x509util.WithOption to conform // profileDefaultDuration is a modifier that sets the certificate
// the SignOption interface. // duration.
type profileDefaultDuration time.Duration type profileDefaultDuration time.Duration
func (v profileDefaultDuration) Option(so Options) x509util.WithOption { func (v profileDefaultDuration) Modify(cert *x509.Certificate, so Options) error {
var backdate time.Duration var backdate time.Duration
notBefore := so.NotBefore.Time() notBefore := so.NotBefore.Time()
if notBefore.IsZero() { if notBefore.IsZero() {
notBefore = now() notBefore = now()
backdate = -1 * so.Backdate backdate = -1 * so.Backdate
} }
notAfter := so.NotAfter.RelativeTime(notBefore) notAfter := so.NotAfter.RelativeTime(notBefore)
return func(p x509util.Profile) error { if notAfter.IsZero() {
fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) notAfter = notBefore.Add(time.Duration(v))
if err := fn(p); err != nil {
return err
}
crt := p.Subject()
crt.NotBefore = crt.NotBefore.Add(backdate)
return nil
} }
cert.NotBefore = notBefore.Add(backdate)
cert.NotAfter = notAfter
return nil
} }
// profileLimitDuration is an x509 profile option that modifies an x509 validity // profileLimitDuration is an x509 profile option that modifies an x509 validity
@ -286,40 +276,37 @@ type profileLimitDuration struct {
// Option returns an x509util option that limits the validity period of a // Option returns an x509util option that limits the validity period of a
// certificate to one that is superficially imposed. // certificate to one that is superficially imposed.
func (v profileLimitDuration) Option(so Options) x509util.WithOption { func (v profileLimitDuration) Modify(cert *x509.Certificate, so Options) error {
return func(p x509util.Profile) error { var backdate time.Duration
var backdate time.Duration notBefore := so.NotBefore.Time()
n := now() if notBefore.IsZero() {
notBefore := so.NotBefore.Time() notBefore = now()
if notBefore.IsZero() { backdate = -1 * so.Backdate
notBefore = n
backdate = -1 * so.Backdate
}
if notBefore.Before(v.notBefore) {
return errors.Errorf("requested certificate notBefore (%s) is before "+
"the active validity window of the provisioning credential (%s)",
notBefore, v.notBefore)
}
notAfter := so.NotAfter.RelativeTime(notBefore)
if notAfter.After(v.notAfter) {
return errors.Errorf("requested certificate notAfter (%s) is after "+
"the expiration of the provisioning credential (%s)",
notAfter, v.notAfter)
}
if notAfter.IsZero() {
t := notBefore.Add(v.def)
if t.After(v.notAfter) {
notAfter = v.notAfter
} else {
notAfter = t
}
}
crt := p.Subject()
crt.NotBefore = notBefore.Add(backdate)
crt.NotAfter = notAfter
return nil
} }
if notBefore.Before(v.notBefore) {
return errors.Errorf("requested certificate notBefore (%s) is before "+
"the active validity window of the provisioning credential (%s)",
notBefore, v.notBefore)
}
notAfter := so.NotAfter.RelativeTime(notBefore)
if notAfter.After(v.notAfter) {
return errors.Errorf("requested certificate notAfter (%s) is after "+
"the expiration of the provisioning credential (%s)",
notAfter, v.notAfter)
}
if notAfter.IsZero() {
t := notBefore.Add(v.def)
if t.After(v.notAfter) {
notAfter = v.notAfter
} else {
notAfter = t
}
}
cert.NotBefore = notBefore.Add(backdate)
cert.NotAfter = notAfter
return nil
} }
// validityValidator validates the certificate validity settings. // validityValidator validates the certificate validity settings.
@ -385,22 +372,21 @@ func newForceCNOption(forceCN bool) *forceCNOption {
return &forceCNOption{forceCN} return &forceCNOption{forceCN}
} }
func (o *forceCNOption) Option(Options) x509util.WithOption { func (o *forceCNOption) Modify(cert *x509.Certificate, _ Options) error {
return func(p x509util.Profile) error { if !o.ForceCN {
if !o.ForceCN { // Forcing CN is disabled, do nothing to certificate
// Forcing CN is disabled, do nothing to certificate
return nil
}
crt := p.Subject()
if crt.Subject.CommonName == "" {
if len(crt.DNSNames) > 0 {
crt.Subject.CommonName = crt.DNSNames[0]
} else {
return errors.New("Cannot force CN, DNSNames is empty")
}
}
return nil return nil
} }
if cert.Subject.CommonName == "" {
if len(cert.DNSNames) > 0 {
cert.Subject.CommonName = cert.DNSNames[0]
} else {
return errors.New("Cannot force CN, DNSNames is empty")
}
}
return nil
} }
type provisionerExtensionOption struct { type provisionerExtensionOption struct {
@ -419,23 +405,20 @@ func newProvisionerExtensionOption(typ Type, name, credentialID string, keyValue
} }
} }
func (o *provisionerExtensionOption) Option(Options) x509util.WithOption { func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ Options) error {
return func(p x509util.Profile) error { ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...)
crt := p.Subject() if err != nil {
ext, err := createProvisionerExtension(o.Type, o.Name, o.CredentialID, o.KeyValuePairs...) return err
if err != nil {
return err
}
// Prepend the provisioner extension. In the auth.Sign code we will
// force the resulting certificate to only have one extension, the
// first stepOIDProvisioner that is found in the ExtraExtensions.
// A client could pass a csr containing a malicious stepOIDProvisioner
// ExtraExtension. If we were to append (rather than prepend) the correct
// stepOIDProvisioner extension, then the resulting certificate would
// contain the malicious extension, rather than the one applied by step-ca.
crt.ExtraExtensions = append([]pkix.Extension{ext}, crt.ExtraExtensions...)
return nil
} }
// Prepend the provisioner extension. In the auth.Sign code we will
// force the resulting certificate to only have one extension, the
// first stepOIDProvisioner that is found in the ExtraExtensions.
// A client could pass a csr containing a malicious stepOIDProvisioner
// ExtraExtension. If we were to append (rather than prepend) the correct
// stepOIDProvisioner extension, then the resulting certificate would
// contain the malicious extension, rather than the one applied by step-ca.
cert.ExtraExtensions = append([]pkix.Extension{ext}, cert.ExtraExtensions...)
return nil
} }
func createProvisionerExtension(typ int, name, credentialID string, keyValuePairs ...string) (pkix.Extension, error) { func createProvisionerExtension(typ int, name, credentialID string, keyValuePairs ...string) (pkix.Extension, error) {
@ -454,8 +437,3 @@ func createProvisionerExtension(typ int, name, credentialID string, keyValuePair
Value: b, Value: b,
}, nil }, nil
} }
func init() {
// Avoid dead-code warning in profileWithOption
_ = profileWithOption(nil)
}