Files
netbird/shared/management/certposture/chain.go
T

107 lines
2.6 KiB
Go

package certposture
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"strings"
"time"
)
var ErrNoCertificateInPEM = errors.New("no certificate found in PEM data")
// ParseCAs builds a root pool from PEM encoded CA certificates.
func ParseCAs(pems []string) (*x509.CertPool, error) {
roots := x509.NewCertPool()
for _, p := range pems {
if !roots.AppendCertsFromPEM([]byte(p)) {
return nil, ErrNoCertificateInPEM
}
}
return roots, nil
}
// VerifyChain reports whether the leaf (chain[0]) chains to one of roots using the
// remaining certificates as intermediates.
func VerifyChain(chain []*x509.Certificate, roots *x509.CertPool, now time.Time) error {
if len(chain) == 0 {
return ErrEmptyChain
}
intermediates := x509.NewCertPool()
for _, cert := range chain[1:] {
intermediates.AddCert(cert)
}
_, err := chain[0].Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
CurrentTime: now,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
return err
}
// ChainMatchesCAs is VerifyChain over the PEM forms stored in peer meta and check config.
func ChainMatchesCAs(chainPEM string, caPEMs []string, now time.Time) bool {
roots, err := ParseCAs(caPEMs)
if err != nil {
return false
}
return chainMatchesPool(chainPEM, roots, now)
}
// AnyChainMatchesCAs reports whether at least one of the peer's verified chains
// is anchored in one of the configured CAs.
func AnyChainMatchesCAs(chainPEMs, caPEMs []string, now time.Time) bool {
roots, err := ParseCAs(caPEMs)
if err != nil {
return false
}
for _, chainPEM := range chainPEMs {
if chainMatchesPool(chainPEM, roots, now) {
return true
}
}
return false
}
func chainMatchesPool(chainPEM string, roots *x509.CertPool, now time.Time) bool {
chain, err := ParseChainPEM(chainPEM)
if err != nil {
return false
}
return VerifyChain(chain, roots, now) == nil
}
func EncodeChainPEM(chain []*x509.Certificate) string {
var b strings.Builder
for _, cert := range chain {
_ = pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
}
return b.String()
}
func ParseChainPEM(chainPEM string) ([]*x509.Certificate, error) {
var chain []*x509.Certificate
rest := []byte(chainPEM)
for {
var block *pem.Block
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse certificate: %w", err)
}
chain = append(chain, cert)
}
if len(chain) == 0 {
return nil, ErrNoCertificateInPEM
}
return chain, nil
}