// StapleOCSP staples OCSP responses to each config according to their certificate. // This should work for any TLS-enabled config, not just Let's Encrypt ones. func StapleOCSP(configs []server.Config) error { for i := 0; i < len(configs); i++ { if configs[i].TLS.Certificate == "" { continue } bundleBytes, err := ioutil.ReadFile(configs[i].TLS.Certificate) if err != nil { return errors.New("load certificate to staple ocsp: " + err.Error()) } ocspBytes, ocspResp, err := acme.GetOCSPForCert(bundleBytes) if err == nil { // TODO: We ignore the error if it exists because some certificates // may not have an issuer URL which we should ignore anyway, and // sometimes we get syntax errors in the responses. To reproduce this // behavior, start Caddy with an empty Caddyfile and -log stderr. Then // add a host to the Caddyfile which requires a new LE certificate. // Reload Caddy's config with SIGUSR1, and see the log report that it // obtains the certificate, but then an error: // getting ocsp: asn1: syntax error: sequence truncated // But retrying the reload again sometimes solves the problem. It's flaky... ocspCache[&bundleBytes] = ocspResp if ocspResp.Status == ocsp.Good { configs[i].TLS.OCSPStaple = ocspBytes } } } return nil }
// autoConfigure enables TLS on allConfigs[cfgIndex] and appends, if necessary, // a new config to allConfigs that redirects plaintext HTTP to its new HTTPS // counterpart. It expects the certificate and key to already be in storage. It // returns the new list of allConfigs, since it may append a new config. This // function assumes that allConfigs[cfgIndex] is already set up for HTTPS. func autoConfigure(allConfigs []server.Config, cfgIndex int) []server.Config { cfg := &allConfigs[cfgIndex] bundleBytes, err := ioutil.ReadFile(storage.SiteCertFile(cfg.Host)) // TODO: Handle these errors better if err == nil { ocsp, status, err := acme.GetOCSPForCert(bundleBytes) ocspStatus[&bundleBytes] = status if err == nil && status == acme.OCSPGood { cfg.TLS.OCSPStaple = ocsp } } cfg.TLS.Certificate = storage.SiteCertFile(cfg.Host) cfg.TLS.Key = storage.SiteKeyFile(cfg.Host) cfg.TLS.Enabled = true // Ensure all defaults are set for the TLS config setup.SetDefaultTLSParams(cfg) if cfg.Port == "" { cfg.Port = "https" } // Set up http->https redirect as long as there isn't already a http counterpart // in the configs and this isn't, for some reason, already on port 80. // Also, the port 80 variant of this config is necessary for proxying challenge requests. if !otherHostHasScheme(allConfigs, cfgIndex, "http") && cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case) allConfigs = append(allConfigs, redirPlaintextHost(*cfg)) } // To support renewals, we need handlers at ports 80 and 443, // depending on the challenge type that is used to complete renewal. for i, c := range allConfigs { if c.Address() == cfg.Host+":80" || c.Address() == cfg.Host+":443" || c.Address() == cfg.Host+":http" || c.Address() == cfg.Host+":https" { // Each virtualhost must have their own handlers, or the chaining gets messed up when middlewares are compiled! handler := new(Handler) mid := func(next middleware.Handler) middleware.Handler { handler.Next = next return handler } // TODO: Currently, acmeHandlers are not referenced, but we need to add a way to toggle // their proxy functionality -- or maybe not. Gotta figure this out for sure. acmeHandlers[c.Address()] = handler allConfigs[i].Middleware["/"] = append(allConfigs[i].Middleware["/"], mid) } } return allConfigs }
// maintainAssets is a permanently-blocking function // that loops indefinitely and, on a regular schedule, checks // certificates for expiration and initiates a renewal of certs // that are expiring soon. It also updates OCSP stapling and // performs other maintenance of assets. // // You must pass in the server configs to maintain and the channel // which you'll close when maintenance should stop, to allow this // goroutine to clean up after itself and unblock. func maintainAssets(configs []server.Config, stopChan chan struct{}) { renewalTicker := time.NewTicker(RenewInterval) ocspTicker := time.NewTicker(OCSPInterval) for { select { case <-renewalTicker.C: n, errs := renewCertificates(configs, true) if len(errs) > 0 { for _, err := range errs { log.Printf("[ERROR] Certificate renewal: %v", err) } } // even if there was an error, some renewals may have succeeded if n > 0 && OnChange != nil { err := OnChange() if err != nil { log.Printf("[ERROR] OnChange after cert renewal: %v", err) } } case <-ocspTicker.C: for bundle, oldResp := range ocspCache { // start checking OCSP staple about halfway through validity period for good measure refreshTime := oldResp.ThisUpdate.Add(oldResp.NextUpdate.Sub(oldResp.ThisUpdate) / 2) // only check for updated OCSP validity window if refreshTime is in the past if time.Now().After(refreshTime) { _, newResp, err := acme.GetOCSPForCert(*bundle) if err != nil { log.Printf("[ERROR] Checking OCSP for bundle: %v", err) continue } // we're not looking for different status, just a more future expiration if newResp.NextUpdate != oldResp.NextUpdate { if OnChange != nil { log.Printf("[INFO] Updating OCSP stapling to extend validity period to %v", newResp.NextUpdate) err := OnChange() if err != nil { log.Printf("[ERROR] OnChange after OCSP trigger: %v", err) } break } } } } case <-stopChan: renewalTicker.Stop() ocspTicker.Stop() return } } }
// autoConfigure enables TLS on allConfigs[cfgIndex] and appends, if necessary, // a new config to allConfigs that redirects plaintext HTTP to its new HTTPS // counterpart. It expects the certificate and key to already be in storage. It // returns the new list of allConfigs, since it may append a new config. This // function assumes that allConfigs[cfgIndex] is already set up for HTTPS. func autoConfigure(allConfigs []server.Config, cfgIndex int) []server.Config { cfg := &allConfigs[cfgIndex] bundleBytes, err := ioutil.ReadFile(storage.SiteCertFile(cfg.Host)) // TODO: Handle these errors better if err == nil { ocsp, status, err := acme.GetOCSPForCert(bundleBytes) ocspStatus[&bundleBytes] = status if err == nil && status == acme.OCSPGood { cfg.TLS.OCSPStaple = ocsp } } cfg.TLS.Certificate = storage.SiteCertFile(cfg.Host) cfg.TLS.Key = storage.SiteKeyFile(cfg.Host) cfg.TLS.Enabled = true // Ensure all defaults are set for the TLS config setup.SetDefaultTLSParams(cfg) if cfg.Port == "" { cfg.Port = "https" } // Chain in ACME middleware proxy if we use up the SSL port if cfg.Port == "https" || cfg.Port == "443" { handler := new(Handler) mid := func(next middleware.Handler) middleware.Handler { handler.Next = next return handler } cfg.Middleware["/"] = append(cfg.Middleware["/"], mid) acmeHandlers[cfg.Host] = handler } // Set up http->https redirect as long as there isn't already a http counterpart // in the configs and this isn't, for some reason, already on port 80 if !otherHostHasScheme(allConfigs, cfgIndex, "http") && cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case) allConfigs = append(allConfigs, redirPlaintextHost(*cfg)) } return allConfigs }
// stapleOCSP staples OCSP information to cert for hostname name. // If you have it handy, you should pass in the PEM-encoded certificate // bundle; otherwise the DER-encoded cert will have to be PEM-encoded. // If you don't have the PEM blocks handy, just pass in nil. // // Errors here are not necessarily fatal, it could just be that the // certificate doesn't have an issuer URL. func stapleOCSP(cert *Certificate, pemBundle []byte) error { if pemBundle == nil { // The function in the acme package that gets OCSP requires a PEM-encoded cert bundle := new(bytes.Buffer) for _, derBytes := range cert.Certificate.Certificate { pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) } pemBundle = bundle.Bytes() } ocspBytes, ocspResp, err := acme.GetOCSPForCert(pemBundle) if err != nil { return err } cert.Certificate.OCSPStaple = ocspBytes cert.OCSP = ocspResp return nil }
// makeCertificate turns a certificate PEM bundle and a key PEM block into // a Certificate, with OCSP and other relevant metadata tagged with it, // except for the OnDemand and Managed flags. It is up to the caller to // set those properties. func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) { var cert Certificate // Convert to a tls.Certificate tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) if err != nil { return cert, err } if len(tlsCert.Certificate) == 0 { return cert, errors.New("certificate is empty") } // Parse leaf certificate and extract relevant metadata leaf, err := x509.ParseCertificate(tlsCert.Certificate[0]) if err != nil { return cert, err } if leaf.Subject.CommonName != "" { cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)} } for _, name := range leaf.DNSNames { if name != leaf.Subject.CommonName { cert.Names = append(cert.Names, strings.ToLower(name)) } } cert.NotAfter = leaf.NotAfter // Staple OCSP ocspBytes, ocspResp, err := acme.GetOCSPForCert(certPEMBlock) if err != nil { // An error here is not a problem because a certificate may simply // not contain a link to an OCSP server. But we should log it anyway. log.Printf("[WARNING] No OCSP stapling for %v: %v", cert.Names, err) } else if ocspResp.Status == ocsp.Good { tlsCert.OCSPStaple = ocspBytes cert.OCSP = ocspResp } cert.Certificate = tlsCert return cert, nil }
// maintainAssets is a permanently-blocking function // that loops indefinitely and, on a regular schedule, checks // certificates for expiration and initiates a renewal of certs // that are expiring soon. It also updates OCSP stapling and // performs other maintenance of assets. // // You must pass in the server configs to maintain and the channel // which you'll close when maintenance should stop, to allow this // goroutine to clean up after itself and unblock. func maintainAssets(configs []server.Config, stopChan chan struct{}) { renewalTicker := time.NewTicker(renewInterval) ocspTicker := time.NewTicker(ocspInterval) for { select { case <-renewalTicker.C: n, errs := renewCertificates(configs, true) if len(errs) > 0 { for _, err := range errs { log.Printf("[ERROR] Certificate renewal: %v", err) } } // even if there was an error, some renewals may have succeeded if n > 0 && OnChange != nil { err := OnChange() if err != nil { log.Printf("[ERROR] OnChange after cert renewal: %v", err) } } case <-ocspTicker.C: for bundle, oldStatus := range ocspStatus { _, newStatus, err := acme.GetOCSPForCert(*bundle) if err == nil && newStatus != oldStatus && OnChange != nil { log.Printf("[INFO] OCSP status changed from %v to %v", oldStatus, newStatus) err := OnChange() if err != nil { log.Printf("[ERROR] OnChange after OCSP update: %v", err) } break } } case <-stopChan: renewalTicker.Stop() ocspTicker.Stop() return } } }
// stapleOCSP staples OCSP information to cert for hostname name. // If you have it handy, you should pass in the PEM-encoded certificate // bundle; otherwise the DER-encoded cert will have to be PEM-encoded. // If you don't have the PEM blocks already, just pass in nil. // // Errors here are not necessarily fatal, it could just be that the // certificate doesn't have an issuer URL. func stapleOCSP(cert *Certificate, pemBundle []byte) error { if pemBundle == nil { // The function in the acme package that gets OCSP requires a PEM-encoded cert bundle := new(bytes.Buffer) for _, derBytes := range cert.Certificate.Certificate { pem.Encode(bundle, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) } pemBundle = bundle.Bytes() } var ocspBytes []byte var ocspResp *ocsp.Response var ocspErr error var gotNewOCSP bool // First try to load OCSP staple from storage and see if // we can still use it. // TODO: Use Storage interface instead of disk directly var ocspFileNamePrefix string if len(cert.Names) > 0 { ocspFileNamePrefix = cert.Names[0] + "-" } ocspFileName := ocspFileNamePrefix + fastHash(pemBundle) ocspCachePath := filepath.Join(ocspFolder, ocspFileName) cachedOCSP, err := ioutil.ReadFile(ocspCachePath) if err == nil { resp, err := ocsp.ParseResponse(cachedOCSP, nil) if err == nil { if freshOCSP(resp) { // staple is still fresh; use it ocspBytes = cachedOCSP ocspResp = resp } } else { // invalid contents; delete the file // (we do this independently of the maintenance routine because // in this case we know for sure this should be a staple file // because we loaded it by name, whereas the maintenance routine // just iterates the list of files, even if somehow a non-staple // file gets in the folder. in this case we are sure it is corrupt.) err := os.Remove(ocspCachePath) if err != nil { log.Printf("[WARNING] Unable to delete invalid OCSP staple file: %v", err) } } } // If we couldn't get a fresh staple by reading the cache, // then we need to request it from the OCSP responder if ocspResp == nil || len(ocspBytes) == 0 { ocspBytes, ocspResp, ocspErr = acme.GetOCSPForCert(pemBundle) if ocspErr != nil { // An error here is not a problem because a certificate may simply // not contain a link to an OCSP server. But we should log it anyway. // There's nothing else we can do to get OCSP for this certificate, // so we can return here with the error. return fmt.Errorf("no OCSP stapling for %v: %v", cert.Names, ocspErr) } gotNewOCSP = true } // By now, we should have a response. If good, staple it to // the certificate. If the OCSP response was not loaded from // storage, we persist it for next time. if ocspResp.Status == ocsp.Good { cert.Certificate.OCSPStaple = ocspBytes cert.OCSP = ocspResp if gotNewOCSP { err := os.MkdirAll(filepath.Join(caddy.AssetsPath(), "ocsp"), 0700) if err != nil { return fmt.Errorf("unable to make OCSP staple path for %v: %v", cert.Names, err) } err = ioutil.WriteFile(ocspCachePath, ocspBytes, 0644) if err != nil { return fmt.Errorf("unable to write OCSP staple file for %v: %v", cert.Names, err) } } } return nil }