machine_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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. t.Run("zero initiator index on responder is fatal", func(t *testing.T) {
  198. m := newTestMachine(t, cs, v, false, 100)
  199. bytes := MarshalPayload(nil, Payload{InitiatorIndex: 0, Time: 1})
  200. err := m.processPayload(bytes, msgFlags{expectsPayload: true})
  201. require.ErrorIs(t, err, ErrInvalidRemoteIndex)
  202. assert.True(t, m.Failed())
  203. assert.Zero(t, m.result.RemoteIndex)
  204. })
  205. t.Run("zero responder index on initiator is fatal", func(t *testing.T) {
  206. m := newTestMachine(t, cs, v, true, 100)
  207. bytes := MarshalPayload(nil, Payload{InitiatorIndex: 100, ResponderIndex: 0, Time: 1})
  208. err := m.processPayload(bytes, msgFlags{expectsPayload: true})
  209. require.ErrorIs(t, err, ErrInvalidRemoteIndex)
  210. assert.True(t, m.Failed())
  211. assert.Zero(t, m.result.RemoteIndex)
  212. })
  213. }
  214. // TestMachineRequireComplete checks the fail-on-incomplete-handshake path
  215. // directly. Like processPayload above this isn't reachable from a normal IX
  216. // flow, so we drive it by hand.
  217. func TestMachineRequireComplete(t *testing.T) {
  218. ca, _, caKey, _ := ct.NewTestCaCert(
  219. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  220. )
  221. caPool := ct.NewTestCAPool(ca)
  222. cs := newTestCertState(t, ca, caKey, "test", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  223. v := testVerifier(caPool)
  224. t.Run("missing both fails", func(t *testing.T) {
  225. m := newTestMachine(t, cs, v, false, 100)
  226. err := m.requireComplete()
  227. require.ErrorIs(t, err, ErrIncompleteHandshake)
  228. assert.True(t, m.Failed())
  229. })
  230. t.Run("payload only fails", func(t *testing.T) {
  231. m := newTestMachine(t, cs, v, false, 100)
  232. m.payloadSet = true
  233. err := m.requireComplete()
  234. require.ErrorIs(t, err, ErrIncompleteHandshake)
  235. assert.True(t, m.Failed())
  236. })
  237. t.Run("cert only fails", func(t *testing.T) {
  238. m := newTestMachine(t, cs, v, false, 100)
  239. m.remoteCertSet = true
  240. err := m.requireComplete()
  241. require.ErrorIs(t, err, ErrIncompleteHandshake)
  242. assert.True(t, m.Failed())
  243. })
  244. t.Run("both set passes", func(t *testing.T) {
  245. m := newTestMachine(t, cs, v, false, 100)
  246. m.payloadSet = true
  247. m.remoteCertSet = true
  248. err := m.requireComplete()
  249. require.NoError(t, err)
  250. assert.False(t, m.Failed())
  251. })
  252. }
  253. func TestMachineAESCipher(t *testing.T) {
  254. ca, _, caKey, _ := ct.NewTestCaCert(
  255. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  256. )
  257. caPool := ct.NewTestCAPool(ca)
  258. initCS := newTestCertStateWithCipher(
  259. t, ca, caKey, "init",
  260. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  261. noiseutil.CipherAESGCM,
  262. )
  263. respCS := newTestCertStateWithCipher(
  264. t, ca, caKey, "resp",
  265. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  266. noiseutil.CipherAESGCM,
  267. )
  268. initR, respR := doFullHandshake(t, initCS, respCS, caPool)
  269. ct1, err := initR.EKey.Encrypt(nil, nil, []byte("works"))
  270. require.NoError(t, err)
  271. pt1, err := respR.DKey.Decrypt(nil, nil, ct1)
  272. require.NoError(t, err)
  273. assert.Equal(t, []byte("works"), pt1)
  274. ct2, err := respR.EKey.Encrypt(nil, nil, []byte("back"))
  275. require.NoError(t, err)
  276. pt2, err := initR.DKey.Decrypt(nil, nil, ct2)
  277. require.NoError(t, err)
  278. assert.Equal(t, []byte("back"), pt2)
  279. }
  280. func TestResultFields(t *testing.T) {
  281. ca, _, caKey, _ := ct.NewTestCaCert(
  282. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  283. )
  284. caPool := ct.NewTestCAPool(ca)
  285. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  286. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  287. initR, respR := doFullHandshake(t, initCS, respCS, caPool)
  288. assert.True(t, initR.Initiator)
  289. assert.False(t, respR.Initiator)
  290. assert.NotZero(t, initR.HandshakeTime)
  291. assert.NotZero(t, respR.HandshakeTime)
  292. assert.NotNil(t, initR.RemoteCert)
  293. assert.NotNil(t, respR.RemoteCert)
  294. }
  295. func TestMachineBufferReuse(t *testing.T) {
  296. ca, _, caKey, _ := ct.NewTestCaCert(
  297. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  298. )
  299. caPool := ct.NewTestCAPool(ca)
  300. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  301. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  302. v := testVerifier(caPool)
  303. initM := newTestMachine(t, initCS, v, true, 1000)
  304. respM := newTestMachine(t, respCS, v, false, 2000)
  305. msg1, err := initM.Initiate(nil)
  306. require.NoError(t, err)
  307. t.Run("response writes into provided buffer", func(t *testing.T) {
  308. buf := make([]byte, 0, 4096)
  309. resp, result, err := respM.ProcessPacket(buf, msg1)
  310. require.NoError(t, err)
  311. require.NotNil(t, result)
  312. assert.NotEmpty(t, resp, "response should have content")
  313. assert.Equal(t, &buf[:1][0], &resp[:1][0],
  314. "response should reuse the provided buffer's backing array")
  315. })
  316. t.Run("initiate writes into provided buffer", func(t *testing.T) {
  317. initM2 := newTestMachine(t, initCS, v, true, 3000)
  318. buf := make([]byte, 0, 4096)
  319. msg, err := initM2.Initiate(buf)
  320. require.NoError(t, err)
  321. assert.NotEmpty(t, msg, "initiate should have content")
  322. assert.Equal(t, &buf[:1][0], &msg[:1][0],
  323. "initiate should reuse the provided buffer's backing array")
  324. })
  325. t.Run("nil out still works", func(t *testing.T) {
  326. initM2 := newTestMachine(t, initCS, v, true, 4000)
  327. respM2 := newTestMachine(t, respCS, v, false, 5000)
  328. msg1, err := initM2.Initiate(nil)
  329. require.NoError(t, err)
  330. resp, _, err := respM2.ProcessPacket(nil, msg1)
  331. require.NoError(t, err)
  332. out, result, err := initM2.ProcessPacket(nil, resp)
  333. require.NoError(t, err)
  334. assert.NotNil(t, result)
  335. assert.Nil(t, out, "initiator should have no response for IX msg2")
  336. })
  337. }
  338. func TestMachineMsgIndexTracking(t *testing.T) {
  339. ca, _, caKey, _ := ct.NewTestCaCert(
  340. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  341. )
  342. caPool := ct.NewTestCAPool(ca)
  343. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  344. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  345. v := testVerifier(caPool)
  346. initM := newTestMachine(t, initCS, v, true, 100)
  347. respM := newTestMachine(t, respCS, v, false, 200)
  348. msg1, err := initM.Initiate(nil)
  349. require.NoError(t, err)
  350. resp1, result1, err := respM.ProcessPacket(nil, msg1)
  351. require.NoError(t, err)
  352. assert.NotNil(t, result1)
  353. _, result2, err := initM.ProcessPacket(nil, resp1)
  354. require.NoError(t, err)
  355. assert.NotNil(t, result2)
  356. }
  357. func TestMachineThreeMessagePattern(t *testing.T) {
  358. registerTestXXInfo(t)
  359. // Use HandshakeXX (3 messages) to verify the Machine handles multi-message
  360. // patterns correctly. XX flow:
  361. // msg1 (I->R): [E] - payload only, no cert
  362. // msg2 (R->I): [E, ee, S, es] - payload + cert
  363. // msg3 (I->R): [S, se] - cert only (no payload, not first two)
  364. ca, _, caKey, _ := ct.NewTestCaCert(
  365. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  366. )
  367. caPool := ct.NewTestCAPool(ca)
  368. v := testVerifier(caPool)
  369. initCS := newTestCertState(t, ca, caKey, "init", []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")})
  370. respCS := newTestCertState(t, ca, caKey, "resp", []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")})
  371. initM, err := NewMachine(
  372. cert.Version2,
  373. initCS.getCredential, v,
  374. func() (uint32, error) { return 1000, nil },
  375. true, header.HandshakeXXPSK0,
  376. nil,
  377. )
  378. require.NoError(t, err)
  379. respM, err := NewMachine(
  380. cert.Version2,
  381. respCS.getCredential, v,
  382. func() (uint32, error) { return 2000, nil },
  383. false, header.HandshakeXXPSK0,
  384. nil,
  385. )
  386. require.NoError(t, err)
  387. // msg1: initiator -> responder (E only, no cert)
  388. msg1, err := initM.Initiate(nil)
  389. require.NoError(t, err)
  390. assert.NotEmpty(t, msg1)
  391. // Responder processes msg1, should not complete yet, should produce msg2
  392. msg2, result, err := respM.ProcessPacket(nil, msg1)
  393. require.NoError(t, err)
  394. assert.Nil(t, result, "XX should not complete on msg1")
  395. assert.NotEmpty(t, msg2, "responder should produce msg2")
  396. // Initiator processes msg2: gets responder's cert, produces msg3, and
  397. // completes (WriteMessage for msg3 derives keys)
  398. msg3, initResult, err := initM.ProcessPacket(nil, msg2)
  399. require.NoError(t, err)
  400. require.NotNil(t, initResult, "XX initiator should complete after reading msg2 and writing msg3")
  401. assert.NotEmpty(t, msg3, "initiator should produce msg3")
  402. assert.Equal(t, "resp", initResult.RemoteCert.Certificate.Name())
  403. // Responder processes msg3: gets initiator's cert and completes
  404. _, respResult, err := respM.ProcessPacket(nil, msg3)
  405. require.NoError(t, err)
  406. require.NotNil(t, respResult, "XX responder should complete on msg3")
  407. assert.Equal(t, "init", respResult.RemoteCert.Certificate.Name())
  408. assert.Equal(t, uint64(3), initResult.MessageIndex, "XX has 3 messages")
  409. assert.Equal(t, uint64(3), respResult.MessageIndex, "XX has 3 messages")
  410. // Verify keys work
  411. ct1, err := initResult.EKey.Encrypt(nil, nil, []byte("three messages"))
  412. require.NoError(t, err)
  413. pt1, err := respResult.DKey.Decrypt(nil, nil, ct1)
  414. require.NoError(t, err)
  415. assert.Equal(t, []byte("three messages"), pt1)
  416. }
  417. // NOTE: ErrIncompleteHandshake is tested implicitly. It can't be triggered with
  418. // IX since the cert is always in the payload. A 3-message pattern test (HybridIX)
  419. // should exercise the case where cert arrives in msg3 and verify that completing
  420. // without it fails.
  421. func TestMachineExpiredCert(t *testing.T) {
  422. ca, _, caKey, _ := ct.NewTestCaCert(
  423. cert.Version2, cert.Curve_CURVE25519,
  424. time.Now().Add(-24*time.Hour), time.Now().Add(24*time.Hour),
  425. nil, nil, nil,
  426. )
  427. caPool := ct.NewTestCAPool(ca)
  428. expCert, _, expKeyPEM, _ := ct.NewTestCert(
  429. cert.Version2, cert.Curve_CURVE25519, ca, caKey,
  430. "expired", time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour),
  431. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}, nil, nil,
  432. )
  433. expKey, _, _, err := cert.UnmarshalPrivateKeyFromPEM(expKeyPEM)
  434. require.NoError(t, err)
  435. expHsBytes, err := expCert.MarshalForHandshakes()
  436. require.NoError(t, err)
  437. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  438. expiredCS := &testCertState{
  439. version: cert.Version2,
  440. creds: map[cert.Version]*Credential{
  441. cert.Version2: NewCredential(expCert, expHsBytes, expKey, ncs),
  442. },
  443. }
  444. respCS := newTestCertState(
  445. t, ca, caKey, "responder",
  446. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  447. )
  448. _, respM, _, _, err := initiateHandshake(
  449. t, expiredCS, testVerifier(caPool),
  450. respCS, testVerifier(caPool),
  451. )
  452. require.ErrorContains(t, err, "verify cert")
  453. assert.True(t, respM.Failed())
  454. }
  455. func TestMachineNoCertNetworks(t *testing.T) {
  456. ca, _, caKey, _ := ct.NewTestCaCert(
  457. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  458. )
  459. caPool := ct.NewTestCAPool(ca)
  460. caHsBytes, err := ca.MarshalForHandshakes()
  461. require.NoError(t, err)
  462. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  463. noNetCS := &testCertState{
  464. version: cert.Version2,
  465. creds: map[cert.Version]*Credential{
  466. cert.Version2: NewCredential(ca, caHsBytes, caKey, ncs),
  467. },
  468. }
  469. respCS := newTestCertState(
  470. t, ca, caKey, "responder",
  471. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  472. )
  473. _, respM, _, _, err := initiateHandshake(
  474. t, noNetCS, testVerifier(caPool),
  475. respCS, testVerifier(caPool),
  476. )
  477. require.Error(t, err)
  478. assert.True(t, respM.Failed())
  479. }
  480. func TestMachineDifferentCAs(t *testing.T) {
  481. ca1, _, caKey1, _ := ct.NewTestCaCert(
  482. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  483. )
  484. ca2, _, caKey2, _ := ct.NewTestCaCert(
  485. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  486. )
  487. initCS := newTestCertState(
  488. t, ca1, caKey1, "init",
  489. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  490. )
  491. respCS := newTestCertState(
  492. t, ca2, caKey2, "resp",
  493. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  494. )
  495. _, respM, _, _, err := initiateHandshake(
  496. t, initCS, testVerifier(ct.NewTestCAPool(ca1)),
  497. respCS, testVerifier(ct.NewTestCAPool(ca2)),
  498. )
  499. require.ErrorContains(t, err, "verify cert")
  500. assert.True(t, respM.Failed())
  501. }
  502. func TestMachineVersionNegotiation(t *testing.T) {
  503. ca1, _, caKey1, _ := ct.NewTestCaCert(
  504. cert.Version1, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  505. )
  506. ca2, _, caKey2, _ := ct.NewTestCaCert(
  507. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  508. )
  509. caPool := ct.NewTestCAPool(ca1, ca2)
  510. makeMultiVersionResp := func(t *testing.T) *testCertState {
  511. t.Helper()
  512. respCertV1, _, respKeyPEM, _ := ct.NewTestCert(
  513. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  514. ca1.NotBefore(), ca1.NotAfter(),
  515. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  516. )
  517. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  518. respCertV2, _ := ct.NewTestCertDifferentVersion(respCertV1, cert.Version2, ca2, caKey2)
  519. respHsV1, _ := respCertV1.MarshalForHandshakes()
  520. respHsV2, _ := respCertV2.MarshalForHandshakes()
  521. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  522. return &testCertState{
  523. version: cert.Version1,
  524. creds: map[cert.Version]*Credential{
  525. cert.Version1: NewCredential(respCertV1, respHsV1, respKey, ncs),
  526. cert.Version2: NewCredential(respCertV2, respHsV2, respKey, ncs),
  527. },
  528. }
  529. }
  530. t.Run("responder matches initiator version", func(t *testing.T) {
  531. initCS := newTestCertState(
  532. t, ca2, caKey2, "init",
  533. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  534. )
  535. respCS := makeMultiVersionResp(t)
  536. v := testVerifier(caPool)
  537. initM, _, respResult, resp, err := initiateHandshake(
  538. t, initCS, v,
  539. respCS, v,
  540. )
  541. require.NoError(t, err)
  542. require.NotNil(t, respResult)
  543. assert.Equal(t, cert.Version2, respResult.MyCert.Version(),
  544. "responder should negotiate to initiator's version")
  545. _, initResult, err := initM.ProcessPacket(nil, resp)
  546. require.NoError(t, err)
  547. require.NotNil(t, initResult)
  548. assert.Equal(t, cert.Version2, initResult.RemoteCert.Certificate.Version(),
  549. "initiator should see V2 cert from responder")
  550. })
  551. t.Run("responder keeps version when no match available", func(t *testing.T) {
  552. initCS := newTestCertState(
  553. t, ca2, caKey2, "init",
  554. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  555. )
  556. respCert, _, respKeyPEM, _ := ct.NewTestCert(
  557. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  558. ca1.NotBefore(), ca1.NotAfter(),
  559. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  560. )
  561. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  562. respHs, _ := respCert.MarshalForHandshakes()
  563. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  564. respCS := &testCertState{
  565. version: cert.Version1,
  566. creds: map[cert.Version]*Credential{
  567. cert.Version1: NewCredential(respCert, respHs, respKey, ncs),
  568. },
  569. }
  570. v := testVerifier(caPool)
  571. _, _, respResult, _, err := initiateHandshake(
  572. t, initCS, v,
  573. respCS, v,
  574. )
  575. require.NoError(t, err)
  576. require.NotNil(t, respResult)
  577. assert.Equal(t, cert.Version1, respResult.MyCert.Version(),
  578. "responder should keep V1 when V2 not available")
  579. })
  580. }