| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package tls |
| |
| import ( |
| "crypto/x509" |
| "runtime" |
| "sync" |
| "sync/atomic" |
| ) |
| |
| type cacheEntry struct { |
| refs atomic.Int64 |
| cert *x509.Certificate |
| } |
| |
| // certCache implements an intern table for reference counted x509.Certificates, |
| // implemented in a similar fashion to BoringSSL's CRYPTO_BUFFER_POOL. This |
| // allows for a single x509.Certificate to be kept in memory and referenced from |
| // multiple Conns. Returned references should not be mutated by callers. Certificates |
| // are still safe to use after they are removed from the cache. |
| // |
| // Certificates are returned wrapped in an activeCert struct that should be held by |
| // the caller. When references to the activeCert are freed, the number of references |
| // to the certificate in the cache is decremented. Once the number of references |
| // reaches zero, the entry is evicted from the cache. |
| // |
| // The main difference between this implementation and CRYPTO_BUFFER_POOL is that |
| // CRYPTO_BUFFER_POOL is a more generic structure which supports blobs of data, |
| // rather than specific structures. Since we only care about x509.Certificates, |
| // certCache is implemented as a specific cache, rather than a generic one. |
| // |
| // See https://boringssl.googlesource.com/boringssl/+/master/include/openssl/pool.h |
| // and https://boringssl.googlesource.com/boringssl/+/master/crypto/pool/pool.c |
| // for the BoringSSL reference. |
| type certCache struct { |
| sync.Map |
| } |
| |
| var globalCertCache = new(certCache) |
| |
| // activeCert is a handle to a certificate held in the cache. Once there are |
| // no alive activeCerts for a given certificate, the certificate is removed |
| // from the cache by a finalizer. |
| type activeCert struct { |
| cert *x509.Certificate |
| } |
| |
| // active increments the number of references to the entry, wraps the |
| // certificate in the entry in an activeCert, and sets the finalizer. |
| // |
| // Note that there is a race between active and the finalizer set on the |
| // returned activeCert, triggered if active is called after the ref count is |
| // decremented such that refs may be > 0 when evict is called. We consider this |
| // safe, since the caller holding an activeCert for an entry that is no longer |
| // in the cache is fine, with the only side effect being the memory overhead of |
| // there being more than one distinct reference to a certificate alive at once. |
| func (cc *certCache) active(e *cacheEntry) *activeCert { |
| e.refs.Add(1) |
| a := &activeCert{e.cert} |
| runtime.SetFinalizer(a, func(_ *activeCert) { |
| if e.refs.Add(-1) == 0 { |
| cc.evict(e) |
| } |
| }) |
| return a |
| } |
| |
| // evict removes a cacheEntry from the cache. |
| func (cc *certCache) evict(e *cacheEntry) { |
| cc.Delete(string(e.cert.Raw)) |
| } |
| |
| // newCert returns a x509.Certificate parsed from der. If there is already a copy |
| // of the certificate in the cache, a reference to the existing certificate will |
| // be returned. Otherwise, a fresh certificate will be added to the cache, and |
| // the reference returned. The returned reference should not be mutated. |
| func (cc *certCache) newCert(der []byte) (*activeCert, error) { |
| if entry, ok := cc.Load(string(der)); ok { |
| return cc.active(entry.(*cacheEntry)), nil |
| } |
| |
| cert, err := x509.ParseCertificate(der) |
| if err != nil { |
| return nil, err |
| } |
| |
| entry := &cacheEntry{cert: cert} |
| if entry, loaded := cc.LoadOrStore(string(der), entry); loaded { |
| return cc.active(entry.(*cacheEntry)), nil |
| } |
| return cc.active(entry), nil |
| } |