1
0

ca_pool.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. package cert
  2. import (
  3. "bufio"
  4. "bytes"
  5. "encoding/pem"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/netip"
  10. "slices"
  11. "time"
  12. )
  13. type CAPool struct {
  14. CAs map[string]*CachedCertificate
  15. certBlocklist map[string]struct{}
  16. }
  17. // NewCAPool creates an empty CAPool
  18. func NewCAPool() *CAPool {
  19. ca := CAPool{
  20. CAs: make(map[string]*CachedCertificate),
  21. certBlocklist: make(map[string]struct{}),
  22. }
  23. return &ca
  24. }
  25. // NewCAPoolFromPEM will create a new CA pool from the provided
  26. // input bytes, which must be a PEM-encoded set of nebula certificates.
  27. // If the pool contains any expired certificates, an ErrExpired will be
  28. // returned along with the pool. The caller must handle any such errors.
  29. func NewCAPoolFromPEM(caPEMs []byte) (*CAPool, error) {
  30. return NewCAPoolFromPEMReader(bytes.NewReader(caPEMs))
  31. }
  32. // NewCAPoolFromPEMReader will create a new CA pool from the provided reader.
  33. // The reader must contain a PEM-encoded set of nebula certificates.
  34. func NewCAPoolFromPEMReader(r io.Reader) (*CAPool, error) {
  35. pool := NewCAPool()
  36. var expired bool
  37. scanner := bufio.NewScanner(r)
  38. scanner.Split(SplitPEM)
  39. for scanner.Scan() {
  40. pemBytes := scanner.Bytes()
  41. block, rest := pem.Decode(pemBytes)
  42. if len(bytes.TrimSpace(rest)) > 0 {
  43. return nil, ErrInvalidPEMBlock
  44. }
  45. if block == nil {
  46. return nil, ErrInvalidPEMBlock
  47. }
  48. c, err := unmarshalCertificateBlock(block)
  49. if err != nil {
  50. return nil, err
  51. }
  52. err = pool.AddCA(c)
  53. if errors.Is(err, ErrExpired) {
  54. expired = true
  55. continue
  56. } else if err != nil {
  57. return nil, err
  58. }
  59. }
  60. if err := scanner.Err(); err != nil {
  61. return nil, ErrInvalidPEMBlock
  62. }
  63. if expired {
  64. return pool, ErrExpired
  65. }
  66. return pool, nil
  67. }
  68. // AddCAFromPEM verifies a Nebula CA certificate and adds it to the pool.
  69. // Only the first pem encoded object will be consumed, any remaining bytes are returned.
  70. // Parsed certificates will be verified and must be a CA
  71. func (ncp *CAPool) AddCAFromPEM(pemBytes []byte) ([]byte, error) {
  72. c, pemBytes, err := UnmarshalCertificateFromPEM(pemBytes)
  73. if err != nil {
  74. return pemBytes, err
  75. }
  76. err = ncp.AddCA(c)
  77. if err != nil {
  78. return pemBytes, err
  79. }
  80. return pemBytes, nil
  81. }
  82. // AddCA verifies a Nebula CA certificate and adds it to the pool.
  83. func (ncp *CAPool) AddCA(c Certificate) error {
  84. if !c.IsCA() {
  85. return fmt.Errorf("%s: %w", c.Name(), ErrNotCA)
  86. }
  87. if !c.CheckSignature(c.PublicKey()) {
  88. return fmt.Errorf("%s: %w", c.Name(), ErrNotSelfSigned)
  89. }
  90. sum, err := c.Fingerprint()
  91. if err != nil {
  92. return fmt.Errorf("could not calculate fingerprint for provided CA; error: %w; %s", err, c.Name())
  93. }
  94. cc := &CachedCertificate{
  95. Certificate: c,
  96. Fingerprint: sum,
  97. InvertedGroups: make(map[string]struct{}),
  98. }
  99. for _, g := range c.Groups() {
  100. cc.InvertedGroups[g] = struct{}{}
  101. }
  102. ncp.CAs[sum] = cc
  103. if c.Expired(time.Now()) {
  104. return fmt.Errorf("%s: %w", c.Name(), ErrExpired)
  105. }
  106. return nil
  107. }
  108. // BlocklistFingerprint adds a cert fingerprint to the blocklist
  109. func (ncp *CAPool) BlocklistFingerprint(f string) {
  110. ncp.certBlocklist[f] = struct{}{}
  111. }
  112. // ResetCertBlocklist removes all previously blocklisted cert fingerprints
  113. func (ncp *CAPool) ResetCertBlocklist() {
  114. ncp.certBlocklist = make(map[string]struct{})
  115. }
  116. // IsBlocklisted tests the provided fingerprint against the pools blocklist.
  117. // Returns true if the fingerprint is blocked.
  118. func (ncp *CAPool) IsBlocklisted(fingerprint string) bool {
  119. if _, ok := ncp.certBlocklist[fingerprint]; ok {
  120. return true
  121. }
  122. return false
  123. }
  124. // VerifyCertificate verifies the certificate is valid and is signed by a trusted CA in the pool.
  125. // If the certificate is valid then the returned CachedCertificate can be used in subsequent verification attempts
  126. // to increase performance.
  127. func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCertificate, error) {
  128. if c == nil {
  129. return nil, fmt.Errorf("no certificate")
  130. }
  131. fp, err := c.Fingerprint()
  132. if err != nil {
  133. return nil, fmt.Errorf("could not calculate fingerprint to verify: %w", err)
  134. }
  135. signer, err := ncp.verify(c, now, fp, "")
  136. if err != nil {
  137. return nil, err
  138. }
  139. // Pre nebula v1.10.3 could generate signatures in either high or low s form and validation
  140. // of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form
  141. // but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we
  142. // need to test both forms until such a time comes that we enforce low-s form on signature validation.
  143. fp2, err := CalculateAlternateFingerprint(c)
  144. if err != nil {
  145. return nil, fmt.Errorf("could not calculate alternate fingerprint to verify: %w", err)
  146. }
  147. if fp2 != "" && ncp.IsBlocklisted(fp2) {
  148. return nil, ErrBlockListed
  149. }
  150. cc := CachedCertificate{
  151. Certificate: c,
  152. InvertedGroups: make(map[string]struct{}),
  153. Fingerprint: fp,
  154. fingerprint2: fp2,
  155. signerFingerprint: signer.Fingerprint,
  156. }
  157. for _, g := range c.Groups() {
  158. cc.InvertedGroups[g] = struct{}{}
  159. }
  160. return &cc, nil
  161. }
  162. // VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
  163. // is a cheaper operation to perform as a result.
  164. func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
  165. // Check any available alternate fingerprint forms for this certificate, re P256 high-s/low-s
  166. if c.fingerprint2 != "" && ncp.IsBlocklisted(c.fingerprint2) {
  167. return ErrBlockListed
  168. }
  169. _, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
  170. return err
  171. }
  172. func (ncp *CAPool) verify(c Certificate, now time.Time, certFp string, signerFp string) (*CachedCertificate, error) {
  173. if ncp.IsBlocklisted(certFp) {
  174. return nil, ErrBlockListed
  175. }
  176. signer, err := ncp.GetCAForCert(c)
  177. if err != nil {
  178. return nil, err
  179. }
  180. if signer.Certificate.Curve() != c.Curve() {
  181. return nil, ErrCurveMismatch
  182. }
  183. if signer.Certificate.Expired(now) {
  184. return nil, ErrRootExpired
  185. }
  186. if c.Expired(now) {
  187. return nil, ErrExpired
  188. }
  189. // If we are checking a cached certificate then we can bail early here
  190. // Either the root is no longer trusted or everything is fine
  191. if len(signerFp) > 0 {
  192. if signerFp != signer.Fingerprint {
  193. return nil, ErrFingerprintMismatch
  194. }
  195. return signer, nil
  196. }
  197. if !c.CheckSignature(signer.Certificate.PublicKey()) {
  198. return nil, ErrSignatureMismatch
  199. }
  200. err = CheckCAConstraints(signer.Certificate, c)
  201. if err != nil {
  202. return nil, err
  203. }
  204. return signer, nil
  205. }
  206. // GetCAForCert attempts to return the signing certificate for the provided certificate.
  207. // No signature validation is performed
  208. func (ncp *CAPool) GetCAForCert(c Certificate) (*CachedCertificate, error) {
  209. issuer := c.Issuer()
  210. if issuer == "" {
  211. return nil, fmt.Errorf("no issuer in certificate")
  212. }
  213. signer, ok := ncp.CAs[issuer]
  214. if ok {
  215. return signer, nil
  216. }
  217. return nil, ErrCaNotFound
  218. }
  219. // GetFingerprints returns an array of trusted CA fingerprints
  220. func (ncp *CAPool) GetFingerprints() []string {
  221. fp := make([]string, len(ncp.CAs))
  222. i := 0
  223. for k := range ncp.CAs {
  224. fp[i] = k
  225. i++
  226. }
  227. return fp
  228. }
  229. // CheckCAConstraints returns an error if the sub certificate violates constraints present in the signer certificate.
  230. func CheckCAConstraints(signer Certificate, sub Certificate) error {
  231. return checkCAConstraints(signer, sub.NotBefore(), sub.NotAfter(), sub.Groups(), sub.Networks(), sub.UnsafeNetworks())
  232. }
  233. // checkCAConstraints is a very generic function allowing both Certificates and TBSCertificates to be tested.
  234. func checkCAConstraints(signer Certificate, notBefore, notAfter time.Time, groups []string, networks, unsafeNetworks []netip.Prefix) error {
  235. // Make sure this cert isn't valid after the root
  236. if notAfter.After(signer.NotAfter()) {
  237. return fmt.Errorf("certificate expires after signing certificate")
  238. }
  239. // Make sure this cert wasn't valid before the root
  240. if notBefore.Before(signer.NotBefore()) {
  241. return fmt.Errorf("certificate is valid before the signing certificate")
  242. }
  243. // If the signer has a limited set of groups make sure the cert only contains a subset
  244. signerGroups := signer.Groups()
  245. if len(signerGroups) > 0 {
  246. for _, g := range groups {
  247. if !slices.Contains(signerGroups, g) {
  248. return fmt.Errorf("certificate contained a group not present on the signing ca: %s", g)
  249. }
  250. }
  251. }
  252. // If the signer has a limited set of ip ranges to issue from make sure the cert only contains a subset
  253. signingNetworks := signer.Networks()
  254. if len(signingNetworks) > 0 {
  255. for _, certNetwork := range networks {
  256. found := false
  257. for _, signingNetwork := range signingNetworks {
  258. if signingNetwork.Contains(certNetwork.Addr()) && signingNetwork.Bits() <= certNetwork.Bits() {
  259. found = true
  260. break
  261. }
  262. }
  263. if !found {
  264. return fmt.Errorf("certificate contained a network assignment outside the limitations of the signing ca: %s", certNetwork.String())
  265. }
  266. }
  267. }
  268. // If the signer has a limited set of subnet ranges to issue from make sure the cert only contains a subset
  269. signingUnsafeNetworks := signer.UnsafeNetworks()
  270. if len(signingUnsafeNetworks) > 0 {
  271. for _, certUnsafeNetwork := range unsafeNetworks {
  272. found := false
  273. for _, caNetwork := range signingUnsafeNetworks {
  274. if caNetwork.Contains(certUnsafeNetwork.Addr()) && caNetwork.Bits() <= certUnsafeNetwork.Bits() {
  275. found = true
  276. break
  277. }
  278. }
  279. if !found {
  280. return fmt.Errorf("certificate contained an unsafe network assignment outside the limitations of the signing ca: %s", certUnsafeNetwork.String())
  281. }
  282. }
  283. }
  284. return nil
  285. }