ca_pool_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. package cert
  2. import (
  3. "bytes"
  4. "io"
  5. "net/netip"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/slackhq/nebula/cert/p256"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. )
  13. func TestNewCAPoolFromBytes(t *testing.T) {
  14. noNewLines := `
  15. # Current provisional, Remove once everything moves over to the real root.
  16. -----BEGIN NEBULA CERTIFICATE-----
  17. Cj4KDm5lYnVsYSByb290IGNhKM0cMM24zPCvBzogV24YEw5YiqeI/oYo8XXFsoo+
  18. PBmiOafNJhLacf9rsspAARJAz9OAnh8TKAUKix1kKVMyQU4iM3LsFfZRf6ODWXIf
  19. 2qWMpB6fpd3PSoVYziPoOt2bIHIFLlgRLPJz3I3xBEdBCQ==
  20. -----END NEBULA CERTIFICATE-----
  21. # root-ca01
  22. -----BEGIN NEBULA CERTIFICATE-----
  23. CkEKEW5lYnVsYSByb290IGNhIDAxKM0cMM24zPCvBzogPzbWTxt8ZgXPQEwup7Br
  24. BrtIt1O0q5AuTRT3+t2x1VJAARJAZ+2ib23qBXjdy49oU1YysrwuKkWWKrtJ7Jye
  25. rFBQpDXikOukhQD/mfkloFwJ+Yjsfru7IpTN4ZfjXL+kN/2sCA==
  26. -----END NEBULA CERTIFICATE-----
  27. `
  28. withNewLines := `
  29. # Current provisional, Remove once everything moves over to the real root.
  30. -----BEGIN NEBULA CERTIFICATE-----
  31. Cj4KDm5lYnVsYSByb290IGNhKM0cMM24zPCvBzogV24YEw5YiqeI/oYo8XXFsoo+
  32. PBmiOafNJhLacf9rsspAARJAz9OAnh8TKAUKix1kKVMyQU4iM3LsFfZRf6ODWXIf
  33. 2qWMpB6fpd3PSoVYziPoOt2bIHIFLlgRLPJz3I3xBEdBCQ==
  34. -----END NEBULA CERTIFICATE-----
  35. # root-ca01
  36. -----BEGIN NEBULA CERTIFICATE-----
  37. CkEKEW5lYnVsYSByb290IGNhIDAxKM0cMM24zPCvBzogPzbWTxt8ZgXPQEwup7Br
  38. BrtIt1O0q5AuTRT3+t2x1VJAARJAZ+2ib23qBXjdy49oU1YysrwuKkWWKrtJ7Jye
  39. rFBQpDXikOukhQD/mfkloFwJ+Yjsfru7IpTN4ZfjXL+kN/2sCA==
  40. -----END NEBULA CERTIFICATE-----
  41. `
  42. expired := `
  43. # expired certificate
  44. -----BEGIN NEBULA CERTIFICATE-----
  45. CjMKB2V4cGlyZWQozRwwzRw6ICJSG94CqX8wn5I65Pwn25V6HftVfWeIySVtp2DA
  46. 7TY/QAESQMaAk5iJT5EnQwK524ZaaHGEJLUqqbh5yyOHhboIGiVTWkFeH3HccTW8
  47. Tq5a8AyWDQdfXbtEZ1FwabeHfH5Asw0=
  48. -----END NEBULA CERTIFICATE-----
  49. `
  50. p256 := `
  51. # p256 certificate
  52. -----BEGIN NEBULA CERTIFICATE-----
  53. CmQKEG5lYnVsYSBQMjU2IHRlc3QozRwwzbjM8K8HOkEEdrmmg40zQp44AkMq6DZp
  54. k+coOv04r+zh33ISyhbsafnYduN17p2eD7CmHvHuerguXD9f32gcxo/KsFCKEjMe
  55. +0ABoAYBEkcwRQIgVoTg38L7uWku9xQgsr06kxZ/viQLOO/w1Qj1vFUEnhcCIQCq
  56. 75SjTiV92kv/1GcbT3wWpAZQQDBiUHVMVmh1822szA==
  57. -----END NEBULA CERTIFICATE-----
  58. `
  59. rootCA := certificateV1{
  60. details: detailsV1{
  61. name: "nebula root ca",
  62. },
  63. }
  64. rootCA01 := certificateV1{
  65. details: detailsV1{
  66. name: "nebula root ca 01",
  67. },
  68. }
  69. rootCAP256 := certificateV1{
  70. details: detailsV1{
  71. name: "nebula P256 test",
  72. },
  73. }
  74. p, err := NewCAPoolFromPEM([]byte(noNewLines))
  75. require.NoError(t, err)
  76. assert.Equal(t, p.CAs["ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300"].Certificate.Name(), rootCA.details.name)
  77. assert.Equal(t, p.CAs["04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e"].Certificate.Name(), rootCA01.details.name)
  78. pp, err := NewCAPoolFromPEM([]byte(withNewLines))
  79. require.NoError(t, err)
  80. assert.Equal(t, pp.CAs["ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300"].Certificate.Name(), rootCA.details.name)
  81. assert.Equal(t, pp.CAs["04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e"].Certificate.Name(), rootCA01.details.name)
  82. // expired cert, no valid certs
  83. ppp, err := NewCAPoolFromPEM([]byte(expired))
  84. assert.Equal(t, ErrExpired, err)
  85. assert.Equal(t, "expired", ppp.CAs["c39b35a0e8f246203fe4f32b9aa8bfd155f1ae6a6be9d78370641e43397f48f5"].Certificate.Name())
  86. // expired cert, with valid certs
  87. pppp, err := NewCAPoolFromPEM(append([]byte(expired), noNewLines...))
  88. assert.Equal(t, ErrExpired, err)
  89. assert.Equal(t, pppp.CAs["ce4e6c7a596996eb0d82a8875f0f0137a4b53ce22d2421c9fd7150e7a26f6300"].Certificate.Name(), rootCA.details.name)
  90. assert.Equal(t, pppp.CAs["04c585fcd9a49b276df956a22b7ebea3bf23f1fca5a17c0b56ce2e626631969e"].Certificate.Name(), rootCA01.details.name)
  91. assert.Equal(t, "expired", pppp.CAs["c39b35a0e8f246203fe4f32b9aa8bfd155f1ae6a6be9d78370641e43397f48f5"].Certificate.Name())
  92. assert.Len(t, pppp.CAs, 3)
  93. ppppp, err := NewCAPoolFromPEM([]byte(p256))
  94. require.NoError(t, err)
  95. assert.Equal(t, ppppp.CAs["552bf7d99bec1fc775a0e4c324bf6d8f789b3078f1919c7960d2e5e0c351ee97"].Certificate.Name(), rootCAP256.details.name)
  96. assert.Len(t, ppppp.CAs, 1)
  97. }
  98. // oneByteReader wraps a reader to return at most 1 byte per Read call,
  99. // exercising the streaming accumulation logic in NewCAPoolFromPEMReader.
  100. type oneByteReader struct {
  101. r io.Reader
  102. }
  103. func (o *oneByteReader) Read(p []byte) (int, error) {
  104. if len(p) == 0 {
  105. return 0, nil
  106. }
  107. return o.r.Read(p[:1])
  108. }
  109. func TestNewCAPoolFromPEMReader_EmptyReader(t *testing.T) {
  110. pool, err := NewCAPoolFromPEMReader(bytes.NewReader(nil))
  111. require.NoError(t, err)
  112. assert.Empty(t, pool.CAs)
  113. pool, err = NewCAPoolFromPEMReader(strings.NewReader(" \n\t\n "))
  114. require.NoError(t, err)
  115. assert.Empty(t, pool.CAs)
  116. }
  117. func TestNewCAPoolFromPEMReader_OneByteReads(t *testing.T) {
  118. ca1, _, _, pem1 := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(time.Hour), nil, nil, nil)
  119. ca2, _, _, pem2 := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(time.Hour), nil, nil, nil)
  120. bundle := append(pem1, pem2...)
  121. pool, err := NewCAPoolFromPEMReader(&oneByteReader{r: bytes.NewReader(bundle)})
  122. require.NoError(t, err)
  123. assert.Len(t, pool.CAs, 2)
  124. fp1, err := ca1.Fingerprint()
  125. require.NoError(t, err)
  126. fp2, err := ca2.Fingerprint()
  127. require.NoError(t, err)
  128. assert.Contains(t, pool.CAs, fp1)
  129. assert.Contains(t, pool.CAs, fp2)
  130. }
  131. func TestNewCAPoolFromPEMReader_TruncatedPEM(t *testing.T) {
  132. _, err := NewCAPoolFromPEMReader(strings.NewReader("-----BEGIN NEBULA CERTIFICATE-----\npartialdata"))
  133. assert.ErrorIs(t, err, ErrInvalidPEMBlock)
  134. }
  135. func TestNewCAPoolFromPEMReader_TrailingGarbage(t *testing.T) {
  136. _, _, _, pem1 := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(time.Hour), nil, nil, nil)
  137. bundle := append(pem1, []byte("some trailing garbage")...)
  138. _, err := NewCAPoolFromPEMReader(bytes.NewReader(bundle))
  139. assert.ErrorIs(t, err, ErrInvalidPEMBlock)
  140. }
  141. func TestCertificateV1_Verify(t *testing.T) {
  142. ca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)
  143. c, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test cert", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)
  144. caPool := NewCAPool()
  145. require.NoError(t, caPool.AddCA(ca))
  146. f, err := c.Fingerprint()
  147. require.NoError(t, err)
  148. caPool.BlocklistFingerprint(f)
  149. _, err = caPool.VerifyCertificate(time.Now(), c)
  150. require.EqualError(t, err, "certificate is in the block list")
  151. caPool.ResetCertBlocklist()
  152. _, err = caPool.VerifyCertificate(time.Now(), c)
  153. require.NoError(t, err)
  154. _, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)
  155. require.EqualError(t, err, "root certificate is expired")
  156. assert.PanicsWithError(t, "certificate is valid before the signing certificate", func() {
  157. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test cert2", time.Time{}, time.Time{}, nil, nil, nil)
  158. })
  159. // Test group assertion
  160. ca, _, caKey, _ = NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{"test1", "test2"})
  161. caPem, err := ca.MarshalPEM()
  162. require.NoError(t, err)
  163. caPool = NewCAPool()
  164. b, err := caPool.AddCAFromPEM(caPem)
  165. require.NoError(t, err)
  166. assert.Empty(t, b)
  167. assert.PanicsWithError(t, "certificate contained a group not present on the signing ca: bad", func() {
  168. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1", "bad"})
  169. })
  170. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test2", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
  171. require.NoError(t, err)
  172. _, err = caPool.VerifyCertificate(time.Now(), c)
  173. require.NoError(t, err)
  174. }
  175. func TestCertificateV1_VerifyP256(t *testing.T) {
  176. ca, _, caKey, _ := NewTestCaCert(Version1, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)
  177. c, _, _, _ := NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)
  178. caPool := NewCAPool()
  179. require.NoError(t, caPool.AddCA(ca))
  180. f, err := c.Fingerprint()
  181. require.NoError(t, err)
  182. caPool.BlocklistFingerprint(f)
  183. _, err = caPool.VerifyCertificate(time.Now(), c)
  184. require.EqualError(t, err, "certificate is in the block list")
  185. // Create a copy of the cert and swap to the alternate form for the signature
  186. nc := c.Copy()
  187. b, err := p256.Swap(c.Signature())
  188. require.NoError(t, err)
  189. require.NoError(t, nc.(*certificateV1).setSignature(b))
  190. _, err = caPool.VerifyCertificate(time.Now(), nc)
  191. require.EqualError(t, err, "certificate is in the block list")
  192. caPool.ResetCertBlocklist()
  193. _, err = caPool.VerifyCertificate(time.Now(), c)
  194. require.NoError(t, err)
  195. _, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)
  196. require.EqualError(t, err, "root certificate is expired")
  197. assert.PanicsWithError(t, "certificate is valid before the signing certificate", func() {
  198. NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Time{}, time.Time{}, nil, nil, nil)
  199. })
  200. // Test group assertion
  201. ca, _, caKey, _ = NewTestCaCert(Version1, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{"test1", "test2"})
  202. caPem, err := ca.MarshalPEM()
  203. require.NoError(t, err)
  204. caPool = NewCAPool()
  205. b, err = caPool.AddCAFromPEM(caPem)
  206. require.NoError(t, err)
  207. assert.Empty(t, b)
  208. assert.PanicsWithError(t, "certificate contained a group not present on the signing ca: bad", func() {
  209. NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1", "bad"})
  210. })
  211. c, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
  212. cc, err := caPool.VerifyCertificate(time.Now(), c)
  213. require.NoError(t, err)
  214. // Reset the blocklist and block the alternate form fingerprint
  215. caPool.ResetCertBlocklist()
  216. caPool.BlocklistFingerprint(cc.fingerprint2)
  217. err = caPool.VerifyCachedCertificate(time.Now(), cc)
  218. require.EqualError(t, err, "certificate is in the block list")
  219. caPool.ResetCertBlocklist()
  220. err = caPool.VerifyCachedCertificate(time.Now(), cc)
  221. require.NoError(t, err)
  222. }
  223. func TestCertificateV1_Verify_IPs(t *testing.T) {
  224. caIp1 := mustParsePrefixUnmapped("10.0.0.0/16")
  225. caIp2 := mustParsePrefixUnmapped("192.168.0.0/24")
  226. ca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{"test"})
  227. caPem, err := ca.MarshalPEM()
  228. require.NoError(t, err)
  229. caPool := NewCAPool()
  230. b, err := caPool.AddCAFromPEM(caPem)
  231. require.NoError(t, err)
  232. assert.Empty(t, b)
  233. // ip is outside the network
  234. cIp1 := mustParsePrefixUnmapped("10.1.0.0/24")
  235. cIp2 := mustParsePrefixUnmapped("192.168.0.1/16")
  236. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  237. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  238. })
  239. // ip is outside the network reversed order of above
  240. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  241. cIp2 = mustParsePrefixUnmapped("10.1.0.0/24")
  242. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  243. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  244. })
  245. // ip is within the network but mask is outside
  246. cIp1 = mustParsePrefixUnmapped("10.0.1.0/15")
  247. cIp2 = mustParsePrefixUnmapped("192.168.0.1/24")
  248. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  249. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  250. })
  251. // ip is within the network but mask is outside reversed order of above
  252. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  253. cIp2 = mustParsePrefixUnmapped("10.0.1.0/15")
  254. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  255. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  256. })
  257. // ip and mask are within the network
  258. cIp1 = mustParsePrefixUnmapped("10.0.1.0/16")
  259. cIp2 = mustParsePrefixUnmapped("192.168.0.1/25")
  260. c, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  261. _, err = caPool.VerifyCertificate(time.Now(), c)
  262. require.NoError(t, err)
  263. // Exact matches
  264. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{"test"})
  265. require.NoError(t, err)
  266. _, err = caPool.VerifyCertificate(time.Now(), c)
  267. require.NoError(t, err)
  268. // Exact matches reversed
  269. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp2, caIp1}, nil, []string{"test"})
  270. require.NoError(t, err)
  271. _, err = caPool.VerifyCertificate(time.Now(), c)
  272. require.NoError(t, err)
  273. // Exact matches reversed with just 1
  274. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1}, nil, []string{"test"})
  275. require.NoError(t, err)
  276. _, err = caPool.VerifyCertificate(time.Now(), c)
  277. require.NoError(t, err)
  278. }
  279. func TestCertificateV1_Verify_Subnets(t *testing.T) {
  280. caIp1 := mustParsePrefixUnmapped("10.0.0.0/16")
  281. caIp2 := mustParsePrefixUnmapped("192.168.0.0/24")
  282. ca, _, caKey, _ := NewTestCaCert(Version1, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{"test"})
  283. caPem, err := ca.MarshalPEM()
  284. require.NoError(t, err)
  285. caPool := NewCAPool()
  286. b, err := caPool.AddCAFromPEM(caPem)
  287. require.NoError(t, err)
  288. assert.Empty(t, b)
  289. // ip is outside the network
  290. cIp1 := mustParsePrefixUnmapped("10.1.0.0/24")
  291. cIp2 := mustParsePrefixUnmapped("192.168.0.1/16")
  292. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  293. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  294. })
  295. // ip is outside the network reversed order of above
  296. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  297. cIp2 = mustParsePrefixUnmapped("10.1.0.0/24")
  298. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  299. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  300. })
  301. // ip is within the network but mask is outside
  302. cIp1 = mustParsePrefixUnmapped("10.0.1.0/15")
  303. cIp2 = mustParsePrefixUnmapped("192.168.0.1/24")
  304. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  305. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  306. })
  307. // ip is within the network but mask is outside reversed order of above
  308. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  309. cIp2 = mustParsePrefixUnmapped("10.0.1.0/15")
  310. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  311. NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  312. })
  313. // ip and mask are within the network
  314. cIp1 = mustParsePrefixUnmapped("10.0.1.0/16")
  315. cIp2 = mustParsePrefixUnmapped("192.168.0.1/25")
  316. c, _, _, _ := NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  317. require.NoError(t, err)
  318. _, err = caPool.VerifyCertificate(time.Now(), c)
  319. require.NoError(t, err)
  320. // Exact matches
  321. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{"test"})
  322. require.NoError(t, err)
  323. _, err = caPool.VerifyCertificate(time.Now(), c)
  324. require.NoError(t, err)
  325. // Exact matches reversed
  326. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp2, caIp1}, []string{"test"})
  327. require.NoError(t, err)
  328. _, err = caPool.VerifyCertificate(time.Now(), c)
  329. require.NoError(t, err)
  330. // Exact matches reversed with just 1
  331. c, _, _, _ = NewTestCert(Version1, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1}, []string{"test"})
  332. require.NoError(t, err)
  333. _, err = caPool.VerifyCertificate(time.Now(), c)
  334. require.NoError(t, err)
  335. }
  336. func TestCertificateV2_Verify(t *testing.T) {
  337. ca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)
  338. c, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test cert", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)
  339. caPool := NewCAPool()
  340. require.NoError(t, caPool.AddCA(ca))
  341. f, err := c.Fingerprint()
  342. require.NoError(t, err)
  343. caPool.BlocklistFingerprint(f)
  344. _, err = caPool.VerifyCertificate(time.Now(), c)
  345. require.EqualError(t, err, "certificate is in the block list")
  346. caPool.ResetCertBlocklist()
  347. _, err = caPool.VerifyCertificate(time.Now(), c)
  348. require.NoError(t, err)
  349. _, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)
  350. require.EqualError(t, err, "root certificate is expired")
  351. assert.PanicsWithError(t, "certificate is valid before the signing certificate", func() {
  352. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test cert2", time.Time{}, time.Time{}, nil, nil, nil)
  353. })
  354. // Test group assertion
  355. ca, _, caKey, _ = NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{"test1", "test2"})
  356. caPem, err := ca.MarshalPEM()
  357. require.NoError(t, err)
  358. caPool = NewCAPool()
  359. b, err := caPool.AddCAFromPEM(caPem)
  360. require.NoError(t, err)
  361. assert.Empty(t, b)
  362. assert.PanicsWithError(t, "certificate contained a group not present on the signing ca: bad", func() {
  363. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1", "bad"})
  364. })
  365. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test2", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
  366. require.NoError(t, err)
  367. _, err = caPool.VerifyCertificate(time.Now(), c)
  368. require.NoError(t, err)
  369. }
  370. func TestCertificateV2_VerifyP256(t *testing.T) {
  371. ca, _, caKey, _ := NewTestCaCert(Version2, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, nil)
  372. c, _, _, _ := NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, nil)
  373. caPool := NewCAPool()
  374. require.NoError(t, caPool.AddCA(ca))
  375. f, err := c.Fingerprint()
  376. require.NoError(t, err)
  377. caPool.BlocklistFingerprint(f)
  378. _, err = caPool.VerifyCertificate(time.Now(), c)
  379. require.EqualError(t, err, "certificate is in the block list")
  380. // Create a copy of the cert and swap to the alternate form for the signature
  381. nc := c.Copy()
  382. b, err := p256.Swap(c.Signature())
  383. require.NoError(t, err)
  384. require.NoError(t, nc.(*certificateV2).setSignature(b))
  385. _, err = caPool.VerifyCertificate(time.Now(), nc)
  386. require.EqualError(t, err, "certificate is in the block list")
  387. caPool.ResetCertBlocklist()
  388. _, err = caPool.VerifyCertificate(time.Now(), c)
  389. require.NoError(t, err)
  390. _, err = caPool.VerifyCertificate(time.Now().Add(time.Hour*1000), c)
  391. require.EqualError(t, err, "root certificate is expired")
  392. assert.PanicsWithError(t, "certificate is valid before the signing certificate", func() {
  393. NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Time{}, time.Time{}, nil, nil, nil)
  394. })
  395. // Test group assertion
  396. ca, _, caKey, _ = NewTestCaCert(Version2, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{"test1", "test2"})
  397. caPem, err := ca.MarshalPEM()
  398. require.NoError(t, err)
  399. caPool = NewCAPool()
  400. b, err = caPool.AddCAFromPEM(caPem)
  401. require.NoError(t, err)
  402. assert.Empty(t, b)
  403. assert.PanicsWithError(t, "certificate contained a group not present on the signing ca: bad", func() {
  404. NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1", "bad"})
  405. })
  406. c, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
  407. cc, err := caPool.VerifyCertificate(time.Now(), c)
  408. require.NoError(t, err)
  409. // Reset the blocklist and block the alternate form fingerprint
  410. caPool.ResetCertBlocklist()
  411. caPool.BlocklistFingerprint(cc.fingerprint2)
  412. err = caPool.VerifyCachedCertificate(time.Now(), cc)
  413. require.EqualError(t, err, "certificate is in the block list")
  414. caPool.ResetCertBlocklist()
  415. err = caPool.VerifyCachedCertificate(time.Now(), cc)
  416. require.NoError(t, err)
  417. }
  418. func TestCertificateV2_Verify_IPs(t *testing.T) {
  419. caIp1 := mustParsePrefixUnmapped("10.0.0.0/16")
  420. caIp2 := mustParsePrefixUnmapped("192.168.0.0/24")
  421. ca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{"test"})
  422. caPem, err := ca.MarshalPEM()
  423. require.NoError(t, err)
  424. caPool := NewCAPool()
  425. b, err := caPool.AddCAFromPEM(caPem)
  426. require.NoError(t, err)
  427. assert.Empty(t, b)
  428. // ip is outside the network
  429. cIp1 := mustParsePrefixUnmapped("10.1.0.0/24")
  430. cIp2 := mustParsePrefixUnmapped("192.168.0.1/16")
  431. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  432. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  433. })
  434. // ip is outside the network reversed order of above
  435. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  436. cIp2 = mustParsePrefixUnmapped("10.1.0.0/24")
  437. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  438. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  439. })
  440. // ip is within the network but mask is outside
  441. cIp1 = mustParsePrefixUnmapped("10.0.1.0/15")
  442. cIp2 = mustParsePrefixUnmapped("192.168.0.1/24")
  443. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  444. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  445. })
  446. // ip is within the network but mask is outside reversed order of above
  447. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  448. cIp2 = mustParsePrefixUnmapped("10.0.1.0/15")
  449. assert.PanicsWithError(t, "certificate contained a network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  450. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  451. })
  452. // ip and mask are within the network
  453. cIp1 = mustParsePrefixUnmapped("10.0.1.0/16")
  454. cIp2 = mustParsePrefixUnmapped("192.168.0.1/25")
  455. c, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1, cIp2}, nil, []string{"test"})
  456. _, err = caPool.VerifyCertificate(time.Now(), c)
  457. require.NoError(t, err)
  458. // Exact matches
  459. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{"test"})
  460. require.NoError(t, err)
  461. _, err = caPool.VerifyCertificate(time.Now(), c)
  462. require.NoError(t, err)
  463. // Exact matches reversed
  464. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp2, caIp1}, nil, []string{"test"})
  465. require.NoError(t, err)
  466. _, err = caPool.VerifyCertificate(time.Now(), c)
  467. require.NoError(t, err)
  468. // Exact matches reversed with just 1
  469. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{caIp1}, nil, []string{"test"})
  470. require.NoError(t, err)
  471. _, err = caPool.VerifyCertificate(time.Now(), c)
  472. require.NoError(t, err)
  473. }
  474. func TestCertificateV2_Verify_Subnets(t *testing.T) {
  475. caIp1 := mustParsePrefixUnmapped("10.0.0.0/16")
  476. caIp2 := mustParsePrefixUnmapped("192.168.0.0/24")
  477. ca, _, caKey, _ := NewTestCaCert(Version2, Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{"test"})
  478. caPem, err := ca.MarshalPEM()
  479. require.NoError(t, err)
  480. caPool := NewCAPool()
  481. b, err := caPool.AddCAFromPEM(caPem)
  482. require.NoError(t, err)
  483. assert.Empty(t, b)
  484. // ip is outside the network
  485. cIp1 := mustParsePrefixUnmapped("10.1.0.0/24")
  486. cIp2 := mustParsePrefixUnmapped("192.168.0.1/16")
  487. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  488. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  489. })
  490. // ip is outside the network reversed order of above
  491. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  492. cIp2 = mustParsePrefixUnmapped("10.1.0.0/24")
  493. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.1.0.0/24", func() {
  494. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  495. })
  496. // ip is within the network but mask is outside
  497. cIp1 = mustParsePrefixUnmapped("10.0.1.0/15")
  498. cIp2 = mustParsePrefixUnmapped("192.168.0.1/24")
  499. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  500. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  501. })
  502. // ip is within the network but mask is outside reversed order of above
  503. cIp1 = mustParsePrefixUnmapped("192.168.0.1/24")
  504. cIp2 = mustParsePrefixUnmapped("10.0.1.0/15")
  505. assert.PanicsWithError(t, "certificate contained an unsafe network assignment outside the limitations of the signing ca: 10.0.1.0/15", func() {
  506. NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  507. })
  508. // ip and mask are within the network
  509. cIp1 = mustParsePrefixUnmapped("10.0.1.0/16")
  510. cIp2 = mustParsePrefixUnmapped("192.168.0.1/25")
  511. c, _, _, _ := NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{cIp1, cIp2}, []string{"test"})
  512. require.NoError(t, err)
  513. _, err = caPool.VerifyCertificate(time.Now(), c)
  514. require.NoError(t, err)
  515. // Exact matches
  516. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1, caIp2}, []string{"test"})
  517. require.NoError(t, err)
  518. _, err = caPool.VerifyCertificate(time.Now(), c)
  519. require.NoError(t, err)
  520. // Exact matches reversed
  521. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp2, caIp1}, []string{"test"})
  522. require.NoError(t, err)
  523. _, err = caPool.VerifyCertificate(time.Now(), c)
  524. require.NoError(t, err)
  525. // Exact matches reversed with just 1
  526. c, _, _, _ = NewTestCert(Version2, Curve_CURVE25519, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, []netip.Prefix{caIp1}, []string{"test"})
  527. require.NoError(t, err)
  528. _, err = caPool.VerifyCertificate(time.Now(), c)
  529. require.NoError(t, err)
  530. }
  531. func TestCertificateV2_CurveMismatch(t *testing.T) {
  532. caIp1 := mustParsePrefixUnmapped("10.0.0.0/16")
  533. caIp2 := mustParsePrefixUnmapped("192.168.0.0/24")
  534. ca, _, caKey, _ := NewTestCaCert(Version2, Curve_P256, time.Now(), time.Now().Add(10*time.Minute), []netip.Prefix{caIp1, caIp2}, nil, []string{"test"})
  535. caPem, err := ca.MarshalPEM()
  536. require.NoError(t, err)
  537. caPool := NewCAPool()
  538. b, err := caPool.AddCAFromPEM(caPem)
  539. require.NoError(t, err)
  540. assert.Empty(t, b)
  541. // ip is outside the network
  542. cIp1 := mustParsePrefixUnmapped("10.0.0.1/24")
  543. c, _, _, _ := NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), []netip.Prefix{cIp1}, nil, []string{"test"})
  544. fp, _ := c.Fingerprint()
  545. _, err = caPool.verify(c, time.Now(), fp, c.Issuer())
  546. require.NoError(t, err)
  547. //
  548. c2 := c.(*certificateV2)
  549. c2.curve = Curve_CURVE25519
  550. fp, _ = c.Fingerprint()
  551. _, err = caPool.verify(c, time.Now(), fp, c.Issuer())
  552. require.Error(t, err)
  553. }