1087 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			1087 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2015 Matthew Holt
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package certmagic
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"crypto"
 | 
						|
	"crypto/rand"
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"crypto/x509/pkix"
 | 
						|
	"encoding/asn1"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	weakrand "math/rand"
 | 
						|
	"net"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/mholt/acmez"
 | 
						|
	"github.com/mholt/acmez/acme"
 | 
						|
	"go.uber.org/zap"
 | 
						|
	"golang.org/x/net/idna"
 | 
						|
)
 | 
						|
 | 
						|
// Config configures a certificate manager instance.
 | 
						|
// An empty Config is not valid: use New() to obtain
 | 
						|
// a valid Config.
 | 
						|
type Config struct {
 | 
						|
	// How much of a certificate's lifetime becomes the
 | 
						|
	// renewal window, which is the span of time at the
 | 
						|
	// end of the certificate's validity period in which
 | 
						|
	// it should be renewed; for most certificates, the
 | 
						|
	// global default is good, but for extremely short-
 | 
						|
	// lived certs, you may want to raise this to ~0.5.
 | 
						|
	RenewalWindowRatio float64
 | 
						|
 | 
						|
	// An optional event callback clients can set
 | 
						|
	// to subscribe to certain things happening
 | 
						|
	// internally by this config; invocations are
 | 
						|
	// synchronous, so make them return quickly!
 | 
						|
	OnEvent func(event string, data interface{})
 | 
						|
 | 
						|
	// DefaultServerName specifies a server name
 | 
						|
	// to use when choosing a certificate if the
 | 
						|
	// ClientHello's ServerName field is empty.
 | 
						|
	DefaultServerName string
 | 
						|
 | 
						|
	// The state needed to operate on-demand TLS;
 | 
						|
	// if non-nil, on-demand TLS is enabled and
 | 
						|
	// certificate operations are deferred to
 | 
						|
	// TLS handshakes (or as-needed).
 | 
						|
	// TODO: Can we call this feature "Reactive/Lazy/Passive TLS" instead?
 | 
						|
	OnDemand *OnDemandConfig
 | 
						|
 | 
						|
	// Adds the must staple TLS extension to the CSR.
 | 
						|
	MustStaple bool
 | 
						|
 | 
						|
	// The source for getting new certificates; the
 | 
						|
	// default Issuer is ACMEManager. If multiple
 | 
						|
	// issuers are specified, they will be tried in
 | 
						|
	// turn until one succeeds.
 | 
						|
	Issuers []Issuer
 | 
						|
 | 
						|
	// The source of new private keys for certificates;
 | 
						|
	// the default KeySource is StandardKeyGenerator.
 | 
						|
	KeySource KeyGenerator
 | 
						|
 | 
						|
	// CertSelection chooses one of the certificates
 | 
						|
	// with which the ClientHello will be completed;
 | 
						|
	// if not set, DefaultCertificateSelector will
 | 
						|
	// be used.
 | 
						|
	CertSelection CertificateSelector
 | 
						|
 | 
						|
	// OCSP configures how OCSP is handled. By default,
 | 
						|
	// OCSP responses are fetched for every certificate
 | 
						|
	// with a responder URL, and cached on disk. Changing
 | 
						|
	// these defaults is STRONGLY discouraged unless you
 | 
						|
	// have a compelling reason to put clients at greater
 | 
						|
	// risk and reduce their privacy.
 | 
						|
	OCSP OCSPConfig
 | 
						|
 | 
						|
	// The storage to access when storing or loading
 | 
						|
	// TLS assets. Default is the local file system.
 | 
						|
	Storage Storage
 | 
						|
 | 
						|
	// Set a logger to enable logging.
 | 
						|
	Logger *zap.Logger
 | 
						|
 | 
						|
	// required pointer to the in-memory cert cache
 | 
						|
	certCache *Cache
 | 
						|
}
 | 
						|
 | 
						|
// NewDefault makes a valid config based on the package
 | 
						|
// Default config. Most users will call this function
 | 
						|
// instead of New() since most use cases require only a
 | 
						|
// single config for any and all certificates.
 | 
						|
//
 | 
						|
// If your requirements are more advanced (for example,
 | 
						|
// multiple configs depending on the certificate), then use
 | 
						|
// New() instead. (You will need to make your own Cache
 | 
						|
// first.) If you only need a single Config to manage your
 | 
						|
// certs (even if that config changes, as long as it is the
 | 
						|
// only one), customize the Default package variable before
 | 
						|
// calling NewDefault().
 | 
						|
//
 | 
						|
// All calls to NewDefault() will return configs that use the
 | 
						|
// same, default certificate cache. All configs returned
 | 
						|
// by NewDefault() are based on the values of the fields of
 | 
						|
// Default at the time it is called.
 | 
						|
//
 | 
						|
// This is the only way to get a config that uses the
 | 
						|
// default certificate cache.
 | 
						|
func NewDefault() *Config {
 | 
						|
	defaultCacheMu.Lock()
 | 
						|
	if defaultCache == nil {
 | 
						|
		defaultCache = NewCache(CacheOptions{
 | 
						|
			// the cache will likely need to renew certificates,
 | 
						|
			// so it will need to know how to do that, which
 | 
						|
			// depends on the certificate being managed and which
 | 
						|
			// can change during the lifetime of the cache; this
 | 
						|
			// callback makes it possible to get the latest and
 | 
						|
			// correct config with which to manage the cert,
 | 
						|
			// but if the user does not provide one, we can only
 | 
						|
			// assume that we are to use the default config
 | 
						|
			GetConfigForCert: func(Certificate) (*Config, error) {
 | 
						|
				return NewDefault(), nil
 | 
						|
			},
 | 
						|
		})
 | 
						|
	}
 | 
						|
	certCache := defaultCache
 | 
						|
	defaultCacheMu.Unlock()
 | 
						|
 | 
						|
	return newWithCache(certCache, Default)
 | 
						|
}
 | 
						|
 | 
						|
// New makes a new, valid config based on cfg and
 | 
						|
// uses the provided certificate cache. certCache
 | 
						|
// MUST NOT be nil or this function will panic.
 | 
						|
//
 | 
						|
// Use this method when you have an advanced use case
 | 
						|
// that requires a custom certificate cache and config
 | 
						|
// that may differ from the Default. For example, if
 | 
						|
// not all certificates are managed/renewed the same
 | 
						|
// way, you need to make your own Cache value with a
 | 
						|
// GetConfigForCert callback that returns the correct
 | 
						|
// configuration for each certificate. However, for
 | 
						|
// the vast majority of cases, there will be only a
 | 
						|
// single Config, thus the default cache (which always
 | 
						|
// uses the default Config) and default config will
 | 
						|
// suffice, and you should use NewDefault() instead.
 | 
						|
func New(certCache *Cache, cfg Config) *Config {
 | 
						|
	if certCache == nil {
 | 
						|
		panic("a certificate cache is required")
 | 
						|
	}
 | 
						|
	if certCache.options.GetConfigForCert == nil {
 | 
						|
		panic("cache must have GetConfigForCert set in its options")
 | 
						|
	}
 | 
						|
	return newWithCache(certCache, cfg)
 | 
						|
}
 | 
						|
 | 
						|
// newWithCache ensures that cfg is a valid config by populating
 | 
						|
// zero-value fields from the Default Config. If certCache is
 | 
						|
// nil, this function panics.
 | 
						|
func newWithCache(certCache *Cache, cfg Config) *Config {
 | 
						|
	if certCache == nil {
 | 
						|
		panic("cannot make a valid config without a pointer to a certificate cache")
 | 
						|
	}
 | 
						|
 | 
						|
	if cfg.OnDemand == nil {
 | 
						|
		cfg.OnDemand = Default.OnDemand
 | 
						|
	}
 | 
						|
	if cfg.RenewalWindowRatio == 0 {
 | 
						|
		cfg.RenewalWindowRatio = Default.RenewalWindowRatio
 | 
						|
	}
 | 
						|
	if cfg.OnEvent == nil {
 | 
						|
		cfg.OnEvent = Default.OnEvent
 | 
						|
	}
 | 
						|
	if cfg.KeySource == nil {
 | 
						|
		cfg.KeySource = Default.KeySource
 | 
						|
	}
 | 
						|
	if cfg.DefaultServerName == "" {
 | 
						|
		cfg.DefaultServerName = Default.DefaultServerName
 | 
						|
	}
 | 
						|
	if cfg.OnDemand == nil {
 | 
						|
		cfg.OnDemand = Default.OnDemand
 | 
						|
	}
 | 
						|
	if !cfg.MustStaple {
 | 
						|
		cfg.MustStaple = Default.MustStaple
 | 
						|
	}
 | 
						|
	if cfg.Storage == nil {
 | 
						|
		cfg.Storage = Default.Storage
 | 
						|
	}
 | 
						|
	if len(cfg.Issuers) == 0 {
 | 
						|
		cfg.Issuers = Default.Issuers
 | 
						|
		if len(cfg.Issuers) == 0 {
 | 
						|
			// at least one issuer is absolutely required
 | 
						|
			cfg.Issuers = []Issuer{NewACMEManager(&cfg, DefaultACME)}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// absolutely don't allow a nil storage,
 | 
						|
	// because that would make almost anything
 | 
						|
	// a config can do pointless
 | 
						|
	if cfg.Storage == nil {
 | 
						|
		cfg.Storage = defaultFileStorage
 | 
						|
	}
 | 
						|
 | 
						|
	cfg.certCache = certCache
 | 
						|
 | 
						|
	return &cfg
 | 
						|
}
 | 
						|
 | 
						|
// ManageSync causes the certificates for domainNames to be managed
 | 
						|
// according to cfg. If cfg.OnDemand is not nil, then this simply
 | 
						|
// whitelists the domain names and defers the certificate operations
 | 
						|
// to when they are needed. Otherwise, the certificates for each
 | 
						|
// name are loaded from storage or obtained from the CA. If loaded
 | 
						|
// from storage, they are renewed if they are expiring or expired.
 | 
						|
// It then caches the certificate in memory and is prepared to serve
 | 
						|
// them up during TLS handshakes.
 | 
						|
//
 | 
						|
// Note that name whitelisting for on-demand management only takes
 | 
						|
// effect if cfg.OnDemand.DecisionFunc is not set (is nil); it will
 | 
						|
// not overwrite an existing DecisionFunc, nor will it overwrite
 | 
						|
// its decision; i.e. the implicit whitelist is only used if no
 | 
						|
// DecisionFunc is set.
 | 
						|
//
 | 
						|
// This method is synchronous, meaning that certificates for all
 | 
						|
// domainNames must be successfully obtained (or renewed) before
 | 
						|
// it returns. It returns immediately on the first error for any
 | 
						|
// of the given domainNames. This behavior is recommended for
 | 
						|
// interactive use (i.e. when an administrator is present) so
 | 
						|
// that errors can be reported and fixed immediately.
 | 
						|
func (cfg *Config) ManageSync(domainNames []string) error {
 | 
						|
	return cfg.manageAll(context.Background(), domainNames, false)
 | 
						|
}
 | 
						|
 | 
						|
// ClientCredentials returns a list of TLS client certificate chains for the given identifiers.
 | 
						|
// The return value can be used in a tls.Config to enable client authentication using managed certificates.
 | 
						|
// Any certificates that need to be obtained or renewed for these identifiers will be managed accordingly.
 | 
						|
func (cfg *Config) ClientCredentials(ctx context.Context, identifiers []string) ([]tls.Certificate, error) {
 | 
						|
	err := cfg.manageAll(ctx, identifiers, false)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var chains []tls.Certificate
 | 
						|
	for _, id := range identifiers {
 | 
						|
		certRes, err := cfg.loadCertResourceAnyIssuer(id)
 | 
						|
		if err != nil {
 | 
						|
			return chains, err
 | 
						|
		}
 | 
						|
		chain, err := tls.X509KeyPair(certRes.CertificatePEM, certRes.PrivateKeyPEM)
 | 
						|
		if err != nil {
 | 
						|
			return chains, err
 | 
						|
		}
 | 
						|
		chains = append(chains, chain)
 | 
						|
	}
 | 
						|
	return chains, nil
 | 
						|
}
 | 
						|
 | 
						|
// ManageAsync is the same as ManageSync, except that ACME
 | 
						|
// operations are performed asynchronously (in the background).
 | 
						|
// This method returns before certificates are ready. It is
 | 
						|
// crucial that the administrator monitors the logs and is
 | 
						|
// notified of any errors so that corrective action can be
 | 
						|
// taken as soon as possible. Any errors returned from this
 | 
						|
// method occurred before ACME transactions started.
 | 
						|
//
 | 
						|
// As long as logs are monitored, this method is typically
 | 
						|
// recommended for non-interactive environments.
 | 
						|
//
 | 
						|
// If there are failures loading, obtaining, or renewing a
 | 
						|
// certificate, it will be retried with exponential backoff
 | 
						|
// for up to about 30 days, with a maximum interval of about
 | 
						|
// 24 hours. Cancelling ctx will cancel retries and shut down
 | 
						|
// any goroutines spawned by ManageAsync.
 | 
						|
func (cfg *Config) ManageAsync(ctx context.Context, domainNames []string) error {
 | 
						|
	return cfg.manageAll(ctx, domainNames, true)
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bool) error {
 | 
						|
	if ctx == nil {
 | 
						|
		ctx = context.Background()
 | 
						|
	}
 | 
						|
 | 
						|
	for _, domainName := range domainNames {
 | 
						|
		// if on-demand is configured, defer obtain and renew operations
 | 
						|
		if cfg.OnDemand != nil {
 | 
						|
			if !cfg.OnDemand.whitelistContains(domainName) {
 | 
						|
				cfg.OnDemand.hostWhitelist = append(cfg.OnDemand.hostWhitelist, domainName)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// otherwise, begin management immediately
 | 
						|
		err := cfg.manageOne(ctx, domainName, async)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) error {
 | 
						|
	// first try loading existing certificate from storage
 | 
						|
	cert, err := cfg.CacheManagedCertificate(domainName)
 | 
						|
	if err != nil {
 | 
						|
		if _, ok := err.(ErrNotExist); !ok {
 | 
						|
			return fmt.Errorf("%s: caching certificate: %v", domainName, err)
 | 
						|
		}
 | 
						|
		// if we don't have one in storage, obtain one
 | 
						|
		obtain := func() error {
 | 
						|
			var err error
 | 
						|
			if async {
 | 
						|
				err = cfg.ObtainCertAsync(ctx, domainName)
 | 
						|
			} else {
 | 
						|
				err = cfg.ObtainCertSync(ctx, domainName)
 | 
						|
			}
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("%s: obtaining certificate: %w", domainName, err)
 | 
						|
			}
 | 
						|
			cert, err = cfg.CacheManagedCertificate(domainName)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("%s: caching certificate after obtaining it: %v", domainName, err)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if async {
 | 
						|
			// Leave the job name empty so as to allow duplicate 'obtain'
 | 
						|
			// jobs; this is because Caddy calls ManageAsync() before the
 | 
						|
			// previous config is stopped (and before its context is
 | 
						|
			// canceled), which means that if an obtain job is still
 | 
						|
			// running for the same domain, Submit() would not queue the
 | 
						|
			// new one because it is still running, even though it is
 | 
						|
			// (probably) about to be canceled (it might not if the new
 | 
						|
			// config fails to finish loading, however). In any case, we
 | 
						|
			// presume it is safe to enqueue a duplicate obtain job because
 | 
						|
			// either the old one (or sometimes the new one) is about to be
 | 
						|
			// canceled. This seems like reasonable logic for any consumer
 | 
						|
			// of this lib. See https://github.com/caddyserver/caddy/issues/3202
 | 
						|
			jm.Submit(cfg.Logger, "", obtain)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return obtain()
 | 
						|
	}
 | 
						|
 | 
						|
	// for an existing certificate, make sure it is renewed
 | 
						|
	renew := func() error {
 | 
						|
		var err error
 | 
						|
		if async {
 | 
						|
			err = cfg.RenewCertAsync(ctx, domainName, false)
 | 
						|
		} else {
 | 
						|
			err = cfg.RenewCertSync(ctx, domainName, false)
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("%s: renewing certificate: %w", domainName, err)
 | 
						|
		}
 | 
						|
		// successful renewal, so update in-memory cache
 | 
						|
		err = cfg.reloadManagedCertificate(cert)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("%s: reloading renewed certificate into memory: %v", domainName, err)
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	if cert.NeedsRenewal(cfg) {
 | 
						|
		if async {
 | 
						|
			jm.Submit(cfg.Logger, "renew_"+domainName, renew)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		return renew()
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Unmanage causes the certificates for domainNames to stop being managed.
 | 
						|
// If there are certificates for the supplied domain names in the cache, they
 | 
						|
// are evicted from the cache.
 | 
						|
func (cfg *Config) Unmanage(domainNames []string) {
 | 
						|
	var deleteQueue []Certificate
 | 
						|
	for _, domainName := range domainNames {
 | 
						|
		certs := cfg.certCache.AllMatchingCertificates(domainName)
 | 
						|
		for _, cert := range certs {
 | 
						|
			if !cert.managed {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			deleteQueue = append(deleteQueue, cert)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	cfg.certCache.mu.Lock()
 | 
						|
	for _, cert := range deleteQueue {
 | 
						|
		cfg.certCache.removeCertificate(cert)
 | 
						|
	}
 | 
						|
	cfg.certCache.mu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// ObtainCertSync generates a new private key and obtains a certificate for
 | 
						|
// name using cfg in the foreground; i.e. interactively and without retries.
 | 
						|
// It stows the renewed certificate and its assets in storage if successful.
 | 
						|
// It DOES NOT load the certificate into the in-memory cache. This method
 | 
						|
// is a no-op if storage already has a certificate for name.
 | 
						|
func (cfg *Config) ObtainCertSync(ctx context.Context, name string) error {
 | 
						|
	return cfg.obtainCert(ctx, name, true)
 | 
						|
}
 | 
						|
 | 
						|
// ObtainCertAsync is the same as ObtainCertSync(), except it runs in the
 | 
						|
// background; i.e. non-interactively, and with retries if it fails.
 | 
						|
func (cfg *Config) ObtainCertAsync(ctx context.Context, name string) error {
 | 
						|
	return cfg.obtainCert(ctx, name, false)
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool) error {
 | 
						|
	if len(cfg.Issuers) == 0 {
 | 
						|
		return fmt.Errorf("no issuers configured; impossible to obtain or check for existing certificate in storage")
 | 
						|
	}
 | 
						|
 | 
						|
	// if storage has all resources for this certificate, obtain is a no-op
 | 
						|
	if cfg.storageHasCertResourcesAnyIssuer(name) {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure storage is writeable and readable
 | 
						|
	// TODO: this is not necessary every time; should only perform check once every so often for each storage, which may require some global state...
 | 
						|
	err := cfg.checkStorage()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
 | 
						|
	}
 | 
						|
 | 
						|
	log := loggerNamed(cfg.Logger, "obtain")
 | 
						|
 | 
						|
	if log != nil {
 | 
						|
		log.Info("acquiring lock", zap.String("identifier", name))
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure idempotency of the obtain operation for this name
 | 
						|
	lockKey := cfg.lockKey(certIssueLockOp, name)
 | 
						|
	err = acquireLock(ctx, cfg.Storage, lockKey)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if log != nil {
 | 
						|
			log.Info("releasing lock", zap.String("identifier", name))
 | 
						|
		}
 | 
						|
		if err := releaseLock(cfg.Storage, lockKey); err != nil {
 | 
						|
			if log != nil {
 | 
						|
				log.Error("unable to unlock",
 | 
						|
					zap.String("identifier", name),
 | 
						|
					zap.String("lock_key", lockKey),
 | 
						|
					zap.Error(err))
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	if log != nil {
 | 
						|
		log.Info("lock acquired", zap.String("identifier", name))
 | 
						|
	}
 | 
						|
 | 
						|
	f := func(ctx context.Context) error {
 | 
						|
		// check if obtain is still needed -- might have been obtained during lock
 | 
						|
		if cfg.storageHasCertResourcesAnyIssuer(name) {
 | 
						|
			if log != nil {
 | 
						|
				log.Info("certificate already exists in storage", zap.String("identifier", name))
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// if storage has a private key already, use it; otherwise,
 | 
						|
		// we'll generate our own
 | 
						|
		privKey, privKeyPEM, issuers, err := cfg.reusePrivateKey(name)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if privKey == nil {
 | 
						|
			privKey, err = cfg.KeySource.GenerateKey()
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			privKeyPEM, err = encodePrivateKey(privKey)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		csr, err := cfg.generateCSR(privKey, []string{name})
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// try to obtain from each issuer until we succeed
 | 
						|
		var issuedCert *IssuedCertificate
 | 
						|
		var issuerUsed Issuer
 | 
						|
		for i, issuer := range issuers {
 | 
						|
			if log != nil {
 | 
						|
				log.Debug(fmt.Sprintf("trying issuer %d/%d", i+1, len(cfg.Issuers)),
 | 
						|
					zap.String("issuer", issuer.IssuerKey()))
 | 
						|
			}
 | 
						|
 | 
						|
			if prechecker, ok := issuer.(PreChecker); ok {
 | 
						|
				err = prechecker.PreCheck(ctx, []string{name}, interactive)
 | 
						|
				if err != nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			issuedCert, err = issuer.Issue(ctx, csr)
 | 
						|
			if err == nil {
 | 
						|
				issuerUsed = issuer
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			// err is usually wrapped, which is nice for simply printing it, but
 | 
						|
			// with our structured error logs we only need the problem string
 | 
						|
			errToLog := err
 | 
						|
			var problem acme.Problem
 | 
						|
			if errors.As(err, &problem) {
 | 
						|
				errToLog = problem
 | 
						|
			}
 | 
						|
			if log != nil {
 | 
						|
				log.Error("could not get certificate from issuer",
 | 
						|
					zap.String("identifier", name),
 | 
						|
					zap.String("issuer", issuer.IssuerKey()),
 | 
						|
					zap.Error(errToLog))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			// only the error from the last issuer will be returned, but we logged the others
 | 
						|
			return fmt.Errorf("[%s] Obtain: %w", name, err)
 | 
						|
		}
 | 
						|
 | 
						|
		// success - immediately save the certificate resource
 | 
						|
		certRes := CertificateResource{
 | 
						|
			SANs:           namesFromCSR(csr),
 | 
						|
			CertificatePEM: issuedCert.Certificate,
 | 
						|
			PrivateKeyPEM:  privKeyPEM,
 | 
						|
			IssuerData:     issuedCert.Metadata,
 | 
						|
		}
 | 
						|
		err = cfg.saveCertResource(issuerUsed, certRes)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
 | 
						|
		}
 | 
						|
 | 
						|
		cfg.emit("cert_obtained", name)
 | 
						|
 | 
						|
		if log != nil {
 | 
						|
			log.Info("certificate obtained successfully", zap.String("identifier", name))
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if interactive {
 | 
						|
		err = f(ctx)
 | 
						|
	} else {
 | 
						|
		err = doWithRetry(ctx, log, f)
 | 
						|
	}
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// reusePrivateKey looks for a private key for domain in storage in the configured issuers
 | 
						|
// paths. For the first private key it finds, it returns that key both decoded and PEM-encoded,
 | 
						|
// as well as the reordered list of issuers to use instead of cfg.Issuers (because if a key
 | 
						|
// is found, that issuer should be tried first, so it is moved to the front in a copy of
 | 
						|
// cfg.Issuers).
 | 
						|
func (cfg *Config) reusePrivateKey(domain string) (privKey crypto.PrivateKey, privKeyPEM []byte, issuers []Issuer, err error) {
 | 
						|
	// make a copy of cfg.Issuers so that if we have to reorder elements, we don't
 | 
						|
	// inadvertently mutate the configured issuers (see append calls below)
 | 
						|
	issuers = make([]Issuer, len(cfg.Issuers))
 | 
						|
	copy(issuers, cfg.Issuers)
 | 
						|
 | 
						|
	for i, issuer := range issuers {
 | 
						|
		// see if this issuer location in storage has a private key for the domain
 | 
						|
		privateKeyStorageKey := StorageKeys.SitePrivateKey(issuer.IssuerKey(), domain)
 | 
						|
		privKeyPEM, err = cfg.Storage.Load(privateKeyStorageKey)
 | 
						|
		if _, ok := err.(ErrNotExist); ok {
 | 
						|
			err = nil // obviously, it's OK to not have a private key; so don't prevent obtaining a cert
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return nil, nil, nil, fmt.Errorf("loading existing private key for reuse with issuer %s: %v", issuer.IssuerKey(), err)
 | 
						|
		}
 | 
						|
 | 
						|
		// we loaded a private key; try decoding it so we can use it
 | 
						|
		privKey, err = decodePrivateKey(privKeyPEM)
 | 
						|
		if err != nil {
 | 
						|
			return nil, nil, nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		// since the private key was found in storage for this issuer, move it
 | 
						|
		// to the front of the list so we prefer this issuer first
 | 
						|
		issuers = append([]Issuer{issuer}, append(issuers[:i], issuers[i+1:]...)...)
 | 
						|
		break
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// storageHasCertResourcesAnyIssuer returns true if storage has all the
 | 
						|
// certificate resources in storage from any configured issuer. It checks
 | 
						|
// all configured issuers in order.
 | 
						|
func (cfg *Config) storageHasCertResourcesAnyIssuer(name string) bool {
 | 
						|
	for _, iss := range cfg.Issuers {
 | 
						|
		if cfg.storageHasCertResources(iss, name) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// RenewCertSync renews the certificate for name using cfg in the foreground;
 | 
						|
// i.e. interactively and without retries. It stows the renewed certificate
 | 
						|
// and its assets in storage if successful. It DOES NOT update the in-memory
 | 
						|
// cache with the new certificate. The certificate will not be renewed if it
 | 
						|
// is not close to expiring unless force is true.
 | 
						|
//
 | 
						|
// Renewing a certificate is the same as obtaining a certificate, except that
 | 
						|
// the existing private key already in storage is reused.
 | 
						|
func (cfg *Config) RenewCertSync(ctx context.Context, name string, force bool) error {
 | 
						|
	return cfg.renewCert(ctx, name, force, true)
 | 
						|
}
 | 
						|
 | 
						|
// RenewCertAsync is the same as RenewCertSync(), except it runs in the
 | 
						|
// background; i.e. non-interactively, and with retries if it fails.
 | 
						|
func (cfg *Config) RenewCertAsync(ctx context.Context, name string, force bool) error {
 | 
						|
	return cfg.renewCert(ctx, name, force, false)
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) renewCert(ctx context.Context, name string, force, interactive bool) error {
 | 
						|
	if len(cfg.Issuers) == 0 {
 | 
						|
		return fmt.Errorf("no issuers configured; impossible to renew or check existing certificate in storage")
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure storage is writeable and readable
 | 
						|
	// TODO: this is not necessary every time; should only perform check once every so often for each storage, which may require some global state...
 | 
						|
	err := cfg.checkStorage()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
 | 
						|
	}
 | 
						|
 | 
						|
	log := loggerNamed(cfg.Logger, "renew")
 | 
						|
 | 
						|
	if log != nil {
 | 
						|
		log.Info("acquiring lock", zap.String("identifier", name))
 | 
						|
	}
 | 
						|
 | 
						|
	// ensure idempotency of the renew operation for this name
 | 
						|
	lockKey := cfg.lockKey(certIssueLockOp, name)
 | 
						|
	err = acquireLock(ctx, cfg.Storage, lockKey)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if log != nil {
 | 
						|
			log.Info("releasing lock", zap.String("identifier", name))
 | 
						|
		}
 | 
						|
		if err := releaseLock(cfg.Storage, lockKey); err != nil {
 | 
						|
			if log != nil {
 | 
						|
				log.Error("unable to unlock",
 | 
						|
					zap.String("identifier", name),
 | 
						|
					zap.String("lock_key", lockKey),
 | 
						|
					zap.Error(err))
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	if log != nil {
 | 
						|
		log.Info("lock acquired", zap.String("identifier", name))
 | 
						|
	}
 | 
						|
 | 
						|
	f := func(ctx context.Context) error {
 | 
						|
		// prepare for renewal (load PEM cert, key, and meta)
 | 
						|
		certRes, err := cfg.loadCertResourceAnyIssuer(name)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// check if renew is still needed - might have been renewed while waiting for lock
 | 
						|
		timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
 | 
						|
		if !needsRenew {
 | 
						|
			if force {
 | 
						|
				if log != nil {
 | 
						|
					log.Info("certificate does not need to be renewed, but renewal is being forced",
 | 
						|
						zap.String("identifier", name),
 | 
						|
						zap.Duration("remaining", timeLeft))
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if log != nil {
 | 
						|
					log.Info("certificate appears to have been renewed already",
 | 
						|
						zap.String("identifier", name),
 | 
						|
						zap.Duration("remaining", timeLeft))
 | 
						|
				}
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if log != nil {
 | 
						|
			log.Info("renewing certificate",
 | 
						|
				zap.String("identifier", name),
 | 
						|
				zap.Duration("remaining", timeLeft))
 | 
						|
		}
 | 
						|
 | 
						|
		privateKey, err := decodePrivateKey(certRes.PrivateKeyPEM)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		csr, err := cfg.generateCSR(privateKey, []string{name})
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// try to obtain from each issuer until we succeed
 | 
						|
		var issuedCert *IssuedCertificate
 | 
						|
		var issuerUsed Issuer
 | 
						|
		for _, issuer := range cfg.Issuers {
 | 
						|
			if prechecker, ok := issuer.(PreChecker); ok {
 | 
						|
				err = prechecker.PreCheck(ctx, []string{name}, interactive)
 | 
						|
				if err != nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			issuedCert, err = issuer.Issue(ctx, csr)
 | 
						|
			if err == nil {
 | 
						|
				issuerUsed = issuer
 | 
						|
				break
 | 
						|
			}
 | 
						|
 | 
						|
			// err is usually wrapped, which is nice for simply printing it, but
 | 
						|
			// with our structured error logs we only need the problem string
 | 
						|
			errToLog := err
 | 
						|
			var problem acme.Problem
 | 
						|
			if errors.As(err, &problem) {
 | 
						|
				errToLog = problem
 | 
						|
			}
 | 
						|
			if log != nil {
 | 
						|
				log.Error("could not get certificate from issuer",
 | 
						|
					zap.String("identifier", name),
 | 
						|
					zap.String("issuer", issuer.IssuerKey()),
 | 
						|
					zap.Error(errToLog))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			// only the error from the last issuer will be returned, but we logged the others
 | 
						|
			return fmt.Errorf("[%s] Renew: %w", name, err)
 | 
						|
		}
 | 
						|
 | 
						|
		// success - immediately save the renewed certificate resource
 | 
						|
		newCertRes := CertificateResource{
 | 
						|
			SANs:           namesFromCSR(csr),
 | 
						|
			CertificatePEM: issuedCert.Certificate,
 | 
						|
			PrivateKeyPEM:  certRes.PrivateKeyPEM,
 | 
						|
			IssuerData:     issuedCert.Metadata,
 | 
						|
		}
 | 
						|
		err = cfg.saveCertResource(issuerUsed, newCertRes)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
 | 
						|
		}
 | 
						|
 | 
						|
		cfg.emit("cert_renewed", name)
 | 
						|
 | 
						|
		if log != nil {
 | 
						|
			log.Info("certificate renewed successfully", zap.String("identifier", name))
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if interactive {
 | 
						|
		err = f(ctx)
 | 
						|
	} else {
 | 
						|
		err = doWithRetry(ctx, log, f)
 | 
						|
	}
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string) (*x509.CertificateRequest, error) {
 | 
						|
	csrTemplate := new(x509.CertificateRequest)
 | 
						|
 | 
						|
	for _, name := range sans {
 | 
						|
		if ip := net.ParseIP(name); ip != nil {
 | 
						|
			csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip)
 | 
						|
		} else if strings.Contains(name, "@") {
 | 
						|
			csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name)
 | 
						|
		} else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") {
 | 
						|
			csrTemplate.URIs = append(csrTemplate.URIs, u)
 | 
						|
		} else {
 | 
						|
			// convert IDNs to ASCII according to RFC 5280 section 7
 | 
						|
			normalizedName, err := idna.ToASCII(name)
 | 
						|
			if err != nil {
 | 
						|
				return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err)
 | 
						|
			}
 | 
						|
			csrTemplate.DNSNames = append(csrTemplate.DNSNames, normalizedName)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if cfg.MustStaple {
 | 
						|
		csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, mustStapleExtension)
 | 
						|
	}
 | 
						|
 | 
						|
	csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privateKey)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return x509.ParseCertificateRequest(csrDER)
 | 
						|
}
 | 
						|
 | 
						|
// RevokeCert revokes the certificate for domain via ACME protocol. It requires
 | 
						|
// that cfg.Issuers is properly configured with the same issuer that issued the
 | 
						|
// certificate being revoked. See RFC 5280 §5.3.1 for reason codes.
 | 
						|
//
 | 
						|
// The certificate assets are deleted from storage after successful revocation
 | 
						|
// to prevent reuse.
 | 
						|
func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, interactive bool) error {
 | 
						|
	for i, issuer := range cfg.Issuers {
 | 
						|
		issuerKey := issuer.IssuerKey()
 | 
						|
 | 
						|
		rev, ok := issuer.(Revoker)
 | 
						|
		if !ok {
 | 
						|
			return fmt.Errorf("issuer %d (%s) is not a Revoker", i, issuerKey)
 | 
						|
		}
 | 
						|
 | 
						|
		certRes, err := cfg.loadCertResource(issuer, domain)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if !cfg.Storage.Exists(StorageKeys.SitePrivateKey(issuerKey, domain)) {
 | 
						|
			return fmt.Errorf("private key not found for %s", certRes.SANs)
 | 
						|
		}
 | 
						|
 | 
						|
		err = rev.Revoke(ctx, certRes, reason)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err)
 | 
						|
		}
 | 
						|
 | 
						|
		cfg.emit("cert_revoked", domain)
 | 
						|
 | 
						|
		err = cfg.deleteSiteAssets(issuerKey, domain)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("certificate revoked, but unable to fully clean up assets from issuer %s: %v", issuerKey, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// TLSConfig is an opinionated method that returns a
 | 
						|
// recommended, modern TLS configuration that can be
 | 
						|
// used to configure TLS listeners, which also supports
 | 
						|
// the TLS-ALPN challenge and serves up certificates
 | 
						|
// managed by cfg.
 | 
						|
//
 | 
						|
// Unlike the package TLS() function, this method does
 | 
						|
// not, by itself, enable certificate management for
 | 
						|
// any domain names.
 | 
						|
//
 | 
						|
// Feel free to further customize the returned tls.Config,
 | 
						|
// but do not mess with the GetCertificate or NextProtos
 | 
						|
// fields unless you know what you're doing, as they're
 | 
						|
// necessary to solve the TLS-ALPN challenge.
 | 
						|
func (cfg *Config) TLSConfig() *tls.Config {
 | 
						|
	return &tls.Config{
 | 
						|
		// these two fields necessary for TLS-ALPN challenge
 | 
						|
		GetCertificate: cfg.GetCertificate,
 | 
						|
		NextProtos:     []string{acmez.ACMETLS1Protocol},
 | 
						|
 | 
						|
		// the rest recommended for modern TLS servers
 | 
						|
		MinVersion: tls.VersionTLS12,
 | 
						|
		CurvePreferences: []tls.CurveID{
 | 
						|
			tls.X25519,
 | 
						|
			tls.CurveP256,
 | 
						|
		},
 | 
						|
		CipherSuites:             preferredDefaultCipherSuites(),
 | 
						|
		PreferServerCipherSuites: true,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// getChallengeInfo loads the challenge info from either the internal challenge memory
 | 
						|
// or the external storage (implying distributed solving). The second return value
 | 
						|
// indicates whether challenge info was loaded from external storage. If true, the
 | 
						|
// challenge is being solved in a distributed fashion; if false, from internal memory.
 | 
						|
// If no matching challenge information can be found, an error is returned.
 | 
						|
func (cfg *Config) getChallengeInfo(identifier string) (Challenge, bool, error) {
 | 
						|
	// first, check if our process initiated this challenge; if so, just return it
 | 
						|
	chalData, ok := GetACMEChallenge(identifier)
 | 
						|
	if ok {
 | 
						|
		return chalData, false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// otherwise, perhaps another instance in the cluster initiated it; check
 | 
						|
	// the configured storage to retrieve challenge data
 | 
						|
 | 
						|
	var chalInfo acme.Challenge
 | 
						|
	var chalInfoBytes []byte
 | 
						|
	var tokenKey string
 | 
						|
	for _, issuer := range cfg.Issuers {
 | 
						|
		ds := distributedSolver{
 | 
						|
			storage:                cfg.Storage,
 | 
						|
			storageKeyIssuerPrefix: storageKeyACMECAPrefix(issuer.IssuerKey()),
 | 
						|
		}
 | 
						|
		tokenKey = ds.challengeTokensKey(identifier)
 | 
						|
		var err error
 | 
						|
		chalInfoBytes, err = cfg.Storage.Load(tokenKey)
 | 
						|
		if err == nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if _, ok := err.(ErrNotExist); ok {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return Challenge{}, false, fmt.Errorf("opening distributed challenge token file %s: %v", tokenKey, err)
 | 
						|
	}
 | 
						|
	if len(chalInfoBytes) == 0 {
 | 
						|
		return Challenge{}, false, fmt.Errorf("no information found to solve challenge for identifier: %s", identifier)
 | 
						|
	}
 | 
						|
 | 
						|
	err := json.Unmarshal(chalInfoBytes, &chalInfo)
 | 
						|
	if err != nil {
 | 
						|
		return Challenge{}, false, fmt.Errorf("decoding challenge token file %s (corrupted?): %v", tokenKey, err)
 | 
						|
	}
 | 
						|
 | 
						|
	return Challenge{Challenge: chalInfo}, true, nil
 | 
						|
}
 | 
						|
 | 
						|
// checkStorage tests the storage by writing random bytes
 | 
						|
// to a random key, and then loading those bytes and
 | 
						|
// comparing the loaded value. If this fails, the provided
 | 
						|
// cfg.Storage mechanism should not be used.
 | 
						|
func (cfg *Config) checkStorage() error {
 | 
						|
	key := fmt.Sprintf("rw_test_%d", weakrand.Int())
 | 
						|
	contents := make([]byte, 1024*10) // size sufficient for one or two ACME resources
 | 
						|
	_, err := weakrand.Read(contents)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	err = cfg.Storage.Store(key, contents)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		deleteErr := cfg.Storage.Delete(key)
 | 
						|
		if deleteErr != nil {
 | 
						|
			if cfg.Logger != nil {
 | 
						|
				cfg.Logger.Error("deleting test key from storage",
 | 
						|
					zap.String("key", key), zap.Error(err))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		// if there was no other error, make sure
 | 
						|
		// to return any error returned from Delete
 | 
						|
		if err == nil {
 | 
						|
			err = deleteErr
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	loaded, err := cfg.Storage.Load(key)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !bytes.Equal(contents, loaded) {
 | 
						|
		return fmt.Errorf("load yielded different value than was stored; expected %d bytes, got %d bytes of differing elements", len(contents), len(loaded))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// storageHasCertResources returns true if the storage
 | 
						|
// associated with cfg's certificate cache has all the
 | 
						|
// resources related to the certificate for domain: the
 | 
						|
// certificate, the private key, and the metadata.
 | 
						|
func (cfg *Config) storageHasCertResources(issuer Issuer, domain string) bool {
 | 
						|
	issuerKey := issuer.IssuerKey()
 | 
						|
	certKey := StorageKeys.SiteCert(issuerKey, domain)
 | 
						|
	keyKey := StorageKeys.SitePrivateKey(issuerKey, domain)
 | 
						|
	metaKey := StorageKeys.SiteMeta(issuerKey, domain)
 | 
						|
	return cfg.Storage.Exists(certKey) &&
 | 
						|
		cfg.Storage.Exists(keyKey) &&
 | 
						|
		cfg.Storage.Exists(metaKey)
 | 
						|
}
 | 
						|
 | 
						|
// deleteSiteAssets deletes the folder in storage containing the
 | 
						|
// certificate, private key, and metadata file for domain from the
 | 
						|
// issuer with the given issuer key.
 | 
						|
func (cfg *Config) deleteSiteAssets(issuerKey, domain string) error {
 | 
						|
	err := cfg.Storage.Delete(StorageKeys.SiteCert(issuerKey, domain))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("deleting certificate file: %v", err)
 | 
						|
	}
 | 
						|
	err = cfg.Storage.Delete(StorageKeys.SitePrivateKey(issuerKey, domain))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("deleting private key: %v", err)
 | 
						|
	}
 | 
						|
	err = cfg.Storage.Delete(StorageKeys.SiteMeta(issuerKey, domain))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("deleting metadata file: %v", err)
 | 
						|
	}
 | 
						|
	err = cfg.Storage.Delete(StorageKeys.CertsSitePrefix(issuerKey, domain))
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("deleting site asset folder: %v", err)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// lockKey returns a key for a lock that is specific to the operation
 | 
						|
// named op being performed related to domainName and this config's CA.
 | 
						|
func (cfg *Config) lockKey(op, domainName string) string {
 | 
						|
	return fmt.Sprintf("%s_%s", op, domainName)
 | 
						|
}
 | 
						|
 | 
						|
// managedCertNeedsRenewal returns true if certRes is expiring soon or already expired,
 | 
						|
// or if the process of decoding the cert and checking its expiration returned an error.
 | 
						|
func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Duration, bool) {
 | 
						|
	certChain, err := parseCertsFromPEMBundle(certRes.CertificatePEM)
 | 
						|
	if err != nil {
 | 
						|
		return 0, true
 | 
						|
	}
 | 
						|
	remaining := time.Until(certChain[0].NotAfter)
 | 
						|
	needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, certChain[0].NotAfter, cfg.RenewalWindowRatio)
 | 
						|
	return remaining, needsRenew
 | 
						|
}
 | 
						|
 | 
						|
func (cfg *Config) emit(eventName string, data interface{}) {
 | 
						|
	if cfg.OnEvent == nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	cfg.OnEvent(eventName, data)
 | 
						|
}
 | 
						|
 | 
						|
func loggerNamed(l *zap.Logger, name string) *zap.Logger {
 | 
						|
	if l == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return l.Named(name)
 | 
						|
}
 | 
						|
 | 
						|
// CertificateSelector is a type which can select a certificate to use given multiple choices.
 | 
						|
type CertificateSelector interface {
 | 
						|
	SelectCertificate(*tls.ClientHelloInfo, []Certificate) (Certificate, error)
 | 
						|
}
 | 
						|
 | 
						|
// OCSPConfig configures how OCSP is handled.
 | 
						|
type OCSPConfig struct {
 | 
						|
	// Disable automatic OCSP stapling; strongly
 | 
						|
	// discouraged unless you have a good reason.
 | 
						|
	// Disabling this puts clients at greater risk
 | 
						|
	// and reduces their privacy.
 | 
						|
	DisableStapling bool
 | 
						|
 | 
						|
	// A map of OCSP responder domains to replacement
 | 
						|
	// domains for querying OCSP servers. Used for
 | 
						|
	// overriding the OCSP responder URL that is
 | 
						|
	// embedded in certificates. Mapping to an empty
 | 
						|
	// URL will disable OCSP from that responder.
 | 
						|
	ResponderOverrides map[string]string
 | 
						|
}
 | 
						|
 | 
						|
// certIssueLockOp is the name of the operation used
 | 
						|
// when naming a lock to make it mutually exclusive
 | 
						|
// with other certificate issuance operations for a
 | 
						|
// certain name.
 | 
						|
const certIssueLockOp = "issue_cert"
 | 
						|
 | 
						|
// Constants for PKIX MustStaple extension.
 | 
						|
var (
 | 
						|
	tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
 | 
						|
	ocspMustStapleFeature  = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
 | 
						|
	mustStapleExtension    = pkix.Extension{
 | 
						|
		Id:    tlsFeatureExtensionOID,
 | 
						|
		Value: ocspMustStapleFeature,
 | 
						|
	}
 | 
						|
)
 |