ca.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package cert
  2. import (
  3. "errors"
  4. "fmt"
  5. "strings"
  6. "time"
  7. )
  8. type NebulaCAPool struct {
  9. CAs map[string]*NebulaCertificate
  10. certBlocklist map[string]struct{}
  11. }
  12. // NewCAPool creates a CAPool
  13. func NewCAPool() *NebulaCAPool {
  14. ca := NebulaCAPool{
  15. CAs: make(map[string]*NebulaCertificate),
  16. certBlocklist: make(map[string]struct{}),
  17. }
  18. return &ca
  19. }
  20. // NewCAPoolFromBytes will create a new CA pool from the provided
  21. // input bytes, which must be a PEM-encoded set of nebula certificates.
  22. // If the pool contains unsupported certificates, they will generate warnings
  23. // in the []error return arg.
  24. // If the pool contains any expired certificates, an ErrExpired will be
  25. // returned along with the pool. The caller must handle any such errors.
  26. func NewCAPoolFromBytes(caPEMs []byte) (*NebulaCAPool, []error, error) {
  27. pool := NewCAPool()
  28. var err error
  29. var warnings []error
  30. good := 0
  31. for {
  32. caPEMs, err = pool.AddCACertificate(caPEMs)
  33. if errors.Is(err, ErrExpired) {
  34. warnings = append(warnings, err)
  35. } else if errors.Is(err, ErrInvalidPEMCertificateUnsupported) {
  36. warnings = append(warnings, err)
  37. } else if err != nil {
  38. return nil, warnings, err
  39. } else {
  40. // Only consider a good certificate if there were no errors present
  41. good++
  42. }
  43. if len(caPEMs) == 0 || strings.TrimSpace(string(caPEMs)) == "" {
  44. break
  45. }
  46. }
  47. if good == 0 {
  48. return nil, warnings, errors.New("no valid CA certificates present")
  49. }
  50. return pool, warnings, nil
  51. }
  52. // AddCACertificate verifies a Nebula CA certificate and adds it to the pool
  53. // Only the first pem encoded object will be consumed, any remaining bytes are returned.
  54. // Parsed certificates will be verified and must be a CA
  55. func (ncp *NebulaCAPool) AddCACertificate(pemBytes []byte) ([]byte, error) {
  56. c, pemBytes, err := UnmarshalNebulaCertificateFromPEM(pemBytes)
  57. if err != nil {
  58. return pemBytes, err
  59. }
  60. if !c.Details.IsCA {
  61. return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotCA)
  62. }
  63. if !c.CheckSignature(c.Details.PublicKey) {
  64. return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrNotSelfSigned)
  65. }
  66. sum, err := c.Sha256Sum()
  67. if err != nil {
  68. return pemBytes, fmt.Errorf("could not calculate shasum for provided CA; error: %s; %s", err, c.Details.Name)
  69. }
  70. ncp.CAs[sum] = c
  71. if c.Expired(time.Now()) {
  72. return pemBytes, fmt.Errorf("%s: %w", c.Details.Name, ErrExpired)
  73. }
  74. return pemBytes, nil
  75. }
  76. // BlocklistFingerprint adds a cert fingerprint to the blocklist
  77. func (ncp *NebulaCAPool) BlocklistFingerprint(f string) {
  78. ncp.certBlocklist[f] = struct{}{}
  79. }
  80. // ResetCertBlocklist removes all previously blocklisted cert fingerprints
  81. func (ncp *NebulaCAPool) ResetCertBlocklist() {
  82. ncp.certBlocklist = make(map[string]struct{})
  83. }
  84. // NOTE: This uses an internal cache for Sha256Sum() that will not be invalidated
  85. // automatically if you manually change any fields in the NebulaCertificate.
  86. func (ncp *NebulaCAPool) IsBlocklisted(c *NebulaCertificate) bool {
  87. return ncp.isBlocklistedWithCache(c, false)
  88. }
  89. // IsBlocklisted returns true if the fingerprint fails to generate or has been explicitly blocklisted
  90. func (ncp *NebulaCAPool) isBlocklistedWithCache(c *NebulaCertificate, useCache bool) bool {
  91. h, err := c.sha256SumWithCache(useCache)
  92. if err != nil {
  93. return true
  94. }
  95. if _, ok := ncp.certBlocklist[h]; ok {
  96. return true
  97. }
  98. return false
  99. }
  100. // GetCAForCert attempts to return the signing certificate for the provided certificate.
  101. // No signature validation is performed
  102. func (ncp *NebulaCAPool) GetCAForCert(c *NebulaCertificate) (*NebulaCertificate, error) {
  103. if c.Details.Issuer == "" {
  104. return nil, fmt.Errorf("no issuer in certificate")
  105. }
  106. signer, ok := ncp.CAs[c.Details.Issuer]
  107. if ok {
  108. return signer, nil
  109. }
  110. return nil, fmt.Errorf("could not find ca for the certificate")
  111. }
  112. // GetFingerprints returns an array of trusted CA fingerprints
  113. func (ncp *NebulaCAPool) GetFingerprints() []string {
  114. fp := make([]string, len(ncp.CAs))
  115. i := 0
  116. for k := range ncp.CAs {
  117. fp[i] = k
  118. i++
  119. }
  120. return fp
  121. }