Source file src/crypto/tls/cache.go
1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tls 6 7 import ( 8 "crypto/x509" 9 "runtime" 10 "sync" 11 "sync/atomic" 12 ) 13 14 type cacheEntry struct { 15 refs atomic.Int64 16 cert *x509.Certificate 17 } 18 19 // certCache implements an intern table for reference counted x509.Certificates, 20 // implemented in a similar fashion to BoringSSL's CRYPTO_BUFFER_POOL. This 21 // allows for a single x509.Certificate to be kept in memory and referenced from 22 // multiple Conns. Returned references should not be mutated by callers. Certificates 23 // are still safe to use after they are removed from the cache. 24 // 25 // Certificates are returned wrapped in an activeCert struct that should be held by 26 // the caller. When references to the activeCert are freed, the number of references 27 // to the certificate in the cache is decremented. Once the number of references 28 // reaches zero, the entry is evicted from the cache. 29 // 30 // The main difference between this implementation and CRYPTO_BUFFER_POOL is that 31 // CRYPTO_BUFFER_POOL is a more generic structure which supports blobs of data, 32 // rather than specific structures. Since we only care about x509.Certificates, 33 // certCache is implemented as a specific cache, rather than a generic one. 34 // 35 // See https://boringssl.googlesource.com/boringssl/+/master/include/openssl/pool.h 36 // and https://boringssl.googlesource.com/boringssl/+/master/crypto/pool/pool.c 37 // for the BoringSSL reference. 38 type certCache struct { 39 sync.Map 40 } 41 42 var globalCertCache = new(certCache) 43 44 // activeCert is a handle to a certificate held in the cache. Once there are 45 // no alive activeCerts for a given certificate, the certificate is removed 46 // from the cache by a finalizer. 47 type activeCert struct { 48 cert *x509.Certificate 49 } 50 51 // active increments the number of references to the entry, wraps the 52 // certificate in the entry in an activeCert, and sets the finalizer. 53 // 54 // Note that there is a race between active and the finalizer set on the 55 // returned activeCert, triggered if active is called after the ref count is 56 // decremented such that refs may be > 0 when evict is called. We consider this 57 // safe, since the caller holding an activeCert for an entry that is no longer 58 // in the cache is fine, with the only side effect being the memory overhead of 59 // there being more than one distinct reference to a certificate alive at once. 60 func (cc *certCache) active(e *cacheEntry) *activeCert { 61 e.refs.Add(1) 62 a := &activeCert{e.cert} 63 runtime.SetFinalizer(a, func(_ *activeCert) { 64 if e.refs.Add(-1) == 0 { 65 cc.evict(e) 66 } 67 }) 68 return a 69 } 70 71 // evict removes a cacheEntry from the cache. 72 func (cc *certCache) evict(e *cacheEntry) { 73 cc.Delete(string(e.cert.Raw)) 74 } 75 76 // newCert returns a x509.Certificate parsed from der. If there is already a copy 77 // of the certificate in the cache, a reference to the existing certificate will 78 // be returned. Otherwise, a fresh certificate will be added to the cache, and 79 // the reference returned. The returned reference should not be mutated. 80 func (cc *certCache) newCert(der []byte) (*activeCert, error) { 81 if entry, ok := cc.Load(string(der)); ok { 82 return cc.active(entry.(*cacheEntry)), nil 83 } 84 85 cert, err := x509.ParseCertificate(der) 86 if err != nil { 87 return nil, err 88 } 89 90 entry := &cacheEntry{cert: cert} 91 if entry, loaded := cc.LoadOrStore(string(der), entry); loaded { 92 return cc.active(entry.(*cacheEntry)), nil 93 } 94 return cc.active(entry), nil 95 } 96