diff --git a/api/ssh.go b/api/ssh.go index fc598502..7e6ca0b6 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -308,14 +308,6 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) { // Sign identity certificate if available. var identityCertificate []Certificate if cr := body.IdentityCSR.CertificateRequest; cr != nil { - var opts provisioner.Options - // Use same duration as ssh certificate for user certificates - if cert.CertType == ssh.UserCert { - opts = provisioner.Options{ - NotBefore: provisioner.NewTimeDuration(time.Unix(int64(cert.ValidAfter), 0)), - NotAfter: provisioner.NewTimeDuration(time.Unix(int64(cert.ValidBefore), 0)), - } - } ctx := authority.NewContextWithSkipTokenReuse(r.Context()) ctx = provisioner.NewContextWithMethod(ctx, provisioner.SignMethod) signOpts, err := h.Authority.Authorize(ctx, body.OTT) @@ -323,7 +315,14 @@ func (h *caHandler) SSHSign(w http.ResponseWriter, r *http.Request) { WriteError(w, errs.UnauthorizedErr(err)) return } - certChain, err := h.Authority.Sign(cr, opts, signOpts...) + + // Enforce the same duration as ssh certificate. + signOpts = append(signOpts, &identityModifier{ + NotBefore: time.Unix(int64(cert.ValidAfter), 0), + NotAfter: time.Unix(int64(cert.ValidBefore), 0), + }) + + certChain, err := h.Authority.Sign(cr, provisioner.Options{}, signOpts...) if err != nil { WriteError(w, errs.ForbiddenErr(err)) return @@ -483,3 +482,15 @@ func (h *caHandler) SSHBastion(w http.ResponseWriter, r *http.Request) { Bastion: bastion, }) } + +// identityModifier is a custom modifier used to force a fixed duration. +type identityModifier struct { + NotBefore time.Time + NotAfter time.Time +} + +func (m *identityModifier) Constrain(cert *x509.Certificate) error { + cert.NotBefore = m.NotBefore + cert.NotAfter = m.NotAfter + return nil +} diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 90c2cd40..d9b8df34 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -47,6 +47,13 @@ type ProfileModifier interface { Option(o Options) x509util.WithOption } +// CertificateConstrainModifier is the interface used to modify a certificate +// after validation. +type CertificateConstrainModifier interface { + SignOption + Constrain(cert *x509.Certificate) error +} + // profileWithOption is a wrapper against x509util.WithOption to conform the // interface. type profileWithOption x509util.WithOption diff --git a/authority/tls.go b/authority/tls.go index 4480314c..c25f91b5 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -61,9 +61,10 @@ func withDefaultASN1DN(def *x509util.ASN1DN) x509util.WithOption { // Sign creates a signed certificate from a certificate signing request. func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Options, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { var ( - opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} - mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} - certValidators = []provisioner.CertificateValidator{} + opts = []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} + mods = []x509util.WithOption{withDefaultASN1DN(a.config.AuthorityConfig.Template)} + certValidators = []provisioner.CertificateValidator{} + constrainModifiers = []provisioner.CertificateConstrainModifier{} ) // Set backdate with the configured value @@ -79,6 +80,8 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti } case provisioner.ProfileModifier: mods = append(mods, k.Option(signOpts)) + case provisioner.CertificateConstrainModifier: + constrainModifiers = append(constrainModifiers, k) default: return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) } @@ -93,12 +96,20 @@ func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.Opti return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } + // Certificate validation for _, v := range certValidators { if err := v.Valid(leaf.Subject(), signOpts); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } + // Certificate modifier after validation + for _, m := range constrainModifiers { + if err := m.Constrain(leaf.Subject()); err != nil { + return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) + } + } + crtBytes, err := leaf.CreateCertificate() if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err,