diff --git a/api/api.go b/api/api.go index 37222be8..1ce1e85d 100644 --- a/api/api.go +++ b/api/api.go @@ -35,6 +35,7 @@ type Authority interface { Root(shasum string) (*x509.Certificate, error) Sign(cr *x509.CertificateRequest, opts provisioner.Options, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) Renew(peer *x509.Certificate) ([]*x509.Certificate, error) + Rekey(peer *x509.Certificate, csr *x509.CertificateRequest) ([]*x509.Certificate, error) LoadProvisionerByCertificate(*x509.Certificate) (provisioner.Interface, error) LoadProvisionerByID(string) (provisioner.Interface, error) GetProvisioners(cursor string, limit int) (provisioner.List, string, error) @@ -249,6 +250,7 @@ func (h *caHandler) Route(r Router) { r.MethodFunc("GET", "/root/{sha}", h.Root) r.MethodFunc("POST", "/sign", h.Sign) r.MethodFunc("POST", "/renew", h.Renew) + r.MethodFunc("POST", "/rekey", h.Rekey) r.MethodFunc("POST", "/revoke", h.Revoke) r.MethodFunc("GET", "/provisioners", h.Provisioners) r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey) diff --git a/api/rekey.go b/api/rekey.go new file mode 100644 index 00000000..c53b9753 --- /dev/null +++ b/api/rekey.go @@ -0,0 +1,65 @@ +package api + +import ( + "net/http" + + "github.com/smallstep/certificates/errs" +) + +// RekeyRequest is the request body for a certificate rekey request. +type RekeyRequest struct { + CsrPEM CertificateRequest `json:"csr"` +} + +// Validate checks the fields of the RekeyRequest and returns nil if they are ok +// or an error if something is wrong. +func (s *RekeyRequest) Validate() error { + if s.CsrPEM.CertificateRequest == nil { + return errs.BadRequest("missing csr") + } + if err := s.CsrPEM.CertificateRequest.CheckSignature(); err != nil { + return errs.Wrap(http.StatusBadRequest, err, "invalid csr") + } + + return nil +} + + +// Rekey is similar to renew except that the certificate will be renewed with new key from csr. +func (h *caHandler) Rekey(w http.ResponseWriter, r *http.Request) { + + var body RekeyRequest + if err := ReadJSON(r.Body, &body); err != nil { + WriteError(w, errs.Wrap(http.StatusBadRequest, err, "error reading request body")) + return + } + + if err := body.Validate(); err != nil { + WriteError(w, err) + return + } + + if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { + WriteError(w, errs.BadRequest("missing peer certificate")) + return + } + + certChain, err := h.Authority.Rekey(r.TLS.PeerCertificates[0],body.CsrPEM.CertificateRequest) + if err != nil { + WriteError(w, errs.Wrap(http.StatusInternalServerError, err, "cahandler.Rekey")) + return + } + certChainPEM := certChainToPEM(certChain) + var caPEM Certificate + if len(certChainPEM) > 1 { + caPEM = certChainPEM[1] + } + + logCertificate(w, certChain[0]) + JSONStatus(w, &SignResponse{ + ServerPEM: certChainPEM[0], + CaPEM: caPEM, + CertChainPEM: certChainPEM, + TLSOptions: h.Authority.GetTLSOptions(), + }, http.StatusCreated) +} diff --git a/authority/tls.go b/authority/tls.go index dbfbf96a..ac8ff578 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -199,6 +199,89 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error "authority.Renew; error renewing certificate from existing server certificate", opts...) } + serverCert, err := x509.ParseCertificate(crtBytes) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, + "authority.Renew; error parsing new server certificate", opts...) + } + + if err = a.db.StoreCertificate(serverCert); err != nil { + if err != db.ErrNotImplemented { + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew; error storing certificate in db", opts...) + } + } + + return []*x509.Certificate{serverCert, a.x509Issuer}, nil +} + +// Rekey is similar to renew except that the certificate will be renewed with new key from csr argument. +func (a *Authority) Rekey(oldCert *x509.Certificate, csr *x509.CertificateRequest) ([]*x509.Certificate, error) { + opts := []interface{}{errs.WithKeyVal("serialNumber", oldCert.SerialNumber.String())} + + // Check step provisioner extensions + if err := a.authorizeRenew(oldCert); err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...) + } + + + // Durations + backdate := a.config.AuthorityConfig.Backdate.Duration + duration := oldCert.NotAfter.Sub(oldCert.NotBefore) + now := time.Now().UTC() + + + newCert := &x509.Certificate{ + PublicKey: csr.PublicKey, + Issuer: a.x509Issuer.Subject, + Subject: oldCert.Subject, + NotBefore: now.Add(-1 * backdate), + NotAfter: now.Add(duration - backdate), + KeyUsage: oldCert.KeyUsage, + UnhandledCriticalExtensions: oldCert.UnhandledCriticalExtensions, + ExtKeyUsage: oldCert.ExtKeyUsage, + UnknownExtKeyUsage: oldCert.UnknownExtKeyUsage, + BasicConstraintsValid: oldCert.BasicConstraintsValid, + IsCA: oldCert.IsCA, + MaxPathLen: oldCert.MaxPathLen, + MaxPathLenZero: oldCert.MaxPathLenZero, + OCSPServer: oldCert.OCSPServer, + IssuingCertificateURL: oldCert.IssuingCertificateURL, + PermittedDNSDomainsCritical: oldCert.PermittedDNSDomainsCritical, + PermittedEmailAddresses: oldCert.PermittedEmailAddresses, + DNSNames: oldCert.DNSNames, + EmailAddresses: oldCert.EmailAddresses, + IPAddresses: oldCert.IPAddresses, + URIs: oldCert.URIs, + PermittedDNSDomains: oldCert.PermittedDNSDomains, + ExcludedDNSDomains: oldCert.ExcludedDNSDomains, + PermittedIPRanges: oldCert.PermittedIPRanges, + ExcludedIPRanges: oldCert.ExcludedIPRanges, + ExcludedEmailAddresses: oldCert.ExcludedEmailAddresses, + PermittedURIDomains: oldCert.PermittedURIDomains, + ExcludedURIDomains: oldCert.ExcludedURIDomains, + CRLDistributionPoints: oldCert.CRLDistributionPoints, + PolicyIdentifiers: oldCert.PolicyIdentifiers, + } + + // Copy all extensions except for Authority Key Identifier. This one might + // be different if we rotate the intermediate certificate and it will cause + // a TLS bad certificate error. + for _, ext := range oldCert.Extensions { + if !ext.Id.Equal(oidAuthorityKeyIdentifier) { + newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext) + } + } + + leaf, err := x509util.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer) + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Renew", opts...) + } + crtBytes, err := leaf.CreateCertificate() + if err != nil { + return nil, errs.Wrap(http.StatusInternalServerError, err, + "authority.Renew; error renewing certificate from existing server certificate", opts...) + } + serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, @@ -214,6 +297,7 @@ func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error return []*x509.Certificate{serverCert, a.x509Issuer}, nil } + // RevokeOptions are the options for the Revoke API. type RevokeOptions struct { Serial string