mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-25 22:09:08 +01:00
Skip certificate files whose key belongs to another certificate
This commit is contained in:
@@ -134,3 +134,29 @@ func TestCollectChallenges_RefusesMalformedInput(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileStore_SkipsKeyOfAnotherCertificate(t *testing.T) {
|
||||||
|
ca := certtest.NewCA(t, "corp-root")
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
// A stale key next to a renewed certificate, sorted before the good pair, must not
|
||||||
|
// produce a proof that management rejects and stop the search there.
|
||||||
|
writeFile(t, dir, "a-renewed.crt", certtest.CertPEM(ca.Issue(t, certtest.ECDSAKey(t), "renewed")))
|
||||||
|
writeFile(t, dir, "a-renewed.key", certtest.KeyPEM(t, certtest.ECDSAKey(t)))
|
||||||
|
goodKey := certtest.ECDSAKey(t)
|
||||||
|
good := ca.Issue(t, goodKey, "good")
|
||||||
|
writeFile(t, dir, "b-good.pem", certtest.CertPEM(good)+certtest.KeyPEM(t, goodKey))
|
||||||
|
|
||||||
|
candidates, err := NewFileStore(dir).Candidates(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, candidates, 1, "only the certificate whose key matches is a candidate")
|
||||||
|
assert.True(t, good.Equal(candidates[0].Chain[0]), "the matching pair is kept")
|
||||||
|
|
||||||
|
challenger := certposture.NewChallenger([]byte("secret"))
|
||||||
|
now := time.Now()
|
||||||
|
nonce := challenger.Nonce(peerKey, now)
|
||||||
|
proofs := CollectChallenges(context.Background(), NewFileStore(dir), []*proto.CertificateChallenge{{Nonce: nonce, CaCertificates: []string{ca.PEM}}}, peerKey)
|
||||||
|
require.Len(t, proofs, 1)
|
||||||
|
_, err = challenger.Verify(proofs[0], peerKey, now)
|
||||||
|
assert.NoError(t, err, "the proof sent is one management accepts")
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ const (
|
|||||||
defaultStoreDir = "/etc/netbird/certs"
|
defaultStoreDir = "/etc/netbird/certs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// errKeyMismatch rejects a key that does not belong to the certificate it sits with: it
|
||||||
|
// would sign a proof management can only reject, in place of a usable later candidate.
|
||||||
|
var errKeyMismatch = errors.New("private key does not match the certificate")
|
||||||
|
|
||||||
// Candidate is a certificate chain the peer can sign for. Signer never exposes the key.
|
// Candidate is a certificate chain the peer can sign for. Signer never exposes the key.
|
||||||
type Candidate struct {
|
type Candidate struct {
|
||||||
Chain []*x509.Certificate
|
Chain []*x509.Certificate
|
||||||
@@ -120,25 +124,45 @@ func loadPEM(path string) ([]*x509.Certificate, crypto.Signer, error) {
|
|||||||
if len(chain) == 0 {
|
if len(chain) == 0 {
|
||||||
return nil, nil, errors.New("no certificate")
|
return nil, nil, errors.New("no certificate")
|
||||||
}
|
}
|
||||||
if signer != nil {
|
|
||||||
return chain, signer, nil
|
|
||||||
}
|
|
||||||
keyData, err := os.ReadFile(strings.TrimSuffix(path, filepath.Ext(path)) + ".key")
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return chain, nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("read key file: %w", err)
|
|
||||||
}
|
|
||||||
if _, signer, err = parsePEM(keyData); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if signer == nil {
|
if signer == nil {
|
||||||
return nil, nil, errors.New("no private key in key file")
|
if signer, err = siblingKey(path); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if signer == nil {
|
||||||
|
return chain, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !samePublicKey(signer.Public(), chain[0].PublicKey) {
|
||||||
|
return nil, nil, errKeyMismatch
|
||||||
}
|
}
|
||||||
return chain, signer, nil
|
return chain, signer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// siblingKey reads the private key from the "<name>.key" file next to a certificate
|
||||||
|
// file, or returns nil when there is no such file.
|
||||||
|
func siblingKey(path string) (crypto.Signer, error) {
|
||||||
|
keyData, err := os.ReadFile(strings.TrimSuffix(path, filepath.Ext(path)) + ".key")
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read key file: %w", err)
|
||||||
|
}
|
||||||
|
_, signer, err := parsePEM(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if signer == nil {
|
||||||
|
return nil, errors.New("no private key in key file")
|
||||||
|
}
|
||||||
|
return signer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func samePublicKey(a, b crypto.PublicKey) bool {
|
||||||
|
equaler, ok := a.(interface{ Equal(crypto.PublicKey) bool })
|
||||||
|
return ok && equaler.Equal(b)
|
||||||
|
}
|
||||||
|
|
||||||
func parsePEM(data []byte) ([]*x509.Certificate, crypto.Signer, error) {
|
func parsePEM(data []byte) ([]*x509.Certificate, crypto.Signer, error) {
|
||||||
var chain []*x509.Certificate
|
var chain []*x509.Certificate
|
||||||
var signer crypto.Signer
|
var signer crypto.Signer
|
||||||
|
|||||||
Reference in New Issue
Block a user