2019-11-19 10:00:20 -07:00
|
|
|
package cert
|
|
|
|
|
|
|
|
import (
|
2021-12-09 20:24:56 -07:00
|
|
|
"errors"
|
2019-11-19 10:00:20 -07:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type NebulaCAPool struct {
|
|
|
|
CAs map[string]*NebulaCertificate
|
2020-08-05 19:17:47 -06:00
|
|
|
certBlocklist map[string]struct{}
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewCAPool creates a CAPool
|
|
|
|
func NewCAPool() *NebulaCAPool {
|
|
|
|
ca := NebulaCAPool{
|
|
|
|
CAs: make(map[string]*NebulaCertificate),
|
2020-08-05 19:17:47 -06:00
|
|
|
certBlocklist: make(map[string]struct{}),
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return &ca
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:24:56 -07:00
|
|
|
// NewCAPoolFromBytes will create a new CA pool from the provided
|
|
|
|
// input bytes, which must be a PEM-encoded set of nebula certificates.
|
|
|
|
// If the pool contains any expired certificates, an ErrExpired will be
|
|
|
|
// returned along with the pool. The caller must handle any such errors.
|
2019-11-19 10:00:20 -07:00
|
|
|
func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, error) {
|
|
|
|
pool := NewCAPool()
|
|
|
|
var err error
|
2021-12-09 20:24:56 -07:00
|
|
|
var expired bool
|
2019-11-19 10:00:20 -07:00
|
|
|
for {
|
|
|
|
caPEMs, err = pool.AddCACertificate(caPEMs)
|
2021-12-09 20:24:56 -07:00
|
|
|
if errors.Is(err, ErrExpired) {
|
|
|
|
expired = true
|
|
|
|
err = nil
|
|
|
|
}
|
2019-11-19 10:00:20 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-12-09 20:24:56 -07:00
|
|
|
if len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
|
2019-11-19 10:00:20 -07:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:24:56 -07:00
|
|
|
if expired {
|
|
|
|
return pool, ErrExpired
|
|
|
|
}
|
|
|
|
|
2019-11-19 10:00:20 -07:00
|
|
|
return pool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddCACertificate verifies a Nebula CA certificate and adds it to the pool
|
|
|
|
// Only the first pem encoded object will be consumed, any remaining bytes are returned.
|
|
|
|
// Parsed certificates will be verified and must be a CA
|
|
|
|
func (ncp *NebulaCAPool) AddCACertificate(pemBytes []byte) ([]byte, error) {
|
|
|
|
c, pemBytes, err := UnmarshalNebulaCertificateFromPEM(pemBytes)
|
|
|
|
if err != nil {
|
|
|
|
return pemBytes, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !c.Details.IsCA {
|
2021-12-09 20:24:56 -07:00
|
|
|
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotCA)
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if !c.CheckSignature(c.Details.PublicKey) {
|
2021-12-09 20:24:56 -07:00
|
|
|
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotSelfSigned)
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
sum, err := c.Sha256Sum()
|
|
|
|
if err != nil {
|
|
|
|
return pemBytes, fmt.Errorf("could not calculate shasum for provided CA; error: %s; %s", err, c.Details.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
ncp.CAs[sum] = c
|
2021-12-09 20:24:56 -07:00
|
|
|
if c.Expired(time.Now()) {
|
|
|
|
return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrExpired)
|
|
|
|
}
|
|
|
|
|
2019-11-19 10:00:20 -07:00
|
|
|
return pemBytes, nil
|
|
|
|
}
|
|
|
|
|
2020-08-05 19:17:47 -06:00
|
|
|
// BlocklistFingerprint adds a cert fingerprint to the blocklist
|
|
|
|
func (ncp *NebulaCAPool) BlocklistFingerprint(f string) {
|
|
|
|
ncp.certBlocklist[f] = struct{}{}
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
2020-08-05 19:17:47 -06:00
|
|
|
// ResetCertBlocklist removes all previously blocklisted cert fingerprints
|
|
|
|
func (ncp *NebulaCAPool) ResetCertBlocklist() {
|
|
|
|
ncp.certBlocklist = make(map[string]struct{})
|
2019-11-19 10:00:20 -07:00
|
|
|
}
|
|
|
|
|
2023-05-17 08:14:26 -06:00
|
|
|
// NOTE: This uses an internal cache for Sha256Sum() that will not be invalidated
|
|
|
|
// automatically if you manually change any fields in the NebulaCertificate.
|
2020-08-05 19:17:47 -06:00
|
|
|
func (ncp *NebulaCAPool) IsBlocklisted(c *NebulaCertificate) bool {
|
2023-05-17 08:14:26 -06:00
|
|
|
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)
|
2019-11-19 10:00:20 -07:00
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-08-05 19:17:47 -06:00
|
|
|
if _, ok := ncp.certBlocklist[h]; ok {
|
2019-11-19 10:00:20 -07:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCAForCert attempts to return the signing certificate for the provided certificate.
|
|
|
|
// No signature validation is performed
|
|
|
|
func (ncp *NebulaCAPool) GetCAForCert(c *NebulaCertificate) (*NebulaCertificate, error) {
|
|
|
|
if c.Details.Issuer == "" {
|
|
|
|
return nil, fmt.Errorf("no issuer in certificate")
|
|
|
|
}
|
|
|
|
|
|
|
|
signer, ok := ncp.CAs[c.Details.Issuer]
|
|
|
|
if ok {
|
|
|
|
return signer, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("could not find ca for the certificate")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFingerprints returns an array of trusted CA fingerprints
|
|
|
|
func (ncp *NebulaCAPool) GetFingerprints() []string {
|
|
|
|
fp := make([]string, len(ncp.CAs))
|
|
|
|
|
|
|
|
i := 0
|
|
|
|
for k := range ncp.CAs {
|
|
|
|
fp[i] = k
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
return fp
|
|
|
|
}
|