machine_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. package handshake
  2. import (
  3. "net/netip"
  4. "testing"
  5. "time"
  6. "github.com/flynn/noise"
  7. "github.com/slackhq/nebula/cert"
  8. ct "github.com/slackhq/nebula/cert_test"
  9. "github.com/slackhq/nebula/header"
  10. "github.com/slackhq/nebula/noiseutil"
  11. "github.com/stretchr/testify/assert"
  12. "github.com/stretchr/testify/require"
  13. )
  14. func TestMachineIXHappyPath(t *testing.T) {
  15. ca, _, caKey, _ := ct.NewTestCaCert(
  16. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  17. )
  18. caPool := ct.NewTestCAPool(ca)
  19. initCS := newTestCertState(t, ca, caKey, "initiator", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  20. respCS := newTestCertState(t, ca, caKey, "responder", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  21. initR, respR := doFullHandshake(t, initCS, respCS, caPool)
  22. assert.Equal(t, "responder", initR.RemoteCert.Certificate.Name())
  23. assert.Equal(t, "initiator", respR.RemoteCert.Certificate.Name())
  24. assert.Equal(t, uint32(1000), initR.LocalIndex)
  25. assert.Equal(t, uint32(2000), initR.RemoteIndex)
  26. assert.Equal(t, uint32(2000), respR.LocalIndex)
  27. assert.Equal(t, uint32(1000), respR.RemoteIndex)
  28. assert.Equal(t, uint64(2), initR.MessageIndex, "IX has 2 messages")
  29. assert.Equal(t, uint64(2), respR.MessageIndex, "IX has 2 messages")
  30. ct1, err := initR.EKey.Encrypt(nil, nil, []byte("hello"))
  31. require.NoError(t, err)
  32. pt1, err := respR.DKey.Decrypt(nil, nil, ct1)
  33. require.NoError(t, err)
  34. assert.Equal(t, []byte("hello"), pt1)
  35. ct2, err := respR.EKey.Encrypt(nil, nil, []byte("world"))
  36. require.NoError(t, err)
  37. pt2, err := initR.DKey.Decrypt(nil, nil, ct2)
  38. require.NoError(t, err)
  39. assert.Equal(t, []byte("world"), pt2)
  40. }
  41. func TestMachineInitiateErrors(t *testing.T) {
  42. ca, _, caKey, _ := ct.NewTestCaCert(
  43. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  44. )
  45. caPool := ct.NewTestCAPool(ca)
  46. cs := newTestCertState(t, ca, caKey, "test", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  47. v := testVerifier(caPool)
  48. t.Run("initiate on responder", func(t *testing.T) {
  49. m := newTestMachine(t, cs, v, false, 100)
  50. _, err := m.Initiate(nil)
  51. require.ErrorIs(t, err, ErrInitiateOnResponder)
  52. assert.True(t, m.Failed())
  53. })
  54. t.Run("initiate called twice", func(t *testing.T) {
  55. m := newTestMachine(t, cs, v, true, 100)
  56. _, err := m.Initiate(nil)
  57. require.NoError(t, err)
  58. _, err = m.Initiate(nil)
  59. require.ErrorIs(t, err, ErrInitiateAlreadyCalled)
  60. assert.True(t, m.Failed())
  61. })
  62. t.Run("process packet before initiate on initiator", func(t *testing.T) {
  63. m := newTestMachine(t, cs, v, true, 100)
  64. _, _, err := m.ProcessPacket(nil, make([]byte, 100))
  65. require.ErrorIs(t, err, ErrInitiateNotCalled)
  66. assert.True(t, m.Failed())
  67. })
  68. t.Run("calling failed machine", func(t *testing.T) {
  69. m := newTestMachine(t, cs, v, false, 100)
  70. _, err := m.Initiate(nil) // fails: responder
  71. require.Error(t, err)
  72. _, err = m.Initiate(nil) // fails: already failed
  73. require.ErrorIs(t, err, ErrMachineFailed)
  74. })
  75. }
  76. func TestMachineProcessPacketErrors(t *testing.T) {
  77. ca, _, caKey, _ := ct.NewTestCaCert(
  78. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  79. )
  80. caPool := ct.NewTestCAPool(ca)
  81. cs := newTestCertState(t, ca, caKey, "test", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  82. v := testVerifier(caPool)
  83. t.Run("packet too short", func(t *testing.T) {
  84. m := newTestMachine(t, cs, v, false, 100)
  85. _, _, err := m.ProcessPacket(nil, []byte{1, 2, 3})
  86. require.ErrorIs(t, err, ErrPacketTooShort)
  87. assert.False(t, m.Failed(), "short packet should not kill machine")
  88. })
  89. t.Run("noise decryption failure is recoverable", func(t *testing.T) {
  90. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  91. initM := newTestMachine(t, initCS, v, true, 100)
  92. msg1, err := initM.Initiate(nil)
  93. require.NoError(t, err)
  94. respM := newTestMachine(t, cs, v, false, 200)
  95. resp, _, err := respM.ProcessPacket(nil, msg1)
  96. require.NoError(t, err)
  97. corrupted := make([]byte, len(resp))
  98. copy(corrupted, resp)
  99. for i := header.Len; i < len(corrupted); i++ {
  100. corrupted[i] ^= 0xff
  101. }
  102. _, _, err = initM.ProcessPacket(nil, corrupted)
  103. require.Error(t, err)
  104. assert.False(t, initM.Failed(), "noise failure should be recoverable")
  105. // And the machine should still complete a real handshake afterward.
  106. _, result, err := initM.ProcessPacket(nil, resp)
  107. require.NoError(t, err)
  108. require.NotNil(t, result, "initiator should complete on the legitimate response")
  109. })
  110. t.Run("invalid cert is fatal", func(t *testing.T) {
  111. otherCA, _, otherCAKey, _ := ct.NewTestCaCert(
  112. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  113. )
  114. otherCS := newTestCertState(t, otherCA, otherCAKey, "other", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  115. initM := newTestMachine(t, otherCS, testVerifier(ct.NewTestCAPool(otherCA)), true, 100)
  116. msg1, err := initM.Initiate(nil)
  117. require.NoError(t, err)
  118. respM := newTestMachine(t, cs, v, false, 200)
  119. _, _, err = respM.ProcessPacket(nil, msg1)
  120. require.Error(t, err)
  121. assert.True(t, respM.Failed(), "cert validation failure should kill machine")
  122. })
  123. t.Run("subtype mismatch is recoverable", func(t *testing.T) {
  124. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  125. initM := newTestMachine(t, initCS, v, true, 100)
  126. msg1, err := initM.Initiate(nil)
  127. require.NoError(t, err)
  128. // Mutate the subtype byte (offset 1 in the header) to a value the
  129. // responder Machine wasn't built for.
  130. bad := make([]byte, len(msg1))
  131. copy(bad, msg1)
  132. bad[1] = 0xff
  133. respM := newTestMachine(t, cs, v, false, 200)
  134. _, _, err = respM.ProcessPacket(nil, bad)
  135. require.ErrorIs(t, err, ErrSubtypeMismatch)
  136. assert.False(t, respM.Failed(), "subtype mismatch should not kill the machine")
  137. // And the machine should still complete a real handshake afterward.
  138. resp, result, err := respM.ProcessPacket(nil, msg1)
  139. require.NoError(t, err)
  140. require.NotNil(t, result, "responder should complete on the legitimate stage-1 packet")
  141. assert.NotEmpty(t, resp, "responder should produce a stage-2 reply")
  142. })
  143. }
  144. // TestMachineProcessPayload exercises processPayload's internal validation
  145. // directly. Most of these failure modes can't be reached black-box once the
  146. // subtype check at the top of ProcessPacket gates external callers, so we
  147. // drive them by hand here for coverage.
  148. func TestMachineProcessPayload(t *testing.T) {
  149. ca, _, caKey, _ := ct.NewTestCaCert(
  150. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  151. )
  152. caPool := ct.NewTestCAPool(ca)
  153. cs := newTestCertState(t, ca, caKey, "test", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  154. v := testVerifier(caPool)
  155. t.Run("empty message with expects fails", func(t *testing.T) {
  156. m := newTestMachine(t, cs, v, false, 100)
  157. err := m.processPayload(nil, msgFlags{expectsPayload: true, expectsCert: true})
  158. require.ErrorIs(t, err, ErrMissingContent)
  159. assert.True(t, m.Failed())
  160. })
  161. t.Run("empty message with no expects passes", func(t *testing.T) {
  162. m := newTestMachine(t, cs, v, false, 100)
  163. err := m.processPayload(nil, msgFlags{})
  164. require.NoError(t, err)
  165. assert.False(t, m.Failed())
  166. })
  167. t.Run("malformed protobuf is fatal", func(t *testing.T) {
  168. m := newTestMachine(t, cs, v, false, 100)
  169. err := m.processPayload([]byte{0xff, 0xff, 0xff}, msgFlags{expectsPayload: true, expectsCert: true})
  170. require.Error(t, err)
  171. assert.True(t, m.Failed())
  172. })
  173. t.Run("unexpected payload data is fatal", func(t *testing.T) {
  174. m := newTestMachine(t, cs, v, false, 100)
  175. // A payload with index data when none was expected.
  176. bytes := MarshalPayload(nil, Payload{InitiatorIndex: 42, Time: 1})
  177. err := m.processPayload(bytes, msgFlags{expectsPayload: false, expectsCert: false})
  178. require.ErrorIs(t, err, ErrUnexpectedContent)
  179. assert.True(t, m.Failed())
  180. })
  181. t.Run("unexpected cert data is fatal", func(t *testing.T) {
  182. m := newTestMachine(t, cs, v, false, 100)
  183. // A payload with cert when none was expected.
  184. bytes := MarshalPayload(nil, Payload{Cert: []byte{1, 2, 3}, CertVersion: 2})
  185. err := m.processPayload(bytes, msgFlags{expectsPayload: false, expectsCert: false})
  186. require.ErrorIs(t, err, ErrUnexpectedContent)
  187. assert.True(t, m.Failed())
  188. })
  189. t.Run("missing payload data when expected is fatal", func(t *testing.T) {
  190. m := newTestMachine(t, cs, v, false, 100)
  191. // Cert present, but no index/time fields.
  192. bytes := MarshalPayload(nil, Payload{Cert: []byte{1, 2, 3}, CertVersion: 2})
  193. err := m.processPayload(bytes, msgFlags{expectsPayload: true, expectsCert: true})
  194. require.ErrorIs(t, err, ErrUnexpectedContent)
  195. assert.True(t, m.Failed())
  196. })
  197. }
  198. // TestMachineRequireComplete checks the fail-on-incomplete-handshake path
  199. // directly. Like processPayload above this isn't reachable from a normal IX
  200. // flow, so we drive it by hand.
  201. func TestMachineRequireComplete(t *testing.T) {
  202. ca, _, caKey, _ := ct.NewTestCaCert(
  203. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  204. )
  205. caPool := ct.NewTestCAPool(ca)
  206. cs := newTestCertState(t, ca, caKey, "test", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  207. v := testVerifier(caPool)
  208. t.Run("missing both fails", func(t *testing.T) {
  209. m := newTestMachine(t, cs, v, false, 100)
  210. err := m.requireComplete()
  211. require.ErrorIs(t, err, ErrIncompleteHandshake)
  212. assert.True(t, m.Failed())
  213. })
  214. t.Run("payload only fails", func(t *testing.T) {
  215. m := newTestMachine(t, cs, v, false, 100)
  216. m.payloadSet = true
  217. err := m.requireComplete()
  218. require.ErrorIs(t, err, ErrIncompleteHandshake)
  219. assert.True(t, m.Failed())
  220. })
  221. t.Run("cert only fails", func(t *testing.T) {
  222. m := newTestMachine(t, cs, v, false, 100)
  223. m.remoteCertSet = true
  224. err := m.requireComplete()
  225. require.ErrorIs(t, err, ErrIncompleteHandshake)
  226. assert.True(t, m.Failed())
  227. })
  228. t.Run("both set passes", func(t *testing.T) {
  229. m := newTestMachine(t, cs, v, false, 100)
  230. m.payloadSet = true
  231. m.remoteCertSet = true
  232. err := m.requireComplete()
  233. require.NoError(t, err)
  234. assert.False(t, m.Failed())
  235. })
  236. }
  237. func TestMachineAESCipher(t *testing.T) {
  238. ca, _, caKey, _ := ct.NewTestCaCert(
  239. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  240. )
  241. caPool := ct.NewTestCAPool(ca)
  242. initCS := newTestCertStateWithCipher(
  243. t, ca, caKey, "init",
  244. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  245. noiseutil.CipherAESGCM,
  246. )
  247. respCS := newTestCertStateWithCipher(
  248. t, ca, caKey, "resp",
  249. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  250. noiseutil.CipherAESGCM,
  251. )
  252. initR, respR := doFullHandshake(t, initCS, respCS, caPool)
  253. ct1, err := initR.EKey.Encrypt(nil, nil, []byte("works"))
  254. require.NoError(t, err)
  255. pt1, err := respR.DKey.Decrypt(nil, nil, ct1)
  256. require.NoError(t, err)
  257. assert.Equal(t, []byte("works"), pt1)
  258. ct2, err := respR.EKey.Encrypt(nil, nil, []byte("back"))
  259. require.NoError(t, err)
  260. pt2, err := initR.DKey.Decrypt(nil, nil, ct2)
  261. require.NoError(t, err)
  262. assert.Equal(t, []byte("back"), pt2)
  263. }
  264. func TestResultFields(t *testing.T) {
  265. ca, _, caKey, _ := ct.NewTestCaCert(
  266. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  267. )
  268. caPool := ct.NewTestCAPool(ca)
  269. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  270. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  271. initR, respR := doFullHandshake(t, initCS, respCS, caPool)
  272. assert.True(t, initR.Initiator)
  273. assert.False(t, respR.Initiator)
  274. assert.NotZero(t, initR.HandshakeTime)
  275. assert.NotZero(t, respR.HandshakeTime)
  276. assert.NotNil(t, initR.RemoteCert)
  277. assert.NotNil(t, respR.RemoteCert)
  278. }
  279. func TestMachineBufferReuse(t *testing.T) {
  280. ca, _, caKey, _ := ct.NewTestCaCert(
  281. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  282. )
  283. caPool := ct.NewTestCAPool(ca)
  284. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  285. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  286. v := testVerifier(caPool)
  287. initM := newTestMachine(t, initCS, v, true, 1000)
  288. respM := newTestMachine(t, respCS, v, false, 2000)
  289. msg1, err := initM.Initiate(nil)
  290. require.NoError(t, err)
  291. t.Run("response writes into provided buffer", func(t *testing.T) {
  292. buf := make([]byte, 0, 4096)
  293. resp, result, err := respM.ProcessPacket(buf, msg1)
  294. require.NoError(t, err)
  295. require.NotNil(t, result)
  296. assert.NotEmpty(t, resp, "response should have content")
  297. assert.Equal(t, &buf[:1][0], &resp[:1][0],
  298. "response should reuse the provided buffer's backing array")
  299. })
  300. t.Run("initiate writes into provided buffer", func(t *testing.T) {
  301. initM2 := newTestMachine(t, initCS, v, true, 3000)
  302. buf := make([]byte, 0, 4096)
  303. msg, err := initM2.Initiate(buf)
  304. require.NoError(t, err)
  305. assert.NotEmpty(t, msg, "initiate should have content")
  306. assert.Equal(t, &buf[:1][0], &msg[:1][0],
  307. "initiate should reuse the provided buffer's backing array")
  308. })
  309. t.Run("nil out still works", func(t *testing.T) {
  310. initM2 := newTestMachine(t, initCS, v, true, 4000)
  311. respM2 := newTestMachine(t, respCS, v, false, 5000)
  312. msg1, err := initM2.Initiate(nil)
  313. require.NoError(t, err)
  314. resp, _, err := respM2.ProcessPacket(nil, msg1)
  315. require.NoError(t, err)
  316. out, result, err := initM2.ProcessPacket(nil, resp)
  317. require.NoError(t, err)
  318. assert.NotNil(t, result)
  319. assert.Nil(t, out, "initiator should have no response for IX msg2")
  320. })
  321. }
  322. func TestMachineMsgIndexTracking(t *testing.T) {
  323. ca, _, caKey, _ := ct.NewTestCaCert(
  324. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  325. )
  326. caPool := ct.NewTestCAPool(ca)
  327. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  328. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  329. v := testVerifier(caPool)
  330. initM := newTestMachine(t, initCS, v, true, 100)
  331. respM := newTestMachine(t, respCS, v, false, 200)
  332. msg1, err := initM.Initiate(nil)
  333. require.NoError(t, err)
  334. resp1, result1, err := respM.ProcessPacket(nil, msg1)
  335. require.NoError(t, err)
  336. assert.NotNil(t, result1)
  337. _, result2, err := initM.ProcessPacket(nil, resp1)
  338. require.NoError(t, err)
  339. assert.NotNil(t, result2)
  340. }
  341. func TestMachineThreeMessagePattern(t *testing.T) {
  342. registerTestXXInfo(t)
  343. // Use HandshakeXX (3 messages) to verify the Machine handles multi-message
  344. // patterns correctly. XX flow:
  345. // msg1 (I->R): [E] - payload only, no cert
  346. // msg2 (R->I): [E, ee, S, es] - payload + cert
  347. // msg3 (I->R): [S, se] - cert only (no payload, not first two)
  348. ca, _, caKey, _ := ct.NewTestCaCert(
  349. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  350. )
  351. caPool := ct.NewTestCAPool(ca)
  352. v := testVerifier(caPool)
  353. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  354. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  355. initM, err := NewMachine(
  356. cert.Version2,
  357. initCS.getCredential, v,
  358. func() (uint32, error) { return 1000, nil },
  359. true, header.HandshakeXXPSK0,
  360. )
  361. require.NoError(t, err)
  362. respM, err := NewMachine(
  363. cert.Version2,
  364. respCS.getCredential, v,
  365. func() (uint32, error) { return 2000, nil },
  366. false, header.HandshakeXXPSK0,
  367. )
  368. require.NoError(t, err)
  369. // msg1: initiator -> responder (E only, no cert)
  370. msg1, err := initM.Initiate(nil)
  371. require.NoError(t, err)
  372. assert.NotEmpty(t, msg1)
  373. // Responder processes msg1, should not complete yet, should produce msg2
  374. msg2, result, err := respM.ProcessPacket(nil, msg1)
  375. require.NoError(t, err)
  376. assert.Nil(t, result, "XX should not complete on msg1")
  377. assert.NotEmpty(t, msg2, "responder should produce msg2")
  378. // Initiator processes msg2: gets responder's cert, produces msg3, and
  379. // completes (WriteMessage for msg3 derives keys)
  380. msg3, initResult, err := initM.ProcessPacket(nil, msg2)
  381. require.NoError(t, err)
  382. require.NotNil(t, initResult, "XX initiator should complete after reading msg2 and writing msg3")
  383. assert.NotEmpty(t, msg3, "initiator should produce msg3")
  384. assert.Equal(t, "resp", initResult.RemoteCert.Certificate.Name())
  385. // Responder processes msg3: gets initiator's cert and completes
  386. _, respResult, err := respM.ProcessPacket(nil, msg3)
  387. require.NoError(t, err)
  388. require.NotNil(t, respResult, "XX responder should complete on msg3")
  389. assert.Equal(t, "init", respResult.RemoteCert.Certificate.Name())
  390. assert.Equal(t, uint64(3), initResult.MessageIndex, "XX has 3 messages")
  391. assert.Equal(t, uint64(3), respResult.MessageIndex, "XX has 3 messages")
  392. // Verify keys work
  393. ct1, err := initResult.EKey.Encrypt(nil, nil, []byte("three messages"))
  394. require.NoError(t, err)
  395. pt1, err := respResult.DKey.Decrypt(nil, nil, ct1)
  396. require.NoError(t, err)
  397. assert.Equal(t, []byte("three messages"), pt1)
  398. }
  399. // NOTE: ErrIncompleteHandshake is tested implicitly. It can't be triggered with
  400. // IX since the cert is always in the payload. A 3-message pattern test (HybridIX)
  401. // should exercise the case where cert arrives in msg3 and verify that completing
  402. // without it fails.
  403. func TestMachineExpiredCert(t *testing.T) {
  404. ca, _, caKey, _ := ct.NewTestCaCert(
  405. cert.Version2, cert.Curve_CURVE25519,
  406. time.Now().Add(-24*time.Hour), time.Now().Add(24*time.Hour),
  407. nil, nil, nil,
  408. )
  409. caPool := ct.NewTestCAPool(ca)
  410. expCert, _, expKeyPEM, _ := ct.NewTestCert(
  411. cert.Version2, cert.Curve_CURVE25519, ca, caKey,
  412. "expired", time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour),
  413. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}, nil, nil,
  414. )
  415. expKey, _, _, err := cert.UnmarshalPrivateKeyFromPEM(expKeyPEM)
  416. require.NoError(t, err)
  417. expHsBytes, err := expCert.MarshalForHandshakes()
  418. require.NoError(t, err)
  419. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  420. expiredCS := &testCertState{
  421. version: cert.Version2,
  422. creds: map[cert.Version]*Credential{
  423. cert.Version2: NewCredential(expCert, expHsBytes, expKey, ncs),
  424. },
  425. }
  426. respCS := newTestCertState(
  427. t, ca, caKey, "responder",
  428. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  429. )
  430. _, respM, _, _, err := initiateHandshake(
  431. t, expiredCS, testVerifier(caPool),
  432. respCS, testVerifier(caPool),
  433. )
  434. require.ErrorContains(t, err, "verify cert")
  435. assert.True(t, respM.Failed())
  436. }
  437. func TestMachineNoCertNetworks(t *testing.T) {
  438. ca, _, caKey, _ := ct.NewTestCaCert(
  439. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  440. )
  441. caPool := ct.NewTestCAPool(ca)
  442. caHsBytes, err := ca.MarshalForHandshakes()
  443. require.NoError(t, err)
  444. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  445. noNetCS := &testCertState{
  446. version: cert.Version2,
  447. creds: map[cert.Version]*Credential{
  448. cert.Version2: NewCredential(ca, caHsBytes, caKey, ncs),
  449. },
  450. }
  451. respCS := newTestCertState(
  452. t, ca, caKey, "responder",
  453. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  454. )
  455. _, respM, _, _, err := initiateHandshake(
  456. t, noNetCS, testVerifier(caPool),
  457. respCS, testVerifier(caPool),
  458. )
  459. require.Error(t, err)
  460. assert.True(t, respM.Failed())
  461. }
  462. func TestMachineDifferentCAs(t *testing.T) {
  463. ca1, _, caKey1, _ := ct.NewTestCaCert(
  464. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  465. )
  466. ca2, _, caKey2, _ := ct.NewTestCaCert(
  467. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  468. )
  469. initCS := newTestCertState(
  470. t, ca1, caKey1, "init",
  471. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  472. )
  473. respCS := newTestCertState(
  474. t, ca2, caKey2, "resp",
  475. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  476. )
  477. _, respM, _, _, err := initiateHandshake(
  478. t, initCS, testVerifier(ct.NewTestCAPool(ca1)),
  479. respCS, testVerifier(ct.NewTestCAPool(ca2)),
  480. )
  481. require.ErrorContains(t, err, "verify cert")
  482. assert.True(t, respM.Failed())
  483. }
  484. func TestMachineVersionNegotiation(t *testing.T) {
  485. ca1, _, caKey1, _ := ct.NewTestCaCert(
  486. cert.Version1, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  487. )
  488. ca2, _, caKey2, _ := ct.NewTestCaCert(
  489. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  490. )
  491. caPool := ct.NewTestCAPool(ca1, ca2)
  492. makeMultiVersionResp := func(t *testing.T) *testCertState {
  493. t.Helper()
  494. respCertV1, _, respKeyPEM, _ := ct.NewTestCert(
  495. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  496. ca1.NotBefore(), ca1.NotAfter(),
  497. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  498. )
  499. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  500. respCertV2, _ := ct.NewTestCertDifferentVersion(respCertV1, cert.Version2, ca2, caKey2)
  501. respHsV1, _ := respCertV1.MarshalForHandshakes()
  502. respHsV2, _ := respCertV2.MarshalForHandshakes()
  503. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  504. return &testCertState{
  505. version: cert.Version1,
  506. creds: map[cert.Version]*Credential{
  507. cert.Version1: NewCredential(respCertV1, respHsV1, respKey, ncs),
  508. cert.Version2: NewCredential(respCertV2, respHsV2, respKey, ncs),
  509. },
  510. }
  511. }
  512. t.Run("responder matches initiator version", func(t *testing.T) {
  513. initCS := newTestCertState(
  514. t, ca2, caKey2, "init",
  515. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  516. )
  517. respCS := makeMultiVersionResp(t)
  518. v := testVerifier(caPool)
  519. initM, _, respResult, resp, err := initiateHandshake(
  520. t, initCS, v,
  521. respCS, v,
  522. )
  523. require.NoError(t, err)
  524. require.NotNil(t, respResult)
  525. assert.Equal(t, cert.Version2, respResult.MyCert.Version(),
  526. "responder should negotiate to initiator's version")
  527. _, initResult, err := initM.ProcessPacket(nil, resp)
  528. require.NoError(t, err)
  529. require.NotNil(t, initResult)
  530. assert.Equal(t, cert.Version2, initResult.RemoteCert.Certificate.Version(),
  531. "initiator should see V2 cert from responder")
  532. })
  533. t.Run("responder keeps version when no match available", func(t *testing.T) {
  534. initCS := newTestCertState(
  535. t, ca2, caKey2, "init",
  536. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  537. )
  538. respCert, _, respKeyPEM, _ := ct.NewTestCert(
  539. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  540. ca1.NotBefore(), ca1.NotAfter(),
  541. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  542. )
  543. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  544. respHs, _ := respCert.MarshalForHandshakes()
  545. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  546. respCS := &testCertState{
  547. version: cert.Version1,
  548. creds: map[cert.Version]*Credential{
  549. cert.Version1: NewCredential(respCert, respHs, respKey, ncs),
  550. },
  551. }
  552. v := testVerifier(caPool)
  553. _, _, respResult, _, err := initiateHandshake(
  554. t, initCS, v,
  555. respCS, v,
  556. )
  557. require.NoError(t, err)
  558. require.NotNil(t, respResult)
  559. assert.Equal(t, cert.Version1, respResult.MyCert.Version(),
  560. "responder should keep V1 when V2 not available")
  561. })
  562. }