From e67ccd9e3d71a3e82a970358b032ecc10cc343c2 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 2 Jan 2020 17:48:28 -0800 Subject: [PATCH] Add fault tolerance against clock skew accross system on TLS certificates. --- authority/config.go | 21 +++++++++++++++++---- authority/provisioner/sign_options.go | 27 +++++++++++++++++++++------ authority/tls.go | 13 ++++++++++--- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/authority/config.go b/authority/config.go index 8cc742e4..75f55a12 100644 --- a/authority/config.go +++ b/authority/config.go @@ -28,6 +28,7 @@ var ( MaxVersion: 1.2, Renegotiation: false, } + defaultBackdate = time.Minute defaultDisableRenewal = false defaultEnableSSHCA = false globalProvisionerClaims = provisioner.Claims{ @@ -65,10 +66,11 @@ type Config struct { // AuthConfig represents the configuration options for the authority. type AuthConfig struct { - Provisioners provisioner.List `json:"provisioners"` - Template *x509util.ASN1DN `json:"template,omitempty"` - Claims *provisioner.Claims `json:"claims,omitempty"` - DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` + Provisioners provisioner.List `json:"provisioners"` + Template *x509util.ASN1DN `json:"template,omitempty"` + Claims *provisioner.Claims `json:"claims,omitempty"` + DisableIssuedAtCheck bool `json:"disableIssuedAtCheck,omitempty"` + Backdate *provisioner.Duration `json:"backdate,omitempty"` } // Validate validates the authority configuration. @@ -91,6 +93,17 @@ func (c *AuthConfig) Validate(audiences provisioner.Audiences) error { if c.Template == nil { c.Template = &x509util.ASN1DN{} } + + if c.Backdate != nil { + if c.Backdate.Duration < 0 { + return errors.New("authority.backdate cannot be less than 0") + } + } else { + c.Backdate = &provisioner.Duration{ + Duration: defaultBackdate, + } + } + return nil } diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 53921a3c..ddc985e3 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -15,10 +15,12 @@ import ( "golang.org/x/crypto/ed25519" ) -// Options contains the options that can be passed to the Sign method. +// Options contains the options that can be passed to the Sign method. Backdate +// is automatically filled and can only be configured in the CA. type Options struct { - NotAfter TimeDuration `json:"notAfter"` - NotBefore TimeDuration `json:"notBefore"` + NotAfter TimeDuration `json:"notAfter"` + NotBefore TimeDuration `json:"notBefore"` + Backdate time.Duration `json:"-"` } // SignOption is the interface used to collect all extra options used in the @@ -189,12 +191,22 @@ func (v emailAddressesValidator) Valid(req *x509.CertificateRequest) error { type profileDefaultDuration time.Duration func (v profileDefaultDuration) Option(so Options) x509util.WithOption { + var backdate time.Duration notBefore := so.NotBefore.Time() if notBefore.IsZero() { notBefore = time.Now() + backdate = -1 * so.Backdate } notAfter := so.NotAfter.RelativeTime(notBefore) - return x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) + return func(p x509util.Profile) error { + fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(v)) + if err := fn(p); err != nil { + return err + } + crt := p.Subject() + crt.NotBefore = crt.NotBefore.Add(backdate) + return nil + } } // profileLimitDuration is an x509 profile option that modifies an x509 validity @@ -208,10 +220,12 @@ type profileLimitDuration struct { // certificate to one that is superficially imposed. func (v profileLimitDuration) Option(so Options) x509util.WithOption { return func(p x509util.Profile) error { + var backdate time.Duration n := now() notBefore := so.NotBefore.Time() if notBefore.IsZero() { notBefore = n + backdate = -1 * so.Backdate } if notBefore.After(v.notAfter) { return errors.Errorf("provisioning credential expiration (%s) is before "+ @@ -232,7 +246,7 @@ func (v profileLimitDuration) Option(so Options) x509util.WithOption { } } crt := p.Subject() - crt.NotBefore = notBefore + crt.NotBefore = notBefore.Add(backdate) crt.NotAfter = notAfter return nil } @@ -255,9 +269,10 @@ func (v *validityValidator) Valid(crt *x509.Certificate) error { var ( na = crt.NotAfter nb = crt.NotBefore - d = na.Sub(nb) now = time.Now() ) + // Get duration from to not take into account the backdate. + var d = na.Sub(now) if na.Before(now) { return errors.Errorf("NotAfter: %v cannot be in the past", na) diff --git a/authority/tls.go b/authority/tls.go index 0dd4f323..eb7cb86a 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -65,6 +65,10 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti certValidators = []provisioner.CertificateValidator{} issIdentity = a.intermediateIdentity ) + + // Set backdate with the configured value + signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration + for _, op := range extraOpts { switch k := op.(type) { case provisioner.CertificateValidator: @@ -136,14 +140,17 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error // Issuer issIdentity := a.intermediateIdentity - now := time.Now().UTC() + // Durations + backdate := a.config.AuthorityConfig.Backdate.Duration duration := oldCert.NotAfter.Sub(oldCert.NotBefore) + now := time.Now().UTC() + newCert := &x509.Certificate{ PublicKey: oldCert.PublicKey, Issuer: issIdentity.Crt.Subject, Subject: oldCert.Subject, - NotBefore: now, - NotAfter: now.Add(duration), + NotBefore: now.Add(-1 * backdate), + NotAfter: now.Add(duration - backdate), KeyUsage: oldCert.KeyUsage, UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions, ExtKeyUsage: oldCert.ExtKeyUsage,