mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	Merge pull request #1821 from mholt/ocspfix
tls: Fix OCSP stapling bug when certificate names overlap other certs
This commit is contained in:
		
						commit
						e49474a4f5
					
				@ -14,7 +14,9 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// certCache stores certificates in memory,
 | 
					// certCache stores certificates in memory,
 | 
				
			||||||
// keying certificates by name.
 | 
					// keying certificates by name. Certificates
 | 
				
			||||||
 | 
					// should not overlap in the names they serve,
 | 
				
			||||||
 | 
					// because a name only maps to one certificate.
 | 
				
			||||||
var certCache = make(map[string]Certificate)
 | 
					var certCache = make(map[string]Certificate)
 | 
				
			||||||
var certCacheMu sync.RWMutex
 | 
					var certCacheMu sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -27,6 +29,8 @@ type Certificate struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Names is the list of names this certificate is written for.
 | 
						// Names is the list of names this certificate is written for.
 | 
				
			||||||
	// The first is the CommonName (if any), the rest are SAN.
 | 
						// The first is the CommonName (if any), the rest are SAN.
 | 
				
			||||||
 | 
						// This should be the exact list of keys by which this cert
 | 
				
			||||||
 | 
						// is accessed in the cache, careful to avoid overlap.
 | 
				
			||||||
	Names []string
 | 
						Names []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// NotAfter is when the certificate expires.
 | 
						// NotAfter is when the certificate expires.
 | 
				
			||||||
@ -164,17 +168,9 @@ func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return cert, err
 | 
							return cert, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(tlsCert.Certificate) == 0 {
 | 
					 | 
				
			||||||
		return cert, errors.New("certificate is empty")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	cert.Certificate = tlsCert
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Parse leaf certificate, extract relevant metadata, and staple OCSP
 | 
						// Extract relevant metadata and staple OCSP
 | 
				
			||||||
	leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
 | 
						err = fillCertFromLeaf(&cert, tlsCert)
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return cert, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	err = fillCertFromLeaf(&cert, leaf)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return cert, err
 | 
							return cert, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -186,9 +182,19 @@ func makeCertificate(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
 | 
				
			|||||||
	return cert, nil
 | 
						return cert, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fillCertFromLeaf populates cert.Names and cert.NotAfter
 | 
					// fillCertFromLeaf populates metadata fields on cert from tlsCert.
 | 
				
			||||||
// using data in leaf.
 | 
					func fillCertFromLeaf(cert *Certificate, tlsCert tls.Certificate) error {
 | 
				
			||||||
func fillCertFromLeaf(cert *Certificate, leaf *x509.Certificate) error {
 | 
						if len(tlsCert.Certificate) == 0 {
 | 
				
			||||||
 | 
							return errors.New("certificate is empty")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cert.Certificate = tlsCert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the leaf cert should be the one for the site; it has what we need
 | 
				
			||||||
 | 
						leaf, err := x509.ParseCertificate(tlsCert.Certificate[0])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if leaf.Subject.CommonName != "" {
 | 
						if leaf.Subject.CommonName != "" {
 | 
				
			||||||
		cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
 | 
							cert.Names = []string{strings.ToLower(leaf.Subject.CommonName)}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -210,7 +216,9 @@ func fillCertFromLeaf(cert *Certificate, leaf *x509.Certificate) error {
 | 
				
			|||||||
	if len(cert.Names) == 0 {
 | 
						if len(cert.Names) == 0 {
 | 
				
			||||||
		return errors.New("certificate has no names")
 | 
							return errors.New("certificate has no names")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cert.NotAfter = leaf.NotAfter
 | 
						cert.NotAfter = leaf.NotAfter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -231,7 +239,6 @@ func cacheCertificate(cert Certificate) {
 | 
				
			|||||||
	if _, ok := certCache[""]; !ok {
 | 
						if _, ok := certCache[""]; !ok {
 | 
				
			||||||
		// use as default - must be *appended* to end of list, or bad things happen!
 | 
							// use as default - must be *appended* to end of list, or bad things happen!
 | 
				
			||||||
		cert.Names = append(cert.Names, "")
 | 
							cert.Names = append(cert.Names, "")
 | 
				
			||||||
		certCache[""] = cert
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for len(certCache)+len(cert.Names) > 10000 {
 | 
						for len(certCache)+len(cert.Names) > 10000 {
 | 
				
			||||||
		// for simplicity, just remove random elements
 | 
							// for simplicity, just remove random elements
 | 
				
			||||||
@ -243,7 +250,20 @@ func cacheCertificate(cert Certificate) {
 | 
				
			|||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, name := range cert.Names {
 | 
						for i := 0; i < len(cert.Names); i++ {
 | 
				
			||||||
 | 
							name := cert.Names[i]
 | 
				
			||||||
 | 
							if _, ok := certCache[name]; ok {
 | 
				
			||||||
 | 
								// do not allow certificates to overlap in the names they serve;
 | 
				
			||||||
 | 
								// this ambiguity causes problems because it is confusing while
 | 
				
			||||||
 | 
								// maintaining certificates; see OCSP maintenance code and
 | 
				
			||||||
 | 
								// https://caddy.community/t/random-ocsp-response-errors-for-random-clients/2473?u=matt.
 | 
				
			||||||
 | 
								log.Printf("[NOTICE] There is already a certificate loaded for %s, "+
 | 
				
			||||||
 | 
									"so certificate for %v will not service that name",
 | 
				
			||||||
 | 
									name, cert.Names)
 | 
				
			||||||
 | 
								cert.Names = append(cert.Names[:i], cert.Names[i+1:]...)
 | 
				
			||||||
 | 
								i--
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		certCache[name] = cert
 | 
							certCache[name] = cert
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	certCacheMu.Unlock()
 | 
						certCacheMu.Unlock()
 | 
				
			||||||
 | 
				
			|||||||
@ -246,6 +246,16 @@ func UpdateOCSPStaples() {
 | 
				
			|||||||
			log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
 | 
								log.Printf("[INFO] Advancing OCSP staple for %v from %s to %s",
 | 
				
			||||||
				cert.Names, lastNextUpdate, cert.OCSP.NextUpdate)
 | 
									cert.Names, lastNextUpdate, cert.OCSP.NextUpdate)
 | 
				
			||||||
			for _, n := range cert.Names {
 | 
								for _, n := range cert.Names {
 | 
				
			||||||
 | 
									// BUG: If this certificate has names on it that appear on another
 | 
				
			||||||
 | 
									// certificate in the cache, AND the other certificate is keyed by
 | 
				
			||||||
 | 
									// that name in the cache, then this method of 'queueing' the staple
 | 
				
			||||||
 | 
									// update will cause this certificate's new OCSP to be stapled to
 | 
				
			||||||
 | 
									// a different certificate! See:
 | 
				
			||||||
 | 
									// https://caddy.community/t/random-ocsp-response-errors-for-random-clients/2473?u=matt
 | 
				
			||||||
 | 
									// This problem should be avoided if names on certificates in the
 | 
				
			||||||
 | 
									// cache don't overlap with regards to the cache keys.
 | 
				
			||||||
 | 
									// (This is isn't a bug anymore, since we're careful when we add
 | 
				
			||||||
 | 
									// certificates to the cache by skipping keying when key already exists.)
 | 
				
			||||||
				updated[n] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP}
 | 
									updated[n] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.OCSP}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user