diff --git a/cert/ca.go b/cert/ca.go index d700544..0ffbd87 100644 --- a/cert/ca.go +++ b/cert/ca.go @@ -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 } diff --git a/cert/cert.go b/cert/cert.go index 8e5dbb9..24a75e3 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -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{} diff --git a/connection_manager.go b/connection_manager.go index a754a84..528cf1b 100644 --- a/connection_manager.go +++ b/connection_manager.go @@ -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 }