// 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" "sync" "time" ) // 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{} } // 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 } // 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{}), } go c.maintainAssets() 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 } // ConfigGetter is a function that returns a 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 } // 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() } func (certCache *Cache) getFirstMatchingCert(name string) (Certificate, bool) { certCache.mu.RLock() defer certCache.mu.RUnlock() allCertKeys := certCache.cacheIndex[name] if len(allCertKeys) == 0 { return Certificate{}, false } cert, ok := certCache.cache[allCertKeys[0]] return cert, ok } // TODO: This seems unused (but could be useful if TLS // handshakes serve up different certs for a single // name depending on other properties such as key type) 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) 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 New(certCache, cfg), nil } var ( defaultCache *Cache defaultCacheMu sync.Mutex )