package authority import ( "context" "crypto" "crypto/tls" "crypto/x509" "encoding/asn1" "encoding/base64" "encoding/pem" "net/http" "strings" "time" "github.com/pkg/errors" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" "github.com/smallstep/certificates/x509util" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/tlsutil" x509legacy "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/jose" ) // GetTLSOptions returns the tls options configured. func (a *Authority) GetTLSOptions() *tlsutil.TLSOptions { return a.config.TLS } var oidAuthorityKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 35} var oidSubjectKeyIdentifier = asn1.ObjectIdentifier{2, 5, 29, 14} func withDefaultASN1DN(def *x509legacy.ASN1DN) provisioner.CertificateModifierFunc { return func(crt *x509.Certificate, opts provisioner.SignOptions) error { if def == nil { return errors.New("default ASN1DN template cannot be nil") } if len(crt.Subject.Country) == 0 && def.Country != "" { crt.Subject.Country = append(crt.Subject.Country, def.Country) } if len(crt.Subject.Organization) == 0 && def.Organization != "" { crt.Subject.Organization = append(crt.Subject.Organization, def.Organization) } if len(crt.Subject.OrganizationalUnit) == 0 && def.OrganizationalUnit != "" { crt.Subject.OrganizationalUnit = append(crt.Subject.OrganizationalUnit, def.OrganizationalUnit) } if len(crt.Subject.Locality) == 0 && def.Locality != "" { crt.Subject.Locality = append(crt.Subject.Locality, def.Locality) } if len(crt.Subject.Province) == 0 && def.Province != "" { crt.Subject.Province = append(crt.Subject.Province, def.Province) } if len(crt.Subject.StreetAddress) == 0 && def.StreetAddress != "" { crt.Subject.StreetAddress = append(crt.Subject.StreetAddress, def.StreetAddress) } return nil } } // Sign creates a signed certificate from a certificate signing request. func (a *Authority) Sign(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) { var ( certOptions []x509util.Option certValidators []provisioner.CertificateValidator certModifiers []provisioner.CertificateModifier certEnforcers []provisioner.CertificateEnforcer ) opts := []interface{}{errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts)} if err := csr.CheckSignature(); err != nil { return nil, errs.Wrap(http.StatusBadRequest, err, "authority.Sign; invalid certificate request", opts...) } // Set backdate with the configured value signOpts.Backdate = a.config.AuthorityConfig.Backdate.Duration for _, op := range extraOpts { switch k := op.(type) { // Adds new options to NewCertificate case provisioner.CertificateOptions: certOptions = append(certOptions, k.Options(signOpts)...) // Validate tie given certificate request. case provisioner.CertificateRequestValidator: if err := k.Valid(csr); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } // Validates the unsigned certificate template. case provisioner.CertificateValidator: certValidators = append(certValidators, k) // Modifies a certificate before validating it. case provisioner.CertificateModifier: certModifiers = append(certModifiers, k) // Modifies a certificate after validating it. case provisioner.CertificateEnforcer: certEnforcers = append(certEnforcers, k) default: return nil, errs.InternalServer("authority.Sign; invalid extra option type %T", append([]interface{}{k}, opts...)...) } } cert, err := x509util.NewCertificate(csr, certOptions...) if err != nil { if _, ok := err.(*x509util.TemplateError); ok { return nil, errs.NewErr(http.StatusBadRequest, err, errs.WithMessage(err.Error()), errs.WithKeyVal("csr", csr), errs.WithKeyVal("signOptions", signOpts), ) } return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign", opts...) } // Certificate modifiers before validation leaf := cert.GetCertificate() // Set default subject if err := withDefaultASN1DN(a.config.AuthorityConfig.Template).Modify(leaf, signOpts); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } for _, m := range certModifiers { if err := m.Modify(leaf, signOpts); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } // Certificate validation. for _, v := range certValidators { if err := v.Valid(leaf, signOpts); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } // Certificate modifiers after validation for _, m := range certEnforcers { if err := m.Enforce(leaf); err != nil { return nil, errs.Wrap(http.StatusUnauthorized, err, "authority.Sign", opts...) } } serverCert, err := x509util.CreateCertificate(leaf, a.x509Issuer, csr.PublicKey, a.x509Signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error creating certificate", opts...) } if err = a.db.StoreCertificate(serverCert); err != nil { if err != db.ErrNotImplemented { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Sign; error storing certificate in db", opts...) } } return []*x509.Certificate{serverCert, a.x509Issuer}, nil } // Renew creates a new Certificate identical to the old certificate, except // with a validity window that begins 'now'. func (a *Authority) Renew(oldCert *x509.Certificate) ([]*x509.Certificate, error) { return a.Rekey(oldCert, nil) } // Rekey is used for rekeying and renewing based on the public key. // If the public key is 'nil' then it's assumed that the cert should be renewed // using the existing public key. If the public key is not 'nil' then it's // assumed that the cert should be rekeyed. // For both Rekey and Renew all other attributes of the new certificate should // match the old certificate. The exceptions are 'AuthorityKeyId' (which may // have changed), 'SubjectKeyId' (different in case of rekey), and // 'NotBefore/NotAfter' (the validity duration of the new certificate should be // equal to the old one, but starting 'now'). func (a *Authority) Rekey(oldCert *x509.Certificate, pk crypto.PublicKey) ([]*x509.Certificate, error) { isRekey := (pk != nil) 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.Rekey", opts...) } // Durations backdate := a.config.AuthorityConfig.Backdate.Duration duration := oldCert.NotAfter.Sub(oldCert.NotBefore) now := time.Now().UTC() newCert := &x509.Certificate{ 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, } if isRekey { newCert.PublicKey = pk } else { newCert.PublicKey = oldCert.PublicKey } // Copy all extensions except: // 1. Authority Key Identifier - This one might be different if we rotate the intermediate certificate // and it will cause a TLS bad certificate error. // 2. Subject Key Identifier, if rekey - For rekey, SubjectKeyIdentifier extension will be calculated // for the new public key by NewLeafProfilewithTemplate() for _, ext := range oldCert.Extensions { if ext.Id.Equal(oidAuthorityKeyIdentifier) { continue } if ext.Id.Equal(oidSubjectKeyIdentifier) && isRekey { newCert.SubjectKeyId = nil continue } newCert.ExtraExtensions = append(newCert.ExtraExtensions, ext) } leaf, err := x509legacy.NewLeafProfileWithTemplate(newCert, a.x509Issuer, a.x509Signer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey", opts...) } crtBytes, err := leaf.CreateCertificate() if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; error renewing certificate from existing server certificate", opts...) } serverCert, err := x509.ParseCertificate(crtBytes) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.Rekey; 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.Rekey; error storing certificate in db", opts...) } } return []*x509.Certificate{serverCert, a.x509Issuer}, nil } // RevokeOptions are the options for the Revoke API. type RevokeOptions struct { Serial string Reason string ReasonCode int PassiveOnly bool MTLS bool Crt *x509.Certificate OTT string } // Revoke revokes a certificate. // // NOTE: Only supports passive revocation - prevent existing certificates from // being renewed. // // TODO: Add OCSP and CRL support. func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error { opts := []interface{}{ errs.WithKeyVal("serialNumber", revokeOpts.Serial), errs.WithKeyVal("reasonCode", revokeOpts.ReasonCode), errs.WithKeyVal("reason", revokeOpts.Reason), errs.WithKeyVal("passiveOnly", revokeOpts.PassiveOnly), errs.WithKeyVal("MTLS", revokeOpts.MTLS), errs.WithKeyVal("context", string(provisioner.MethodFromContext(ctx))), } if revokeOpts.MTLS { opts = append(opts, errs.WithKeyVal("certificate", base64.StdEncoding.EncodeToString(revokeOpts.Crt.Raw))) } else { opts = append(opts, errs.WithKeyVal("token", revokeOpts.OTT)) } rci := &db.RevokedCertificateInfo{ Serial: revokeOpts.Serial, ReasonCode: revokeOpts.ReasonCode, Reason: revokeOpts.Reason, MTLS: revokeOpts.MTLS, RevokedAt: time.Now().UTC(), } var ( p provisioner.Interface err error ) // If not mTLS then get the TokenID of the token. if !revokeOpts.MTLS { token, err := jose.ParseSigned(revokeOpts.OTT) if err != nil { return errs.Wrap(http.StatusUnauthorized, err, "authority.Revoke; error parsing token", opts...) } // Get claims w/out verification. var claims Claims if err = token.UnsafeClaimsWithoutVerification(&claims); err != nil { return errs.Wrap(http.StatusUnauthorized, err, "authority.Revoke", opts...) } // This method will also validate the audiences for JWK provisioners. var ok bool p, ok = a.provisioners.LoadByToken(token, &claims.Claims) if !ok { return errs.InternalServer("authority.Revoke; provisioner not found", opts...) } rci.TokenID, err = p.GetTokenID(revokeOpts.OTT) if err != nil { return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke; could not get ID for token") } opts = append(opts, errs.WithKeyVal("tokenID", rci.TokenID)) } else { // Load the Certificate provisioner if one exists. p, err = a.LoadProvisionerByCertificate(revokeOpts.Crt) if err != nil { return errs.Wrap(http.StatusUnauthorized, err, "authority.Revoke: unable to load certificate provisioner", opts...) } } rci.ProvisionerID = p.GetID() opts = append(opts, errs.WithKeyVal("provisionerID", rci.ProvisionerID)) if provisioner.MethodFromContext(ctx) == provisioner.SSHRevokeMethod { err = a.db.RevokeSSH(rci) } else { // default to revoke x509 err = a.db.Revoke(rci) } switch err { case nil: return nil case db.ErrNotImplemented: return errs.NotImplemented("authority.Revoke; no persistence layer configured", opts...) case db.ErrAlreadyExists: return errs.BadRequest("authority.Revoke; certificate with serial "+ "number %s has already been revoked", append([]interface{}{rci.Serial}, opts...)...) default: return errs.Wrap(http.StatusInternalServerError, err, "authority.Revoke", opts...) } } // GetTLSCertificate creates a new leaf certificate to be used by the CA HTTPS server. func (a *Authority) GetTLSCertificate() (*tls.Certificate, error) { profile, err := x509legacy.NewLeafProfile("Step Online CA", a.x509Issuer, a.x509Signer, x509legacy.WithHosts(strings.Join(a.config.DNSNames, ","))) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } crtBytes, err := profile.CreateCertificate() if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } keyPEM, err := pemutil.Serialize(profile.SubjectPrivateKey()) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } crtPEM := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: crtBytes, }) // Load the x509 key pair (combining server and intermediate blocks) // to a tls.Certificate. intermediatePEM, err := pemutil.Serialize(a.x509Issuer) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate") } tlsCrt, err := tls.X509KeyPair(append(crtPEM, pem.EncodeToMemory(intermediatePEM)...), pem.EncodeToMemory(keyPEM)) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate; error creating tls certificate") } // Get the 'leaf' certificate and set the attribute accordingly. leaf, err := x509.ParseCertificate(tlsCrt.Certificate[0]) if err != nil { return nil, errs.Wrap(http.StatusInternalServerError, err, "authority.GetTLSCertificate; error parsing tls certificate") } tlsCrt.Leaf = leaf return &tlsCrt, nil }