Skip to content

Commit 2e85d13

Browse files
[v1.9.x] do not panic when loading a V2 CA certificate (#1282)
Co-authored-by: Jack Doan <jackdoan@rivian.com>
1 parent 9bfdfba commit 2e85d13

File tree

5 files changed

+64
-39
lines changed

5 files changed

+64
-39
lines changed

cert/ca.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,39 @@ func NewCAPool() *NebulaCAPool {
2424

2525
// NewCAPoolFromBytes will create a new CA pool from the provided
2626
// input bytes, which must be a PEM-encoded set of nebula certificates.
27+
// If the pool contains unsupported certificates, they will generate warnings
28+
// in the []error return arg.
2729
// If the pool contains any expired certificates, an ErrExpired will be
2830
// returned along with the pool. The caller must handle any such errors.
29-
func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, error) {
31+
func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, []error, error) {
3032
pool := NewCAPool()
3133
var err error
32-
var expired bool
34+
var warnings []error
35+
good := 0
36+
3337
for {
3438
caPEMs, err = pool.AddCACertificate(caPEMs)
3539
if errors.Is(err, ErrExpired) {
36-
expired = true
37-
err = nil
38-
}
39-
if err != nil {
40-
return nil, err
40+
warnings = append(warnings, err)
41+
} else if errors.Is(err, ErrInvalidPEMCertificateUnsupported) {
42+
warnings = append(warnings, err)
43+
} else if err != nil {
44+
return nil, warnings, err
45+
} else {
46+
// Only consider a good certificate if there were no errors present
47+
good++
4148
}
49+
4250
if len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
4351
break
4452
}
4553
}
4654

47-
if expired {
48-
return pool, ErrExpired
55+
if good == 0 {
56+
return nil, warnings, errors.New("no valid CA certificates present")
4957
}
5058

51-
return pool, nil
59+
return pool, warnings, nil
5260
}
5361

5462
// AddCACertificate verifies a Nebula CA certificate and adds it to the pool

cert/cert.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const publicKeyLen = 32
2828

2929
const (
3030
CertBanner = "NEBULA CERTIFICATE"
31+
CertificateV2Banner = "NEBULA CERTIFICATE V2"
3132
X25519PrivateKeyBanner = "NEBULA X25519 PRIVATE KEY"
3233
X25519PublicKeyBanner = "NEBULA X25519 PUBLIC KEY"
3334
EncryptedEd25519PrivateKeyBanner = "NEBULA ED25519 ENCRYPTED PRIVATE KEY"
@@ -163,6 +164,9 @@ func UnmarshalNebulaCertificateFromPEM(b []byte) (*NebulaCertificate, []byte, er
163164
if p == nil {
164165
return nil, r, fmt.Errorf("input did not contain a valid PEM encoded block")
165166
}
167+
if p.Type == CertificateV2Banner {
168+
return nil, r, fmt.Errorf("%w: %s", ErrInvalidPEMCertificateUnsupported, p.Type)
169+
}
166170
if p.Type != CertBanner {
167171
return nil, r, fmt.Errorf("bytes did not contain a proper nebula certificate banner")
168172
}

cert/cert_test.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/ecdsa"
66
"crypto/elliptic"
77
"crypto/rand"
8+
"errors"
89
"fmt"
910
"io"
1011
"net"
@@ -572,6 +573,13 @@ CmYKEG5lYnVsYSBQMjU2IHRlc3Qo4s+7mgYw4tXrsAc6QQRkaW2jFmllYvN4+/k2
572573
76gvQAGgBgESRzBFAiEAib0/te6eMiZOKD8gdDeloMTS0wGuX2t0C7TFdUhAQzgC
573574
IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX
574575
-----END NEBULA CERTIFICATE-----
576+
`
577+
578+
v2 := `
579+
# valid PEM with the V2 header
580+
-----BEGIN NEBULA CERTIFICATE V2-----
581+
CmYKEG5lYnVsYSBQMjU2IHRlc3Qo4s+7mgYw4tXrsAc6QQRkaW2jFmllYvN4+/k2
582+
-----END NEBULA CERTIFICATE V2-----
575583
`
576584

577585
rootCA := NebulaCertificate{
@@ -592,33 +600,46 @@ IBNWYMep3ysx9zCgknfG5dKtwGTaqF++BWKDYdyl34KX
592600
},
593601
}
594602

595-
p, err := NewCAPoolFromBytes([]byte(noNewLines))
603+
p, warn, err := NewCAPoolFromBytes([]byte(noNewLines))
596604
assert.Nil(t, err)
605+
assert.Nil(t, warn)
597606
assert.Equal(t, p.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name)
598607
assert.Equal(t, p.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name)
599608

600-
pp, err := NewCAPoolFromBytes([]byte(withNewLines))
609+
pp, warn, err := NewCAPoolFromBytes([]byte(withNewLines))
601610
assert.Nil(t, err)
611+
assert.Nil(t, warn)
602612
assert.Equal(t, pp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name)
603613
assert.Equal(t, pp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name)
604614

605615
// expired cert, no valid certs
606-
ppp, err := NewCAPoolFromBytes([]byte(expired))
607-
assert.Equal(t, ErrExpired, err)
608-
assert.Equal(t, ppp.CAs[string("152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0")].Details.Name, "expired")
616+
ppp, warn, err := NewCAPoolFromBytes([]byte(expired))
617+
assert.Error(t, err, "no valid CA certificates present")
618+
assert.Len(t, warn, 1)
619+
assert.Error(t, warn[0], ErrExpired)
620+
assert.Nil(t, ppp)
609621

610622
// expired cert, with valid certs
611-
pppp, err := NewCAPoolFromBytes(append([]byte(expired), noNewLines...))
612-
assert.Equal(t, ErrExpired, err)
623+
pppp, warn, err := NewCAPoolFromBytes(append([]byte(expired), noNewLines...))
624+
assert.Len(t, warn, 1)
625+
assert.Nil(t, err)
626+
assert.Error(t, warn[0], ErrExpired)
613627
assert.Equal(t, pppp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name)
614628
assert.Equal(t, pppp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name)
615629
assert.Equal(t, pppp.CAs[string("152070be6bb19bc9e3bde4c2f0e7d8f4ff5448b4c9856b8eccb314fade0229b0")].Details.Name, "expired")
616630
assert.Equal(t, len(pppp.CAs), 3)
617631

618-
ppppp, err := NewCAPoolFromBytes([]byte(p256))
632+
ppppp, warn, err := NewCAPoolFromBytes([]byte(p256))
619633
assert.Nil(t, err)
634+
assert.Nil(t, warn)
620635
assert.Equal(t, ppppp.CAs[string("a7938893ec8c4ef769b06d7f425e5e46f7a7f5ffa49c3bcf4a86b608caba9159")].Details.Name, rootCAP256.Details.Name)
621636
assert.Equal(t, len(ppppp.CAs), 1)
637+
638+
pppppp, warn, err := NewCAPoolFromBytes(append([]byte(p256), []byte(v2)...))
639+
assert.Nil(t, err)
640+
assert.True(t, errors.Is(warn[0], ErrInvalidPEMCertificateUnsupported))
641+
assert.Equal(t, pppppp.CAs[string("a7938893ec8c4ef769b06d7f425e5e46f7a7f5ffa49c3bcf4a86b608caba9159")].Details.Name, rootCAP256.Details.Name)
642+
assert.Equal(t, len(pppppp.CAs), 1)
622643
}
623644

624645
func appendByteSlices(b ...[]byte) []byte {

cert/errors.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import (
55
)
66

77
var (
8-
ErrRootExpired = errors.New("root certificate is expired")
9-
ErrExpired = errors.New("certificate is expired")
10-
ErrNotCA = errors.New("certificate is not a CA")
11-
ErrNotSelfSigned = errors.New("certificate is not self-signed")
12-
ErrBlockListed = errors.New("certificate is in the block list")
13-
ErrSignatureMismatch = errors.New("certificate signature did not match")
8+
ErrRootExpired = errors.New("root certificate is expired")
9+
ErrExpired = errors.New("certificate is expired")
10+
ErrNotCA = errors.New("certificate is not a CA")
11+
ErrNotSelfSigned = errors.New("certificate is not self-signed")
12+
ErrBlockListed = errors.New("certificate is in the block list")
13+
ErrSignatureMismatch = errors.New("certificate signature did not match")
14+
ErrInvalidPEMCertificateUnsupported = errors.New("bytes contain an unsupported certificate format")
1415
)

pki.go

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -223,22 +223,13 @@ func loadCAPoolFromConfig(l *logrus.Logger, c *config.C) (*cert.NebulaCAPool, er
223223
}
224224
}
225225

226-
caPool, err := cert.NewCAPoolFromBytes(rawCA)
227-
if errors.Is(err, cert.ErrExpired) {
228-
var expired int
229-
for _, crt := range caPool.CAs {
230-
if crt.Expired(time.Now()) {
231-
expired++
232-
l.WithField("cert", crt).Warn("expired certificate present in CA pool")
233-
}
234-
}
235-
236-
if expired >= len(caPool.CAs) {
237-
return nil, errors.New("no valid CA certificates present")
238-
}
226+
caPool, warnings, err := cert.NewCAPoolFromBytes(rawCA)
227+
for _, w := range warnings {
228+
l.WithError(w).Warn("parsing a CA certificate failed")
229+
}
239230

240-
} else if err != nil {
241-
return nil, fmt.Errorf("error while adding CA certificate to CA trust store: %s", err)
231+
if err != nil {
232+
return nil, fmt.Errorf("could not create CA certificate pool: %s", err)
242233
}
243234

244235
for _, fp := range c.GetStringSlice("pki.blocklist", []string{}) {

0 commit comments

Comments
 (0)