pki.go 15 KB

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