pki.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. package nebula
  2. import (
  3. "crypto/fips140"
  4. "encoding/binary"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "log/slog"
  10. "net"
  11. "net/netip"
  12. "os"
  13. "slices"
  14. "strings"
  15. "sync/atomic"
  16. "time"
  17. "github.com/flynn/noise"
  18. "github.com/gaissmai/bart"
  19. "github.com/slackhq/nebula/cert"
  20. "github.com/slackhq/nebula/config"
  21. "github.com/slackhq/nebula/handshake"
  22. "github.com/slackhq/nebula/noiseutil"
  23. "github.com/slackhq/nebula/util"
  24. )
  25. type PKI struct {
  26. cs atomic.Pointer[CertState]
  27. caPool atomic.Pointer[cert.CAPool]
  28. l *slog.Logger
  29. }
  30. type CertState struct {
  31. v1Cert cert.Certificate
  32. v1Credential *handshake.Credential
  33. v2Cert cert.Certificate
  34. v2Credential *handshake.Credential
  35. initiatingVersion cert.Version
  36. privateKey []byte
  37. pkcs11Backed bool
  38. cipher string
  39. myVpnNetworks []netip.Prefix
  40. myVpnNetworksTable *bart.Lite
  41. myVpnAddrs []netip.Addr
  42. myVpnAddrsTable *bart.Lite
  43. myVpnBroadcastAddrsTable *bart.Lite
  44. }
  45. // errCertNeedsRestart marks a new cert that is refused on reload because only a restart can apply it
  46. var errCertNeedsRestart = errors.New("a restart is required to use the new cert")
  47. func NewPKIFromConfig(l *slog.Logger, c *config.C) (*PKI, error) {
  48. pki := &PKI{l: l}
  49. err := pki.reload(c, true, nil)
  50. if err != nil {
  51. return nil, err
  52. }
  53. c.RegisterReloadCallback(func(c *config.C, r *config.Reload) {
  54. r.RestartRequiredIfChanged("cipher")
  55. rErr := pki.reload(c, false, r)
  56. if rErr != nil {
  57. util.LogWithContextIfNeeded("Failed to reload PKI from config", rErr, l)
  58. }
  59. })
  60. return pki, nil
  61. }
  62. func (p *PKI) GetCAPool() *cert.CAPool {
  63. return p.caPool.Load()
  64. }
  65. func (p *PKI) getCertState() *CertState {
  66. return p.cs.Load()
  67. }
  68. // r is nil on the initial load
  69. func (p *PKI) reload(c *config.C, initial bool, r *config.Reload) error {
  70. before := p.cs.Load()
  71. err := p.reloadCerts(c, initial)
  72. if err != nil {
  73. if initial {
  74. return err
  75. }
  76. err.Log(p.l)
  77. if errors.Is(err, errCertNeedsRestart) {
  78. r.RestartRequired()
  79. } else {
  80. r.Failed()
  81. }
  82. } else if !initial {
  83. // A cert for a version we weren't running is accepted without comparing its networks to ours. The tun and the
  84. // lighthouse keep the networks they started with
  85. after := p.cs.Load()
  86. if !slices.Equal(before.myVpnNetworks, after.myVpnNetworks) {
  87. p.l.Warn("The new cert changes our networks, a restart is required to use them",
  88. "old_networks", before.myVpnNetworks, "new_networks", after.myVpnNetworks)
  89. r.RestartRequired()
  90. }
  91. }
  92. err = p.reloadCAPool(c)
  93. if err != nil {
  94. if initial {
  95. return err
  96. }
  97. err.Log(p.l)
  98. r.Failed()
  99. }
  100. return nil
  101. }
  102. func (p *PKI) reloadCerts(c *config.C, initial bool) *util.ContextualError {
  103. var cipher string
  104. var currentState *CertState
  105. if initial {
  106. cipher = c.GetString("cipher", "aes")
  107. switch cipher {
  108. case "aes", "chachapoly":
  109. // Each post-handshake CipherState in noiseutil hardcodes its own
  110. // nonce endianness now, so there's nothing to set up here.
  111. default:
  112. return util.NewContextualError(
  113. "unknown cipher",
  114. m{"cipher": cipher},
  115. nil,
  116. )
  117. }
  118. } else {
  119. // Cipher cant be hot swapped so just leave it at what it was before
  120. currentState = p.cs.Load()
  121. cipher = currentState.cipher
  122. }
  123. newState, err := newCertStateFromConfig(c, cipher)
  124. if err != nil {
  125. return util.NewContextualError("Could not load client cert", nil, err)
  126. }
  127. if currentState != nil {
  128. if newState.v1Cert != nil {
  129. if currentState.v1Cert == nil {
  130. //adding certs is fine, actually. Networks-in-common confirmed in newCertState().
  131. } else {
  132. // did IP in cert change? if so, don't set
  133. if !slices.Equal(currentState.v1Cert.Networks(), newState.v1Cert.Networks()) {
  134. return util.NewContextualError(
  135. "Networks in new cert was different from old",
  136. m{"new_networks": newState.v1Cert.Networks(), "old_networks": currentState.v1Cert.Networks(), "cert_version": cert.Version1},
  137. errCertNeedsRestart,
  138. )
  139. }
  140. if currentState.v1Cert.Curve() != newState.v1Cert.Curve() {
  141. return util.NewContextualError(
  142. "Curve in new v1 cert was different from old",
  143. m{"new_curve": newState.v1Cert.Curve(), "old_curve": currentState.v1Cert.Curve(), "cert_version": cert.Version1},
  144. errCertNeedsRestart,
  145. )
  146. }
  147. }
  148. }
  149. if newState.v2Cert != nil {
  150. if currentState.v2Cert == nil {
  151. //adding certs is fine, actually
  152. } else {
  153. // did IP in cert change? if so, don't set
  154. if !slices.Equal(currentState.v2Cert.Networks(), newState.v2Cert.Networks()) {
  155. return util.NewContextualError(
  156. "Networks in new cert was different from old",
  157. m{"new_networks": newState.v2Cert.Networks(), "old_networks": currentState.v2Cert.Networks(), "cert_version": cert.Version2},
  158. errCertNeedsRestart,
  159. )
  160. }
  161. if currentState.v2Cert.Curve() != newState.v2Cert.Curve() {
  162. return util.NewContextualError(
  163. "Curve in new cert was different from old",
  164. m{"new_curve": newState.v2Cert.Curve(), "old_curve": currentState.v2Cert.Curve(), "cert_version": cert.Version2},
  165. errCertNeedsRestart,
  166. )
  167. }
  168. }
  169. } else if currentState.v2Cert != nil {
  170. //newState.v1Cert is non-nil bc empty certstates aren't permitted
  171. if newState.v1Cert == nil {
  172. return util.NewContextualError("v1 and v2 certs are nil, this should be impossible", nil, err)
  173. }
  174. //if we're going to v1-only, we need to make sure we didn't orphan any v2-cert vpnaddrs
  175. if !slices.Equal(currentState.v2Cert.Networks(), newState.v1Cert.Networks()) {
  176. return util.NewContextualError(
  177. "Removing a V2 cert is not permitted unless it has identical networks to the new V1 cert",
  178. m{"new_v1_networks": newState.v1Cert.Networks(), "old_v2_networks": currentState.v2Cert.Networks()},
  179. errCertNeedsRestart,
  180. )
  181. }
  182. }
  183. }
  184. p.cs.Store(newState)
  185. if initial {
  186. p.l.Debug("Client nebula certificate(s)", "cert", newState)
  187. } else {
  188. p.l.Info("Client certificate(s) refreshed from disk", "cert", newState)
  189. }
  190. return nil
  191. }
  192. func (p *PKI) reloadCAPool(c *config.C) *util.ContextualError {
  193. caPool, err := loadCAPoolFromConfig(p.l, c)
  194. if err != nil {
  195. return util.NewContextualError("Failed to load ca from config", nil, err)
  196. }
  197. p.caPool.Store(caPool)
  198. p.l.Debug("Trusted CA fingerprints", "fingerprints", caPool.GetFingerprints())
  199. return nil
  200. }
  201. func (cs *CertState) GetDefaultCertificate() cert.Certificate {
  202. c := cs.getCertificate(cs.initiatingVersion)
  203. if c == nil {
  204. panic("No default certificate found")
  205. }
  206. return c
  207. }
  208. // DefaultVersion returns the preferred cert version for initiating handshakes.
  209. func (cs *CertState) DefaultVersion() cert.Version { return cs.initiatingVersion }
  210. // GetCredential returns the pre-computed handshake credential for the given version, or nil.
  211. func (cs *CertState) GetCredential(v cert.Version) *handshake.Credential {
  212. switch v {
  213. case cert.Version1:
  214. return cs.v1Credential
  215. case cert.Version2:
  216. return cs.v2Credential
  217. }
  218. return nil
  219. }
  220. func (cs *CertState) getCertificate(v cert.Version) cert.Certificate {
  221. switch v {
  222. case cert.Version1:
  223. return cs.v1Cert
  224. case cert.Version2:
  225. return cs.v2Cert
  226. }
  227. return nil
  228. }
  229. // newCipherSuite builds the noise.CipherSuite for the given curve and cipher.
  230. // When fips140Enforced is true (FIPS 140-only mode), non-approved algorithms
  231. // (Curve25519 and ChaChaPoly) are rejected with an error. Callers pass
  232. // fips140.Enforced() for fips140Enforced.
  233. func newCipherSuite(curve cert.Curve, pkcs11backed bool, cipher string, fips140Enforced bool) (noise.CipherSuite, error) {
  234. var dhFunc noise.DHFunc
  235. switch curve {
  236. case cert.Curve_CURVE25519:
  237. if fips140Enforced {
  238. return nil, errors.New("pki: use of Curve25519 is not allowed in FIPS 140-only mode")
  239. }
  240. dhFunc = noise.DH25519
  241. case cert.Curve_P256:
  242. if pkcs11backed {
  243. dhFunc = noiseutil.DHP256PKCS11
  244. } else {
  245. dhFunc = noiseutil.DHP256
  246. }
  247. default:
  248. return nil, fmt.Errorf("unsupported curve: %s", curve)
  249. }
  250. if cipher == "chachapoly" {
  251. if fips140Enforced {
  252. return nil, errors.New("pki: use of ChaChaPoly is not allowed in FIPS 140-only mode")
  253. }
  254. return noise.NewCipherSuite(dhFunc, noise.CipherChaChaPoly, noise.HashSHA256), nil
  255. }
  256. return noise.NewCipherSuite(dhFunc, noiseutil.CipherAESGCM, noise.HashSHA256), nil
  257. }
  258. func (cs *CertState) String() string {
  259. b, err := cs.MarshalJSON()
  260. if err != nil {
  261. return fmt.Sprintf("error marshaling certificate state: %v", err)
  262. }
  263. return string(b)
  264. }
  265. func (cs *CertState) MarshalJSON() ([]byte, error) {
  266. msg := []json.RawMessage{}
  267. if cs.v1Cert != nil {
  268. b, err := cs.v1Cert.MarshalJSON()
  269. if err != nil {
  270. return nil, err
  271. }
  272. msg = append(msg, b)
  273. }
  274. if cs.v2Cert != nil {
  275. b, err := cs.v2Cert.MarshalJSON()
  276. if err != nil {
  277. return nil, err
  278. }
  279. msg = append(msg, b)
  280. }
  281. return json.Marshal(msg)
  282. }
  283. func newCertStateFromConfig(c *config.C, cipher string) (*CertState, error) {
  284. var err error
  285. privPathOrPEM := c.GetString("pki.key", "")
  286. if privPathOrPEM == "" {
  287. return nil, errors.New("no pki.key path or PEM data provided")
  288. }
  289. rawKey, curve, isPkcs11, err := loadPrivateKey(privPathOrPEM)
  290. if err != nil {
  291. return nil, err
  292. }
  293. var rawCert []byte
  294. pubPathOrPEM := c.GetString("pki.cert", "")
  295. if pubPathOrPEM == "" {
  296. return nil, errors.New("no pki.cert path or PEM data provided")
  297. }
  298. if strings.Contains(pubPathOrPEM, "-----BEGIN") {
  299. rawCert = []byte(pubPathOrPEM)
  300. pubPathOrPEM = "<inline>"
  301. } else {
  302. rawCert, err = os.ReadFile(pubPathOrPEM)
  303. if err != nil {
  304. return nil, fmt.Errorf("unable to read pki.cert file %s: %s", pubPathOrPEM, err)
  305. }
  306. }
  307. var crt, v1, v2 cert.Certificate
  308. for {
  309. // Load the certificate
  310. crt, rawCert, err = loadCertificate(rawCert)
  311. if err != nil {
  312. return nil, err
  313. }
  314. if fips140.Enforced() && crt.Curve() != cert.Curve_P256 {
  315. return nil, fmt.Errorf("pki: use of %s is not allowed in FIPS 140-only mode", crt.Curve())
  316. }
  317. switch crt.Version() {
  318. case cert.Version1:
  319. if v1 != nil {
  320. return nil, fmt.Errorf("v1 certificate already found in pki.cert")
  321. }
  322. v1 = crt
  323. case cert.Version2:
  324. if v2 != nil {
  325. return nil, fmt.Errorf("v2 certificate already found in pki.cert")
  326. }
  327. v2 = crt
  328. default:
  329. return nil, fmt.Errorf("unknown certificate version %v", crt.Version())
  330. }
  331. if len(rawCert) == 0 || strings.TrimSpace(string(rawCert)) == "" {
  332. break
  333. }
  334. }
  335. if v1 == nil && v2 == nil {
  336. return nil, errors.New("no certificates found in pki.cert")
  337. }
  338. useInitiatingVersion := uint32(1)
  339. if v1 == nil {
  340. // The only condition that requires v2 as the default is if only a v2 certificate is present
  341. // We do this to avoid having to configure it specifically in the config file
  342. useInitiatingVersion = 2
  343. }
  344. rawInitiatingVersion := c.GetUint32("pki.initiating_version", useInitiatingVersion)
  345. var initiatingVersion cert.Version
  346. switch rawInitiatingVersion {
  347. case 1:
  348. if v1 == nil {
  349. return nil, fmt.Errorf("can not use pki.initiating_version 1 without a v1 certificate in pki.cert")
  350. }
  351. initiatingVersion = cert.Version1
  352. case 2:
  353. initiatingVersion = cert.Version2
  354. default:
  355. return nil, fmt.Errorf("unknown pki.initiating_version: %v", rawInitiatingVersion)
  356. }
  357. return newCertState(initiatingVersion, v1, v2, isPkcs11, curve, rawKey, cipher)
  358. }
  359. func newCertState(dv cert.Version, v1, v2 cert.Certificate, pkcs11backed bool, privateKeyCurve cert.Curve, privateKey []byte, cipher string) (*CertState, error) {
  360. cs := CertState{
  361. privateKey: privateKey,
  362. pkcs11Backed: pkcs11backed,
  363. cipher: cipher,
  364. myVpnNetworksTable: new(bart.Lite),
  365. myVpnAddrsTable: new(bart.Lite),
  366. myVpnBroadcastAddrsTable: new(bart.Lite),
  367. }
  368. if v1 != nil && v2 != nil {
  369. if !slices.Equal(v1.PublicKey(), v2.PublicKey()) {
  370. return nil, util.NewContextualError("v1 and v2 public keys are not the same, ignoring", nil, nil)
  371. }
  372. if v1.Curve() != v2.Curve() {
  373. return nil, util.NewContextualError("v1 and v2 curve are not the same, ignoring", nil, nil)
  374. }
  375. if v1.Networks()[0] != v2.Networks()[0] {
  376. return nil, util.NewContextualError("v1 and v2 networks are not the same", nil, nil)
  377. }
  378. cs.initiatingVersion = dv
  379. }
  380. if v1 != nil {
  381. if pkcs11backed {
  382. //NOTE: We do not currently have a method to verify a public private key pair when the private key is in an hsm
  383. } else {
  384. if err := v1.VerifyPrivateKey(privateKeyCurve, privateKey); err != nil {
  385. return nil, fmt.Errorf("private key is not a pair with public key in nebula cert: %w", err)
  386. }
  387. }
  388. v1hs, err := v1.MarshalForHandshakes()
  389. if err != nil {
  390. return nil, fmt.Errorf("error marshalling v1 certificate for handshake: %w", err)
  391. }
  392. ncs, err := newCipherSuite(v1.Curve(), pkcs11backed, cipher, fips140.Enforced())
  393. if err != nil {
  394. return nil, err
  395. }
  396. cs.v1Cert = v1
  397. cs.v1Credential = handshake.NewCredential(v1, v1hs, privateKey, ncs)
  398. if cs.initiatingVersion == 0 {
  399. cs.initiatingVersion = cert.Version1
  400. }
  401. }
  402. if v2 != nil {
  403. if pkcs11backed {
  404. //NOTE: We do not currently have a method to verify a public private key pair when the private key is in an hsm
  405. } else {
  406. if err := v2.VerifyPrivateKey(privateKeyCurve, privateKey); err != nil {
  407. return nil, fmt.Errorf("private key is not a pair with public key in nebula cert: %w", err)
  408. }
  409. }
  410. v2hs, err := v2.MarshalForHandshakes()
  411. if err != nil {
  412. return nil, fmt.Errorf("error marshalling v2 certificate for handshake: %w", err)
  413. }
  414. ncs, err := newCipherSuite(v2.Curve(), pkcs11backed, cipher, fips140.Enforced())
  415. if err != nil {
  416. return nil, err
  417. }
  418. cs.v2Cert = v2
  419. cs.v2Credential = handshake.NewCredential(v2, v2hs, privateKey, ncs)
  420. if cs.initiatingVersion == 0 {
  421. cs.initiatingVersion = cert.Version2
  422. }
  423. }
  424. var crt cert.Certificate
  425. crt = cs.getCertificate(cert.Version2)
  426. if crt == nil {
  427. // v2 certificates are a superset, only look at v1 if its all we have
  428. crt = cs.getCertificate(cert.Version1)
  429. }
  430. for _, network := range crt.Networks() {
  431. cs.myVpnNetworks = append(cs.myVpnNetworks, network)
  432. cs.myVpnNetworksTable.Insert(network)
  433. cs.myVpnAddrs = append(cs.myVpnAddrs, network.Addr())
  434. cs.myVpnAddrsTable.Insert(netip.PrefixFrom(network.Addr(), network.Addr().BitLen()))
  435. if network.Addr().Is4() {
  436. addr := network.Masked().Addr().As4()
  437. mask := net.CIDRMask(network.Bits(), network.Addr().BitLen())
  438. binary.BigEndian.PutUint32(addr[:], binary.BigEndian.Uint32(addr[:])|^binary.BigEndian.Uint32(mask))
  439. cs.myVpnBroadcastAddrsTable.Insert(netip.PrefixFrom(netip.AddrFrom4(addr), network.Addr().BitLen()))
  440. }
  441. }
  442. return &cs, nil
  443. }
  444. func loadPrivateKey(privPathOrPEM string) (rawKey []byte, curve cert.Curve, isPkcs11 bool, err error) {
  445. var pemPrivateKey []byte
  446. if strings.Contains(privPathOrPEM, "-----BEGIN") {
  447. pemPrivateKey = []byte(privPathOrPEM)
  448. privPathOrPEM = "<inline>"
  449. rawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)
  450. if err != nil {
  451. return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
  452. }
  453. } else if strings.HasPrefix(privPathOrPEM, "pkcs11:") {
  454. rawKey = []byte(privPathOrPEM)
  455. return rawKey, cert.Curve_P256, true, nil
  456. } else {
  457. pemPrivateKey, err = os.ReadFile(privPathOrPEM)
  458. if err != nil {
  459. return nil, curve, false, fmt.Errorf("unable to read pki.key file %s: %s", privPathOrPEM, err)
  460. }
  461. rawKey, _, curve, err = cert.UnmarshalPrivateKeyFromPEM(pemPrivateKey)
  462. if err != nil {
  463. return nil, curve, false, fmt.Errorf("error while unmarshaling pki.key %s: %s", privPathOrPEM, err)
  464. }
  465. }
  466. return
  467. }
  468. func loadCertificate(b []byte) (cert.Certificate, []byte, error) {
  469. c, b, err := cert.UnmarshalCertificateFromPEM(b)
  470. if err != nil {
  471. return nil, b, fmt.Errorf("error while unmarshaling pki.cert: %w", err)
  472. }
  473. if c.Expired(time.Now()) {
  474. return nil, b, fmt.Errorf("nebula certificate for this host is expired")
  475. }
  476. if len(c.Networks()) == 0 {
  477. return nil, b, fmt.Errorf("no networks encoded in certificate")
  478. }
  479. if c.IsCA() {
  480. return nil, b, fmt.Errorf("host certificate is a CA certificate")
  481. }
  482. return c, b, nil
  483. }
  484. func loadCAPoolFromConfig(l *slog.Logger, c *config.C) (*cert.CAPool, error) {
  485. caPathOrPEM := c.GetString("pki.ca", "")
  486. if caPathOrPEM == "" {
  487. return nil, errors.New("no pki.ca path or PEM data provided")
  488. }
  489. var caReader io.ReadCloser
  490. var err error
  491. if strings.Contains(caPathOrPEM, "-----BEGIN") {
  492. caReader = io.NopCloser(strings.NewReader(caPathOrPEM))
  493. } else {
  494. caReader, err = os.Open(caPathOrPEM)
  495. if err != nil {
  496. return nil, fmt.Errorf("unable to read pki.ca file %s: %s", caPathOrPEM, err)
  497. }
  498. }
  499. defer caReader.Close()
  500. caPool, err := cert.NewCAPoolFromPEMReader(caReader)
  501. if errors.Is(err, cert.ErrExpired) {
  502. var expired int
  503. for _, crt := range caPool.CAs {
  504. if crt.Certificate.Expired(time.Now()) {
  505. expired++
  506. l.Warn("expired certificate present in CA pool", "cert", crt)
  507. }
  508. }
  509. if expired >= len(caPool.CAs) {
  510. return nil, errors.New("no valid CA certificates present")
  511. }
  512. } else if err != nil {
  513. return nil, fmt.Errorf("error while adding CA certificate to CA trust store: %s", err)
  514. }
  515. bl := c.GetStringSlice("pki.blocklist", []string{})
  516. if len(bl) > 0 {
  517. for _, fp := range bl {
  518. caPool.BlocklistFingerprint(fp)
  519. }
  520. l.Info("Blocklisted certificates", "fingerprintCount", len(bl))
  521. }
  522. return caPool, nil
  523. }