mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-10 07:29:06 +02:00
115 lines
4.2 KiB
Go
115 lines
4.2 KiB
Go
package certproof
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/shared/management/certposture"
|
|
"github.com/netbirdio/netbird/shared/management/proto"
|
|
)
|
|
|
|
// Collect answers the certificate challenges in checks: for each challenge it picks a
|
|
// stored certificate that chains to the challenge's CAs and signs the nonce with its
|
|
// key. The same certificate is proven once even if several checks accept it.
|
|
func Collect(ctx context.Context, store Store, checks []*proto.Checks, peerKey []byte) []certposture.Proof {
|
|
challenges := certificateChallenges(checks)
|
|
if len(challenges) == 0 {
|
|
logNoChallenges(checks)
|
|
return nil
|
|
}
|
|
return CollectChallenges(ctx, store, challenges, peerKey)
|
|
}
|
|
|
|
func logNoChallenges(checks []*proto.Checks) {
|
|
if len(checks) > 0 {
|
|
log.Infof("certificate posture: %d posture checks received, none carries a certificate challenge", len(checks))
|
|
}
|
|
}
|
|
|
|
// CollectChallenges answers challenges already extracted from the posture checks, so a
|
|
// caller that ships them across a process boundary reuses the same matching and signing.
|
|
func CollectChallenges(ctx context.Context, store Store, challenges []*proto.CertificateChallenge, peerKey []byte) []certposture.Proof {
|
|
log.Infof("certificate posture: answering %d certificate challenges from store %T", len(challenges), store)
|
|
|
|
candidates, err := store.Candidates(ctx)
|
|
if err != nil {
|
|
log.Warnf("failed loading certificates for posture checks: %v", err)
|
|
return nil
|
|
}
|
|
if len(candidates) == 0 {
|
|
log.Info("certificate posture: certificate store holds no candidates, no proof will be sent")
|
|
return nil
|
|
}
|
|
log.Infof("certificate posture: store holds %d candidate certificates", len(candidates))
|
|
|
|
now := time.Now()
|
|
proven := make(map[[sha256.Size]byte]struct{})
|
|
var proofs []certposture.Proof
|
|
for i, challenge := range challenges {
|
|
roots, err := certposture.ParseCAs(challenge.GetCaCertificates())
|
|
if err != nil {
|
|
log.Warnf("skipping certificate challenge with invalid CA certificates: %v", err)
|
|
continue
|
|
}
|
|
log.Infof("certificate posture: challenge %d accepts %d CA certificates, nonce is %d bytes", i, len(challenge.GetCaCertificates()), len(challenge.GetNonce()))
|
|
|
|
matched := false
|
|
for _, candidate := range candidates {
|
|
if len(candidate.Chain) == 0 {
|
|
continue
|
|
}
|
|
leaf := candidate.Chain[0]
|
|
if err := certposture.VerifyChain(candidate.Chain, roots, now); err != nil {
|
|
log.Infof("certificate posture: challenge %d rejected %q issued by %q, chain of %d: %v", i, leaf.Subject, leaf.Issuer, len(candidate.Chain), err)
|
|
continue
|
|
}
|
|
matched = true
|
|
|
|
fingerprint := sha256.Sum256(leaf.Raw)
|
|
if _, done := proven[fingerprint]; done {
|
|
log.Infof("certificate posture: challenge %d matched %q, already proven for an earlier challenge", i, leaf.Subject)
|
|
break
|
|
}
|
|
proof, err := prove(candidate, challenge.GetNonce(), peerKey)
|
|
if err != nil {
|
|
log.Warnf("failed signing certificate proof for %s: %v", leaf.Subject, err)
|
|
continue
|
|
}
|
|
log.Infof("certificate posture: challenge %d proven by %q with %s, signature %d bytes, chain of %d", i, leaf.Subject, proof.SigAlg, len(proof.Signature), len(proof.Chain))
|
|
proven[fingerprint] = struct{}{}
|
|
proofs = append(proofs, proof)
|
|
break
|
|
}
|
|
if !matched {
|
|
log.Infof("certificate posture: challenge %d matched none of the %d candidates", i, len(candidates))
|
|
}
|
|
}
|
|
log.Infof("certificate posture: %d challenges produced %d proofs", len(challenges), len(proofs))
|
|
return proofs
|
|
}
|
|
|
|
func certificateChallenges(checks []*proto.Checks) []*proto.CertificateChallenge {
|
|
var challenges []*proto.CertificateChallenge
|
|
for _, check := range checks {
|
|
if challenge := check.GetCertificateChallenge(); challenge != nil && len(challenge.GetNonce()) > 0 {
|
|
challenges = append(challenges, challenge)
|
|
}
|
|
}
|
|
return challenges
|
|
}
|
|
|
|
func prove(candidate Candidate, nonce, peerKey []byte) (certposture.Proof, error) {
|
|
sigAlg, sig, err := certposture.Sign(candidate.Signer, nonce, peerKey)
|
|
if err != nil {
|
|
return certposture.Proof{}, err
|
|
}
|
|
chain := make([][]byte, 0, len(candidate.Chain))
|
|
for _, cert := range candidate.Chain {
|
|
chain = append(chain, cert.Raw)
|
|
}
|
|
return certposture.Proof{Nonce: nonce, Chain: chain, SigAlg: sigAlg, Signature: sig}, nil
|
|
}
|