mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-26 20:11:02 +00:00 
			
		
		
		
	* use certmagic for more extensible/robust ACME cert handling * accept TOS based on config option Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			326 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			326 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| // 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 (
 | |
| 	"fmt"
 | |
| 	weakrand "math/rand" // seeded elsewhere
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"go.uber.org/zap"
 | |
| )
 | |
| 
 | |
| // Cache is a structure that stores certificates in memory.
 | |
| // A Cache indexes certificates by name for quick access
 | |
| // during TLS handshakes, and avoids duplicating certificates
 | |
| // in memory. Generally, there should only be one per process.
 | |
| // However, that is not a strict requirement; but using more
 | |
| // than one is a code smell, and may indicate an
 | |
| // over-engineered design.
 | |
| //
 | |
| // An empty cache is INVALID and must not be used. Be sure
 | |
| // to call NewCache to get a valid value.
 | |
| //
 | |
| // These should be very long-lived values and must not be
 | |
| // copied. Before all references leave scope to be garbage
 | |
| // collected, ensure you call Stop() to stop maintenance on
 | |
| // the certificates stored in this cache and release locks.
 | |
| //
 | |
| // Caches are not usually manipulated directly; create a
 | |
| // Config value with a pointer to a Cache, and then use
 | |
| // the Config to interact with the cache. Caches are
 | |
| // agnostic of any particular storage or ACME config,
 | |
| // since each certificate may be managed and stored
 | |
| // differently.
 | |
| type Cache struct {
 | |
| 	// User configuration of the cache
 | |
| 	options CacheOptions
 | |
| 
 | |
| 	// The cache is keyed by certificate hash
 | |
| 	cache map[string]Certificate
 | |
| 
 | |
| 	// cacheIndex is a map of SAN to cache key (cert hash)
 | |
| 	cacheIndex map[string][]string
 | |
| 
 | |
| 	// Protects the cache and index maps
 | |
| 	mu sync.RWMutex
 | |
| 
 | |
| 	// Close this channel to cancel asset maintenance
 | |
| 	stopChan chan struct{}
 | |
| 
 | |
| 	// Used to signal when stopping is completed
 | |
| 	doneChan chan struct{}
 | |
| 
 | |
| 	logger *zap.Logger
 | |
| }
 | |
| 
 | |
| // NewCache returns a new, valid Cache for efficiently
 | |
| // accessing certificates in memory. It also begins a
 | |
| // maintenance goroutine to tend to the certificates
 | |
| // in the cache. Call Stop() when you are done with the
 | |
| // cache so it can clean up locks and stuff.
 | |
| //
 | |
| // Most users of this package will not need to call this
 | |
| // because a default certificate cache is created for you.
 | |
| // Only advanced use cases require creating a new cache.
 | |
| //
 | |
| // This function panics if opts.GetConfigForCert is not
 | |
| // set. The reason is that a cache absolutely needs to
 | |
| // be able to get a Config with which to manage TLS
 | |
| // assets, and it is not safe to assume that the Default
 | |
| // config is always the correct one, since you have
 | |
| // created the cache yourself.
 | |
| //
 | |
| // See the godoc for Cache to use it properly. When
 | |
| // no longer needed, caches should be stopped with
 | |
| // Stop() to clean up resources even if the process
 | |
| // is being terminated, so that it can clean up
 | |
| // any locks for other processes to unblock!
 | |
| func NewCache(opts CacheOptions) *Cache {
 | |
| 	// assume default options if necessary
 | |
| 	if opts.OCSPCheckInterval <= 0 {
 | |
| 		opts.OCSPCheckInterval = DefaultOCSPCheckInterval
 | |
| 	}
 | |
| 	if opts.RenewCheckInterval <= 0 {
 | |
| 		opts.RenewCheckInterval = DefaultRenewCheckInterval
 | |
| 	}
 | |
| 	if opts.Capacity < 0 {
 | |
| 		opts.Capacity = 0
 | |
| 	}
 | |
| 
 | |
| 	// this must be set, because we cannot not
 | |
| 	// safely assume that the Default Config
 | |
| 	// is always the correct one to use
 | |
| 	if opts.GetConfigForCert == nil {
 | |
| 		panic("cache must be initialized with a GetConfigForCert callback")
 | |
| 	}
 | |
| 
 | |
| 	c := &Cache{
 | |
| 		options:    opts,
 | |
| 		cache:      make(map[string]Certificate),
 | |
| 		cacheIndex: make(map[string][]string),
 | |
| 		stopChan:   make(chan struct{}),
 | |
| 		doneChan:   make(chan struct{}),
 | |
| 		logger:     opts.Logger,
 | |
| 	}
 | |
| 
 | |
| 	go c.maintainAssets(0)
 | |
| 
 | |
| 	return c
 | |
| }
 | |
| 
 | |
| // Stop stops the maintenance goroutine for
 | |
| // certificates in certCache. It blocks until
 | |
| // stopping is complete. Once a cache is
 | |
| // stopped, it cannot be reused.
 | |
| func (certCache *Cache) Stop() {
 | |
| 	close(certCache.stopChan) // signal to stop
 | |
| 	<-certCache.doneChan      // wait for stop to complete
 | |
| }
 | |
| 
 | |
| // CacheOptions is used to configure certificate caches.
 | |
| // Once a cache has been created with certain options,
 | |
| // those settings cannot be changed.
 | |
| type CacheOptions struct {
 | |
| 	// REQUIRED. A function that returns a configuration
 | |
| 	// used for managing a certificate, or for accessing
 | |
| 	// that certificate's asset storage (e.g. for
 | |
| 	// OCSP staples, etc). The returned Config MUST
 | |
| 	// be associated with the same Cache as the caller.
 | |
| 	//
 | |
| 	// The reason this is a callback function, dynamically
 | |
| 	// returning a Config (instead of attaching a static
 | |
| 	// pointer to a Config on each certificate) is because
 | |
| 	// the config for how to manage a domain's certificate
 | |
| 	// might change from maintenance to maintenance. The
 | |
| 	// cache is so long-lived, we cannot assume that the
 | |
| 	// host's situation will always be the same; e.g. the
 | |
| 	// certificate might switch DNS providers, so the DNS
 | |
| 	// challenge (if used) would need to be adjusted from
 | |
| 	// the last time it was run ~8 weeks ago.
 | |
| 	GetConfigForCert ConfigGetter
 | |
| 
 | |
| 	// How often to check certificates for renewal;
 | |
| 	// if unset, DefaultOCSPCheckInterval will be used.
 | |
| 	OCSPCheckInterval time.Duration
 | |
| 
 | |
| 	// How often to check certificates for renewal;
 | |
| 	// if unset, DefaultRenewCheckInterval will be used.
 | |
| 	RenewCheckInterval time.Duration
 | |
| 
 | |
| 	// Maximum number of certificates to allow in the cache.
 | |
| 	// If reached, certificates will be randomly evicted to
 | |
| 	// make room for new ones. 0 means unlimited.
 | |
| 	Capacity int
 | |
| 
 | |
| 	// Set a logger to enable logging
 | |
| 	Logger *zap.Logger
 | |
| }
 | |
| 
 | |
| // ConfigGetter is a function that returns a prepared,
 | |
| // valid config that should be used when managing the
 | |
| // given certificate or its assets.
 | |
| type ConfigGetter func(Certificate) (*Config, error)
 | |
| 
 | |
| // cacheCertificate calls unsyncedCacheCertificate with a write lock.
 | |
| //
 | |
| // This function is safe for concurrent use.
 | |
| func (certCache *Cache) cacheCertificate(cert Certificate) {
 | |
| 	certCache.mu.Lock()
 | |
| 	certCache.unsyncedCacheCertificate(cert)
 | |
| 	certCache.mu.Unlock()
 | |
| }
 | |
| 
 | |
| // unsyncedCacheCertificate adds cert to the in-memory cache unless
 | |
| // it already exists in the cache (according to cert.Hash). It
 | |
| // updates the name index.
 | |
| //
 | |
| // This function is NOT safe for concurrent use. Callers MUST acquire
 | |
| // a write lock on certCache.mu first.
 | |
| func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
 | |
| 	// no-op if this certificate already exists in the cache
 | |
| 	if _, ok := certCache.cache[cert.hash]; ok {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// if the cache is at capacity, make room for new cert
 | |
| 	cacheSize := len(certCache.cache)
 | |
| 	if certCache.options.Capacity > 0 && cacheSize >= certCache.options.Capacity {
 | |
| 		// Go maps are "nondeterministic" but not actually random,
 | |
| 		// so although we could just chop off the "front" of the
 | |
| 		// map with less code, that is a heavily skewed eviction
 | |
| 		// strategy; generating random numbers is cheap and
 | |
| 		// ensures a much better distribution.
 | |
| 		rnd := weakrand.Intn(cacheSize)
 | |
| 		i := 0
 | |
| 		for _, randomCert := range certCache.cache {
 | |
| 			if i == rnd {
 | |
| 				certCache.removeCertificate(randomCert)
 | |
| 				break
 | |
| 			}
 | |
| 			i++
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// store the certificate
 | |
| 	certCache.cache[cert.hash] = cert
 | |
| 
 | |
| 	// update the index so we can access it by name
 | |
| 	for _, name := range cert.Names {
 | |
| 		certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // removeCertificate removes cert from the cache.
 | |
| //
 | |
| // This function is NOT safe for concurrent use; callers
 | |
| // MUST first acquire a write lock on certCache.mu.
 | |
| func (certCache *Cache) removeCertificate(cert Certificate) {
 | |
| 	// delete all mentions of this cert from the name index
 | |
| 	for _, name := range cert.Names {
 | |
| 		keyList := certCache.cacheIndex[name]
 | |
| 		for i, cacheKey := range keyList {
 | |
| 			if cacheKey == cert.hash {
 | |
| 				keyList = append(keyList[:i], keyList[i+1:]...)
 | |
| 			}
 | |
| 		}
 | |
| 		if len(keyList) == 0 {
 | |
| 			delete(certCache.cacheIndex, name)
 | |
| 		} else {
 | |
| 			certCache.cacheIndex[name] = keyList
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// delete the actual cert from the cache
 | |
| 	delete(certCache.cache, cert.hash)
 | |
| }
 | |
| 
 | |
| // replaceCertificate atomically replaces oldCert with newCert in
 | |
| // the cache.
 | |
| //
 | |
| // This method is safe for concurrent use.
 | |
| func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
 | |
| 	certCache.mu.Lock()
 | |
| 	certCache.removeCertificate(oldCert)
 | |
| 	certCache.unsyncedCacheCertificate(newCert)
 | |
| 	certCache.mu.Unlock()
 | |
| 	if certCache.logger != nil {
 | |
| 		certCache.logger.Info("replaced certificate in cache",
 | |
| 			zap.Strings("identifiers", newCert.Names),
 | |
| 			zap.Time("new_expiration", newCert.Leaf.NotAfter))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {
 | |
| 	certCache.mu.RLock()
 | |
| 	defer certCache.mu.RUnlock()
 | |
| 
 | |
| 	allCertKeys := certCache.cacheIndex[name]
 | |
| 
 | |
| 	certs := make([]Certificate, len(allCertKeys))
 | |
| 	for i := range allCertKeys {
 | |
| 		certs[i] = certCache.cache[allCertKeys[i]]
 | |
| 	}
 | |
| 
 | |
| 	return certs
 | |
| }
 | |
| 
 | |
| func (certCache *Cache) getAllCerts() []Certificate {
 | |
| 	certCache.mu.RLock()
 | |
| 	defer certCache.mu.RUnlock()
 | |
| 	certs := make([]Certificate, 0, len(certCache.cache))
 | |
| 	for _, cert := range certCache.cache {
 | |
| 		certs = append(certs, cert)
 | |
| 	}
 | |
| 	return certs
 | |
| }
 | |
| 
 | |
| func (certCache *Cache) getConfig(cert Certificate) (*Config, error) {
 | |
| 	cfg, err := certCache.options.GetConfigForCert(cert)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if cfg.certCache != nil && cfg.certCache != certCache {
 | |
| 		return nil, fmt.Errorf("config returned for certificate %v is not nil and points to different cache; got %p, expected %p (this one)",
 | |
| 			cert.Names, cfg.certCache, certCache)
 | |
| 	}
 | |
| 	return cfg, nil
 | |
| }
 | |
| 
 | |
| // AllMatchingCertificates returns a list of all certificates that could
 | |
| // be used to serve the given SNI name, including exact SAN matches and
 | |
| // wildcard matches.
 | |
| func (certCache *Cache) AllMatchingCertificates(name string) []Certificate {
 | |
| 	// get exact matches first
 | |
| 	certs := certCache.getAllMatchingCerts(name)
 | |
| 
 | |
| 	// then look for wildcard matches by replacing each
 | |
| 	// label of the domain name with wildcards
 | |
| 	labels := strings.Split(name, ".")
 | |
| 	for i := range labels {
 | |
| 		labels[i] = "*"
 | |
| 		candidate := strings.Join(labels, ".")
 | |
| 		certs = append(certs, certCache.getAllMatchingCerts(candidate)...)
 | |
| 	}
 | |
| 
 | |
| 	return certs
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	defaultCache   *Cache
 | |
| 	defaultCacheMu sync.Mutex
 | |
| )
 |