1
0

pki.go 16 KB

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