Cache cert verification methods (#871)

* cache cert verification

CheckSignature and Verify are expensive methods, and certificates are
static. Cache the results.

* use atomics

* make sure public key bytes match

* add VerifyWithCache and ResetCache

* cleanup

* use VerifyWithCache

* doc
This commit is contained in:
Wade Simmons 2023-05-17 10:14:26 -04:00 committed by GitHub
parent eb9f22a8fa
commit 9a7ed57a3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 79 additions and 5 deletions

View File

@ -91,9 +91,15 @@ func (ncp *NebulaCAPool) ResetCertBlocklist() {
ncp.certBlocklist = make(map[string]struct{})
}
// IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted
// NOTE: This uses an internal cache for Sha256Sum() that will not be invalidated
// automatically if you manually change any fields in the NebulaCertificate.
func (ncp *NebulaCAPool) IsBlocklisted(c *NebulaCertificate) bool {
h, err := c.Sha256Sum()
return ncp.isBlocklistedWithCache(c, false)
}
// IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted
func (ncp *NebulaCAPool) isBlocklistedWithCache(c *NebulaCertificate, useCache bool) bool {
h, err := c.sha256SumWithCache(useCache)
if err != nil {
return true
}

View File

@ -17,6 +17,7 @@ import (
"math"
"math/big"
"net"
"sync/atomic"
"time"
"golang.org/x/crypto/curve25519"
@ -42,6 +43,14 @@ const (
type NebulaCertificate struct {
Details NebulaCertificateDetails
Signature []byte
// the cached hex string of the calculated sha256sum
// for VerifyWithCache
sha256sum atomic.Pointer[string]
// the cached public key bytes if they were verified as the signer
// for VerifyWithCache
signatureVerified atomic.Pointer[[]byte]
}
type NebulaCertificateDetails struct {
@ -562,6 +571,27 @@ func (nc *NebulaCertificate) CheckSignature(key []byte) bool {
}
}
// NOTE: This uses an internal cache that will not be invalidated automatically
// if you manually change any fields in the NebulaCertificate.
func (nc *NebulaCertificate) checkSignatureWithCache(key []byte, useCache bool) bool {
if !useCache {
return nc.CheckSignature(key)
}
if v := nc.signatureVerified.Load(); v != nil {
return bytes.Equal(*v, key)
}
verified := nc.CheckSignature(key)
if verified {
keyCopy := make([]byte, len(key))
copy(keyCopy, key)
nc.signatureVerified.Store(&keyCopy)
}
return verified
}
// Expired will return true if the nebula cert is too young or too old compared to the provided time, otherwise false
func (nc *NebulaCertificate) Expired(t time.Time) bool {
return nc.Details.NotBefore.After(t) || nc.Details.NotAfter.Before(t)
@ -569,7 +599,26 @@ func (nc *NebulaCertificate) Expired(t time.Time) bool {
// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error) {
if ncp.IsBlocklisted(nc) {
return nc.verify(t, ncp, false)
}
// VerifyWithCache will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
//
// NOTE: This uses an internal cache that will not be invalidated automatically
// if you manually change any fields in the NebulaCertificate.
func (nc *NebulaCertificate) VerifyWithCache(t time.Time, ncp *NebulaCAPool) (bool, error) {
return nc.verify(t, ncp, true)
}
// ResetCache resets the cache used by VerifyWithCache.
func (nc *NebulaCertificate) ResetCache() {
nc.sha256sum.Store(nil)
nc.signatureVerified.Store(nil)
}
// Verify will ensure a certificate is good in all respects (expiry, group membership, signature, cert blocklist, etc)
func (nc *NebulaCertificate) verify(t time.Time, ncp *NebulaCAPool, useCache bool) (bool, error) {
if ncp.isBlocklistedWithCache(nc, useCache) {
return false, ErrBlockListed
}
@ -586,7 +635,7 @@ func (nc *NebulaCertificate) Verify(t time.Time, ncp *NebulaCAPool) (bool, error
return false, ErrExpired
}
if !nc.CheckSignature(signer.Details.PublicKey) {
if !nc.checkSignatureWithCache(signer.Details.PublicKey, useCache) {
return false, ErrSignatureMismatch
}
@ -809,6 +858,25 @@ func (nc *NebulaCertificate) Sha256Sum() (string, error) {
return hex.EncodeToString(sum[:]), nil
}
// NOTE: This uses an internal cache that will not be invalidated automatically
// if you manually change any fields in the NebulaCertificate.
func (nc *NebulaCertificate) sha256SumWithCache(useCache bool) (string, error) {
if !useCache {
return nc.Sha256Sum()
}
if s := nc.sha256sum.Load(); s != nil {
return *s, nil
}
s, err := nc.Sha256Sum()
if err != nil {
return s, err
}
nc.sha256sum.Store(&s)
return s, nil
}
func (nc *NebulaCertificate) MarshalJSON() ([]byte, error) {
toString := func(ips []*net.IPNet) []string {
s := []string{}

View File

@ -427,7 +427,7 @@ func (n *connectionManager) isInvalidCertificate(now time.Time, hostinfo *HostIn
return false
}
valid, err := remoteCert.Verify(now, n.intf.caPool)
valid, err := remoteCert.VerifyWithCache(now, n.intf.caPool)
if valid {
return false
}