mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	
		
			
	
	
		
			235 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			235 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package https
 | ||
|  | 
 | ||
|  | 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
 | ||
|  | 
 | ||
|  | 	// Managed certificates are certificates that Caddy is managing,
 | ||
|  | 	// as opposed to the user specifying a certificate and key file
 | ||
|  | 	// or directory and managing the certificate resources themselves.
 | ||
|  | 	Managed bool
 | ||
|  | 
 | ||
|  | 	// OnDemand certificates are obtained or loaded on-demand during TLS
 | ||
|  | 	// handshakes (as opposed to preloaded certificates, which are loaded
 | ||
|  | 	// at startup). If OnDemand is true, Managed must necessarily be true.
 | ||
|  | 	// OnDemand certificates are maintained in the background just like
 | ||
|  | 	// preloaded ones, however, if an OnDemand certificate fails to renew,
 | ||
|  | 	// it is removed from the in-memory cache.
 | ||
|  | 	OnDemand bool
 | ||
|  | 
 | ||
|  | 	// OCSP contains the certificate's parsed OCSP response.
 | ||
|  | 	OCSP *ocsp.Response
 | ||
|  | }
 | ||
|  | 
 | ||
|  | // 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, onDemand bool) (Certificate, error) {
 | ||
|  | 	cert, err := makeCertificateFromDisk(storage.SiteCertFile(domain), storage.SiteKeyFile(domain))
 | ||
|  | 	if err != nil {
 | ||
|  | 		return cert, err
 | ||
|  | 	}
 | ||
|  | 	cert.Managed = true
 | ||
|  | 	cert.OnDemand = onDemand
 | ||
|  | 	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) {
 | ||
|  | 	certCacheMu.Lock()
 | ||
|  | 	if _, ok := certCache[""]; !ok {
 | ||
|  | 		// use as default
 | ||
|  | 		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()
 | ||
|  | }
 |