Example #1
0
// 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
}
Example #2
0
// 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
}
Example #3
0
// 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
		}
	}
}
Example #4
0
// 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
}
Example #5
0
// 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
}
Example #6
0
// 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
}
Example #7
0
// 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
		}
	}
}
Example #8
0
// 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
}