mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	* Initial concept for pluggable storage (sans tests and docs) * Add TLS storage docs, test harness, and minor clean up from code review * Fix issue with caddymain's temporary moveStorage * Formatting improvement on struct array literal by removing struct name * Pluggable storage changes: * Change storage interface to persist all site or user data in one call * Add lock/unlock calls for renewal and cert obtaining * Key fields on composite literals
		
			
				
	
	
		
			245 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package caddytls
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/tls"
 | 
						|
	"crypto/x509"
 | 
						|
	"errors"
 | 
						|
	"io/ioutil"
 | 
						|
	"log"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/xenolf/lego/acme"
 | 
						|
	"golang.org/x/crypto/ocsp"
 | 
						|
)
 | 
						|
 | 
						|
// certCache stores certificates in memory,
 | 
						|
// keying certificates by name.
 | 
						|
var certCache = make(map[string]Certificate)
 | 
						|
var certCacheMu sync.RWMutex
 | 
						|
 | 
						|
// Certificate is a tls.Certificate with associated metadata tacked on.
 | 
						|
// Even if the metadata can be obtained by parsing the certificate,
 | 
						|
// we can be more efficient by extracting the metadata once so it's
 | 
						|
// just there, ready to use.
 | 
						|
type Certificate struct {
 | 
						|
	tls.Certificate
 | 
						|
 | 
						|
	// Names is the list of names this certificate is written for.
 | 
						|
	// The first is the CommonName (if any), the rest are SAN.
 | 
						|
	Names []string
 | 
						|
 | 
						|
	// NotAfter is when the certificate expires.
 | 
						|
	NotAfter time.Time
 | 
						|
 | 
						|
	// OCSP contains the certificate's parsed OCSP response.
 | 
						|
	OCSP *ocsp.Response
 | 
						|
 | 
						|
	// Config is the configuration with which the certificate was
 | 
						|
	// loaded or obtained and with which it should be maintained.
 | 
						|
	Config *Config
 | 
						|
}
 | 
						|
 | 
						|
// getCertificate gets a certificate that matches name (a server name)
 | 
						|
// from the in-memory cache. If there is no exact match for name, it
 | 
						|
// will be checked against names of the form '*.example.com' (wildcard
 | 
						|
// certificates) according to RFC 6125. If a match is found, matched will
 | 
						|
// be true. If no matches are found, matched will be false and a default
 | 
						|
// certificate will be returned with defaulted set to true. If no default
 | 
						|
// certificate is set, defaulted will be set to false.
 | 
						|
//
 | 
						|
// The logic in this function is adapted from the Go standard library,
 | 
						|
// which is by the Go Authors.
 | 
						|
//
 | 
						|
// This function is safe for concurrent use.
 | 
						|
func getCertificate(name string) (cert Certificate, matched, defaulted bool) {
 | 
						|
	var ok bool
 | 
						|
 | 
						|
	// Not going to trim trailing dots here since RFC 3546 says,
 | 
						|
	// "The hostname is represented ... without a trailing dot."
 | 
						|
	// Just normalize to lowercase.
 | 
						|
	name = strings.ToLower(name)
 | 
						|
 | 
						|
	certCacheMu.RLock()
 | 
						|
	defer certCacheMu.RUnlock()
 | 
						|
 | 
						|
	// exact match? great, let's use it
 | 
						|
	if cert, ok = certCache[name]; ok {
 | 
						|
		matched = true
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// try replacing labels in the name with wildcards until we get a match
 | 
						|
	labels := strings.Split(name, ".")
 | 
						|
	for i := range labels {
 | 
						|
		labels[i] = "*"
 | 
						|
		candidate := strings.Join(labels, ".")
 | 
						|
		if cert, ok = certCache[candidate]; ok {
 | 
						|
			matched = true
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// if nothing matches, use the default certificate or bust
 | 
						|
	cert, defaulted = certCache[""]
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// CacheManagedCertificate loads the certificate for domain into the
 | 
						|
// cache, flagging it as Managed and, if onDemand is true, as "OnDemand"
 | 
						|
// (meaning that it was obtained or loaded during a TLS handshake).
 | 
						|
//
 | 
						|
// This function is safe for concurrent use.
 | 
						|
func CacheManagedCertificate(domain string, cfg *Config) (Certificate, error) {
 | 
						|
	storage, err := cfg.StorageFor(cfg.CAUrl)
 | 
						|
	if err != nil {
 | 
						|
		return Certificate{}, err
 | 
						|
	}
 | 
						|
	siteData, err := storage.LoadSite(domain)
 | 
						|
	if err != nil {
 | 
						|
		return Certificate{}, err
 | 
						|
	}
 | 
						|
	cert, err := makeCertificate(siteData.Cert, siteData.Key)
 | 
						|
	if err != nil {
 | 
						|
		return cert, err
 | 
						|
	}
 | 
						|
	cert.Config = cfg
 | 
						|
	cacheCertificate(cert)
 | 
						|
	return cert, nil
 | 
						|
}
 | 
						|
 | 
						|
// cacheUnmanagedCertificatePEMFile loads a certificate for host using certFile
 | 
						|
// and keyFile, which must be in PEM format. It stores the certificate in
 | 
						|
// memory. The Managed and OnDemand flags of the certificate will be set to
 | 
						|
// false.
 | 
						|
//
 | 
						|
// This function is safe for concurrent use.
 | 
						|
func cacheUnmanagedCertificatePEMFile(certFile, keyFile string) error {
 | 
						|
	cert, err := makeCertificateFromDisk(certFile, keyFile)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	cacheCertificate(cert)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// cacheUnmanagedCertificatePEMBytes makes a certificate out of the PEM bytes
 | 
						|
// of the certificate and key, then caches it in memory.
 | 
						|
//
 | 
						|
// This function is safe for concurrent use.
 | 
						|
func cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) error {
 | 
						|
	cert, err := makeCertificate(certBytes, keyBytes)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	cacheCertificate(cert)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// makeCertificateFromDisk makes a Certificate by loading the
 | 
						|
// certificate and key files. It fills out all the fields in
 | 
						|
// the certificate except for the Managed and OnDemand flags.
 | 
						|
// (It is up to the caller to set those.)
 | 
						|
func makeCertificateFromDisk(certFile, keyFile string) (Certificate, error) {
 | 
						|
	certPEMBlock, err := ioutil.ReadFile(certFile)
 | 
						|
	if err != nil {
 | 
						|
		return Certificate{}, err
 | 
						|
	}
 | 
						|
	keyPEMBlock, err := ioutil.ReadFile(keyFile)
 | 
						|
	if err != nil {
 | 
						|
		return Certificate{}, err
 | 
						|
	}
 | 
						|
	return makeCertificate(certPEMBlock, keyPEMBlock)
 | 
						|
}
 | 
						|
 | 
						|
// 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
 | 
						|
}
 | 
						|
 | 
						|
// cacheCertificate adds cert to the in-memory cache. If the cache is
 | 
						|
// empty, cert will be used as the default certificate. If the cache is
 | 
						|
// full, random entries are deleted until there is room to map all the
 | 
						|
// names on the certificate.
 | 
						|
//
 | 
						|
// This certificate will be keyed to the names in cert.Names. Any name
 | 
						|
// that is already a key in the cache will be replaced with this cert.
 | 
						|
//
 | 
						|
// This function is safe for concurrent use.
 | 
						|
func cacheCertificate(cert Certificate) {
 | 
						|
	if cert.Config == nil {
 | 
						|
		cert.Config = new(Config)
 | 
						|
	}
 | 
						|
	certCacheMu.Lock()
 | 
						|
	if _, ok := certCache[""]; !ok {
 | 
						|
		// use as default - must be *appended* to list, or bad things happen!
 | 
						|
		cert.Names = append(cert.Names, "")
 | 
						|
		certCache[""] = cert
 | 
						|
	}
 | 
						|
	for len(certCache)+len(cert.Names) > 10000 {
 | 
						|
		// for simplicity, just remove random elements
 | 
						|
		for key := range certCache {
 | 
						|
			if key == "" { // ... but not the default cert
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			delete(certCache, key)
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, name := range cert.Names {
 | 
						|
		certCache[name] = cert
 | 
						|
	}
 | 
						|
	certCacheMu.Unlock()
 | 
						|
}
 | 
						|
 | 
						|
// uncacheCertificate deletes name's certificate from the
 | 
						|
// cache. If name is not a key in the certificate cache,
 | 
						|
// this function does nothing.
 | 
						|
func uncacheCertificate(name string) {
 | 
						|
	certCacheMu.Lock()
 | 
						|
	delete(certCache, name)
 | 
						|
	certCacheMu.Unlock()
 | 
						|
}
 |