// revCheck should check the certificate for any revocations. It // returns a pair of booleans: the first indicates whether the certificate // is revoked, the second indicates whether the revocations were // successfully checked.. This leads to the following combinations: // // false, false: an error was encountered while checking revocations. // // false, true: the certificate was checked successfully and // it is not revoked. // // true, true: the certificate was checked successfully and // it is revoked. // // true, false: failure to check revocation status causes // verification to fail func revCheck(cert *x509.Certificate) (revoked, ok bool) { for _, url := range cert.CRLDistributionPoints { if ldapURL(url) { log.Infof("skipping LDAP CRL: %s", url) continue } if revoked, ok := certIsRevokedCRL(cert, url); !ok { log.Warning("error checking revocation via CRL") if HardFail { return true, false } return false, false } else if revoked { log.Info("certificate is revoked via CRL") return true, true } if revoked, ok := certIsRevokedOCSP(cert, HardFail); !ok { log.Warning("error checking revocation via OCSP") if HardFail { return true, false } return false, false } else if revoked { log.Info("certificate is revoked via OCSP") return true, true } } return false, true }
// CSRValidate contains the default validation logic for certificate requests to // the API server. This follows the Baseline Requirements for the Issuance and // Management of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser // Forum (https://cabforum.org). Specifically, section 10.2.3 ("Information // Requirements"), states: // // "Applicant information MUST include, but not be limited to, at least one // Fully-Qualified Domain Name or IP address to be included in the Certificate’s // SubjectAltName extension." func CSRValidate(req *csr.CertificateRequest) error { if len(req.Hosts) == 0 { log.Warning("request for CSR is missing the host parameter") return errors.NewBadRequestMissingParameter("hosts") } return nil }
// Handle implements an http.Handler interface for the bundle handler. func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) error { blob, matched, err := api.ProcessRequestFirstMatchOf(r, [][]string{ []string{"certificate"}, []string{"domain"}, }) if err != nil { log.Warningf("invalid request: %v", err) return err } flavor := blob["flavor"] bf := bundler.Ubiquitous if flavor != "" { bf = bundler.BundleFlavor(flavor) } log.Infof("request for flavor %v", bf) var result *bundler.Bundle switch matched[0] { case "domain": bundle, err := h.bundler.BundleFromRemote(blob["domain"], blob["ip"], bf) if err != nil { log.Warningf("couldn't bundle from remote: %v", err) return err } result = bundle case "certificate": bundle, err := h.bundler.BundleFromPEMorDER([]byte(blob["certificate"]), []byte(blob["private_key"]), bf, "") if err != nil { log.Warning("bad PEM certifcate or private key") return err } serverName := blob["domain"] ip := blob["ip"] if serverName != "" { err := bundle.Cert.VerifyHostname(serverName) if err != nil { return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } } if ip != "" { err := bundle.Cert.VerifyHostname(ip) if err != nil { return errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } } result = bundle } log.Info("wrote response") return api.SendResponse(w, result) }
// Handle receives the incoming request, validates it, and processes it. func (h *AuthHandler) Handle(w http.ResponseWriter, r *http.Request) error { log.Info("signature request received") body, err := ioutil.ReadAll(r.Body) if err != nil { log.Errorf("failed to read response body: %v", err) return err } r.Body.Close() var aReq auth.AuthenticatedRequest err = json.Unmarshal(body, &aReq) if err != nil { log.Errorf("failed to unmarshal authenticated request: %v", err) return errors.NewBadRequest(err) } var req jsonSignRequest err = json.Unmarshal(aReq.Request, &req) if err != nil { log.Errorf("failed to unmarshal request from authenticated request: %v", err) return errors.NewBadRequestString("Unable to parse authenticated sign request") } // Sanity checks to ensure that we have a valid policy. This // should have been checked in NewAuthHandler. policy := h.signer.Policy() if policy == nil { log.Critical("signer was initialised without a signing policy") return errors.NewBadRequestString("invalid policy") } profile, err := signer.Profile(h.signer, req.Profile) if err != nil { return err } if profile.Provider == nil { log.Error("profile has no authentication provider") return errors.NewBadRequestString("no authentication provider") } if !profile.Provider.Verify(&aReq) { log.Warning("received authenticated request with invalid token") return errors.NewBadRequestString("invalid token") } signReq := jsonReqToTrue(req) if signReq.Request == "" { return errors.NewBadRequestString("missing parameter 'certificate_request'") } cert, err := h.signer.Sign(signReq) if err != nil { log.Errorf("signature failed: %v", err) return err } result := map[string]string{"certificate": string(cert)} log.Info("wrote response") return api.SendResponse(w, result) }
func metricsDisallowed(w http.ResponseWriter, req *http.Request) { log.Warning("attempt to access metrics endpoint from external address ", req.RemoteAddr) http.NotFound(w, req) }
// Bundle takes an X509 certificate (already in the // Certificate structure), a private key as crypto.Signer in one of the appropriate // formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to // build a certificate bundle. func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) { log.Infof("bundling certificate for %+v", certs[0].Subject) if len(certs) == 0 { return nil, nil } // Detect reverse ordering of the cert chain. if len(certs) > 1 && !partialVerify(certs) { rcerts := reverse(certs) if partialVerify(rcerts) { certs = rcerts } } var ok bool cert := certs[0] if key != nil { switch { case cert.PublicKeyAlgorithm == x509.RSA: var rsaPublicKey *rsa.PublicKey if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } if cert.PublicKey.(*rsa.PublicKey).N.Cmp(rsaPublicKey.N) != 0 { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } case cert.PublicKeyAlgorithm == x509.ECDSA: var ecdsaPublicKey *ecdsa.PublicKey if ecdsaPublicKey, ok = key.Public().(*ecdsa.PublicKey); !ok { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } default: return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC) } } else { switch { case cert.PublicKeyAlgorithm == x509.RSA: case cert.PublicKeyAlgorithm == x509.ECDSA: default: return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC) } } bundle := new(Bundle) bundle.Cert = cert bundle.Key = key bundle.Issuer = &cert.Issuer bundle.Subject = &cert.Subject bundle.buildHostnames() if flavor == Force { // force bundle checks the certificates // forms a verification chain. if !partialVerify(certs) { return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, goerr.New("Unable to verify the certificate chain")) } bundle.Chain = certs } else { // disallow self-signed cert if cert.CheckSignatureFrom(cert) == nil { return nil, errors.New(errors.CertificateError, errors.SelfSigned) } // verify and store input intermediates to the intermediate pool. // Ignore the returned error here, will treat it in the second call. b.fetchIntermediates(certs) chains, err := cert.Verify(b.VerifyOptions()) if err != nil { log.Debugf("verification failed: %v", err) // If the error was an unknown authority, try to fetch // the intermediate specified in the AIA and add it to // the intermediates bundle. switch err := err.(type) { case x509.UnknownAuthorityError: // Do nothing -- have the default case return out. default: return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("searching for intermediates via AIA issuer") err = b.fetchIntermediates(certs) if err != nil { log.Debugf("search failed: %v", err) return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("verifying new chain") chains, err = cert.Verify(b.VerifyOptions()) if err != nil { log.Debugf("failed to verify chain: %v", err) return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("verify ok") } var matchingChains [][]*x509.Certificate switch flavor { case Optimal: matchingChains = optimalChains(chains) case Ubiquitous: if len(ubiquity.Platforms) == 0 { log.Warning("No metadata, Ubiquitous falls back to Optimal.") } matchingChains = ubiquitousChains(chains) default: matchingChains = ubiquitousChains(chains) } bundle.Chain = matchingChains[0] } statusCode := int(errors.Success) var messages []string // Check if bundle is expiring. expiringCerts := checkExpiringCerts(bundle.Chain) bundle.Expires = helpers.ExpiryTime(bundle.Chain) if len(expiringCerts) > 0 { statusCode |= errors.BundleExpiringBit messages = append(messages, expirationWarning(expiringCerts)) } // Check if bundle contains SHA2 certs. if ubiquity.ChainHashUbiquity(bundle.Chain) <= ubiquity.SHA2Ubiquity { statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, sha2Warning) } // Check if bundle contains ECDSA signatures. if ubiquity.ChainKeyAlgoUbiquity(bundle.Chain) <= ubiquity.ECDSA256Ubiquity { statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, ecdsaWarning) } // when forcing a bundle, bundle ubiquity doesn't matter // also we don't retrieve the anchoring root of the bundle var untrusted []string if flavor != Force { // Add root store presence info root := bundle.Chain[len(bundle.Chain)-1] bundle.Root = root log.Infof("the anchoring root is %v", root.Subject) // Check if there is any platform that doesn't trust the chain. // Also, an warning will be generated if ubiquity.Platforms is nil, untrusted = ubiquity.UntrustedPlatforms(root) untrustedMsg := untrustedPlatformsWarning(untrusted) if len(untrustedMsg) > 0 { log.Debug("Populate untrusted platform warning.") statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, untrustedMsg) } } // Check if there is any platform that rejects the chain because of SHA1 deprecation. sha1Msgs := ubiquity.SHA1DeprecationMessages(bundle.Chain) if len(sha1Msgs) > 0 { log.Debug("Populate SHA1 deprecation warning.") statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, sha1Msgs...) } bundle.Status = &BundleStatus{ExpiringSKIs: getSKIs(bundle.Chain, expiringCerts), Code: statusCode, Messages: messages, Untrusted: untrusted} // attempt to not to include the root certificate for optimization if flavor != Force { // Include at least one intermediate if the leaf has enabled OCSP and is not CA. if bundle.Cert.OCSPServer != nil && !bundle.Cert.IsCA && len(bundle.Chain) <= 2 { // No op. Return one intermediate if there is one. } else { // do not include the root. bundle.Chain = bundle.Chain[:len(bundle.Chain)-1] } } bundle.Status.IsRebundled = diff(bundle.Chain, certs) log.Debugf("bundle complete") return bundle, nil }
func parsePrivateKeySpec(spec string, cfg map[string]string) (crypto.Signer, error) { specURL, err := url.Parse(spec) if err != nil { return nil, err } var priv crypto.Signer switch specURL.Scheme { case "file": // A file spec will be parsed such that the root // directory of a relative path will be stored as the // hostname, and the remainder of the file's path is // stored in the Path field. log.Debug("loading private key file", specURL.Path) path := filepath.Join(specURL.Host, specURL.Path) in, err := ioutil.ReadFile(path) if err != nil { return nil, err } log.Debug("attempting to load PEM-encoded private key") priv, err = helpers.ParsePrivateKeyPEM(in) if err != nil { log.Debug("file is not a PEM-encoded private key") log.Debug("attempting to load DER-encoded private key") priv, err = derhelpers.ParsePrivateKeyDER(in) if err != nil { return nil, err } } log.Debug("loaded private key") return priv, nil case "rofile": log.Warning("Red October support is currently experimental") path := filepath.Join(specURL.Host, specURL.Path) in, err := ioutil.ReadFile(path) if err != nil { return nil, err } roServer := cfg["ro_server"] if roServer == "" { return nil, errors.New("config: no RedOctober server available") } // roCAPath can be empty; if it is, the client uses // the system default CA roots. roCAPath := cfg["ro_ca"] roUser := cfg["ro_user"] if roUser == "" { return nil, errors.New("config: no RedOctober user available") } roPass := cfg["ro_pass"] if roPass == "" { return nil, errors.New("config: no RedOctober passphrase available") } log.Debug("decrypting key via RedOctober Server") roClient, err := client.NewRemoteServer(roServer, roCAPath) if err != nil { return nil, err } req := core.DecryptRequest{ Name: roUser, Password: roPass, Data: in, } in, err = roClient.DecryptIntoData(req) if err != nil { return nil, err } return priv, nil default: return nil, ErrUnsupportedScheme } }
// Handle responds to requests for the CA to generate a new private // key and certificate on behalf of the client. The format for these // requests is documented in the API documentation. func (cg *CertGeneratorHandler) Handle(w http.ResponseWriter, r *http.Request) error { log.Info("request for CSR") req := new(genSignRequest) body, err := ioutil.ReadAll(r.Body) if err != nil { log.Warningf("failed to read request body: %v", err) return errors.NewBadRequest(err) } err = json.Unmarshal(body, req) if err != nil { log.Warningf("failed to unmarshal request: %v", err) return errors.NewBadRequest(err) } if req.Request == nil { log.Warning("empty request received") return errors.NewBadRequestString("missing request section") } if req.Request.CA != nil { log.Warningf("request received with CA section") return errors.NewBadRequestString("ca section only permitted in initca") } csr, key, err := cg.generator.ProcessRequest(req.Request) if err != nil { log.Warningf("failed to process CSR: %v", err) // The validator returns a *cfssl/errors.HttpError return err } signReq := signer.SignRequest{ Request: string(csr), Profile: req.Profile, Label: req.Label, } certBytes, err := cg.signer.Sign(signReq) if err != nil { log.Warningf("failed to sign request: %v", err) return err } reqSum, err := computeSum(csr) if err != nil { return errors.NewBadRequest(err) } certSum, err := computeSum(certBytes) if err != nil { return errors.NewBadRequest(err) } result := map[string]interface{}{ "private_key": string(key), "certificate_request": string(csr), "certificate": string(certBytes), "sums": map[string]Sum{ "certificate_request": reqSum, "certificate": certSum, }, } return api.SendResponse(w, result) }