1
0

machine_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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. )
  377. require.NoError(t, err)
  378. respM, err := NewMachine(
  379. cert.Version2,
  380. respCS.getCredential, v,
  381. func() (uint32, error) { return 2000, nil },
  382. false, header.HandshakeXXPSK0,
  383. )
  384. require.NoError(t, err)
  385. // msg1: initiator -> responder (E only, no cert)
  386. msg1, err := initM.Initiate(nil)
  387. require.NoError(t, err)
  388. assert.NotEmpty(t, msg1)
  389. // Responder processes msg1, should not complete yet, should produce msg2
  390. msg2, result, err := respM.ProcessPacket(nil, msg1)
  391. require.NoError(t, err)
  392. assert.Nil(t, result, "XX should not complete on msg1")
  393. assert.NotEmpty(t, msg2, "responder should produce msg2")
  394. // Initiator processes msg2: gets responder's cert, produces msg3, and
  395. // completes (WriteMessage for msg3 derives keys)
  396. msg3, initResult, err := initM.ProcessPacket(nil, msg2)
  397. require.NoError(t, err)
  398. require.NotNil(t, initResult, "XX initiator should complete after reading msg2 and writing msg3")
  399. assert.NotEmpty(t, msg3, "initiator should produce msg3")
  400. assert.Equal(t, "resp", initResult.RemoteCert.Certificate.Name())
  401. // Responder processes msg3: gets initiator's cert and completes
  402. _, respResult, err := respM.ProcessPacket(nil, msg3)
  403. require.NoError(t, err)
  404. require.NotNil(t, respResult, "XX responder should complete on msg3")
  405. assert.Equal(t, "init", respResult.RemoteCert.Certificate.Name())
  406. assert.Equal(t, uint64(3), initResult.MessageIndex, "XX has 3 messages")
  407. assert.Equal(t, uint64(3), respResult.MessageIndex, "XX has 3 messages")
  408. // Verify keys work
  409. ct1, err := initResult.EKey.Encrypt(nil, nil, []byte("three messages"))
  410. require.NoError(t, err)
  411. pt1, err := respResult.DKey.Decrypt(nil, nil, ct1)
  412. require.NoError(t, err)
  413. assert.Equal(t, []byte("three messages"), pt1)
  414. }
  415. // NOTE: ErrIncompleteHandshake is tested implicitly. It can't be triggered with
  416. // IX since the cert is always in the payload. A 3-message pattern test (HybridIX)
  417. // should exercise the case where cert arrives in msg3 and verify that completing
  418. // without it fails.
  419. func TestMachineExpiredCert(t *testing.T) {
  420. ca, _, caKey, _ := ct.NewTestCaCert(
  421. cert.Version2, cert.Curve_CURVE25519,
  422. time.Now().Add(-24*time.Hour), time.Now().Add(24*time.Hour),
  423. nil, nil, nil,
  424. )
  425. caPool := ct.NewTestCAPool(ca)
  426. expCert, _, expKeyPEM, _ := ct.NewTestCert(
  427. cert.Version2, cert.Curve_CURVE25519, ca, caKey,
  428. "expired", time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour),
  429. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")}, nil, nil,
  430. )
  431. expKey, _, _, err := cert.UnmarshalPrivateKeyFromPEM(expKeyPEM)
  432. require.NoError(t, err)
  433. expHsBytes, err := expCert.MarshalForHandshakes()
  434. require.NoError(t, err)
  435. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  436. expiredCS := &testCertState{
  437. version: cert.Version2,
  438. creds: map[cert.Version]*Credential{
  439. cert.Version2: NewCredential(expCert, expHsBytes, expKey, ncs),
  440. },
  441. }
  442. respCS := newTestCertState(
  443. t, ca, caKey, "responder",
  444. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  445. )
  446. _, respM, _, _, err := initiateHandshake(
  447. t, expiredCS, testVerifier(caPool),
  448. respCS, testVerifier(caPool),
  449. )
  450. require.ErrorContains(t, err, "verify cert")
  451. assert.True(t, respM.Failed())
  452. }
  453. func TestMachineNoCertNetworks(t *testing.T) {
  454. ca, _, caKey, _ := ct.NewTestCaCert(
  455. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  456. )
  457. caPool := ct.NewTestCAPool(ca)
  458. caHsBytes, err := ca.MarshalForHandshakes()
  459. require.NoError(t, err)
  460. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  461. noNetCS := &testCertState{
  462. version: cert.Version2,
  463. creds: map[cert.Version]*Credential{
  464. cert.Version2: NewCredential(ca, caHsBytes, caKey, ncs),
  465. },
  466. }
  467. respCS := newTestCertState(
  468. t, ca, caKey, "responder",
  469. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  470. )
  471. _, respM, _, _, err := initiateHandshake(
  472. t, noNetCS, testVerifier(caPool),
  473. respCS, testVerifier(caPool),
  474. )
  475. require.Error(t, err)
  476. assert.True(t, respM.Failed())
  477. }
  478. func TestMachineDifferentCAs(t *testing.T) {
  479. ca1, _, caKey1, _ := ct.NewTestCaCert(
  480. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  481. )
  482. ca2, _, caKey2, _ := ct.NewTestCaCert(
  483. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  484. )
  485. initCS := newTestCertState(
  486. t, ca1, caKey1, "init",
  487. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  488. )
  489. respCS := newTestCertState(
  490. t, ca2, caKey2, "resp",
  491. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")},
  492. )
  493. _, respM, _, _, err := initiateHandshake(
  494. t, initCS, testVerifier(ct.NewTestCAPool(ca1)),
  495. respCS, testVerifier(ct.NewTestCAPool(ca2)),
  496. )
  497. require.ErrorContains(t, err, "verify cert")
  498. assert.True(t, respM.Failed())
  499. }
  500. func TestMachineVersionNegotiation(t *testing.T) {
  501. ca1, _, caKey1, _ := ct.NewTestCaCert(
  502. cert.Version1, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  503. )
  504. ca2, _, caKey2, _ := ct.NewTestCaCert(
  505. cert.Version2, cert.Curve_CURVE25519, time.Time{}, time.Time{}, nil, nil, nil,
  506. )
  507. caPool := ct.NewTestCAPool(ca1, ca2)
  508. makeMultiVersionResp := func(t *testing.T) *testCertState {
  509. t.Helper()
  510. respCertV1, _, respKeyPEM, _ := ct.NewTestCert(
  511. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  512. ca1.NotBefore(), ca1.NotAfter(),
  513. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  514. )
  515. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  516. respCertV2, _ := ct.NewTestCertDifferentVersion(respCertV1, cert.Version2, ca2, caKey2)
  517. respHsV1, _ := respCertV1.MarshalForHandshakes()
  518. respHsV2, _ := respCertV2.MarshalForHandshakes()
  519. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  520. return &testCertState{
  521. version: cert.Version1,
  522. creds: map[cert.Version]*Credential{
  523. cert.Version1: NewCredential(respCertV1, respHsV1, respKey, ncs),
  524. cert.Version2: NewCredential(respCertV2, respHsV2, respKey, ncs),
  525. },
  526. }
  527. }
  528. t.Run("responder matches initiator version", func(t *testing.T) {
  529. initCS := newTestCertState(
  530. t, ca2, caKey2, "init",
  531. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  532. )
  533. respCS := makeMultiVersionResp(t)
  534. v := testVerifier(caPool)
  535. initM, _, respResult, resp, err := initiateHandshake(
  536. t, initCS, v,
  537. respCS, v,
  538. )
  539. require.NoError(t, err)
  540. require.NotNil(t, respResult)
  541. assert.Equal(t, cert.Version2, respResult.MyCert.Version(),
  542. "responder should negotiate to initiator's version")
  543. _, initResult, err := initM.ProcessPacket(nil, resp)
  544. require.NoError(t, err)
  545. require.NotNil(t, initResult)
  546. assert.Equal(t, cert.Version2, initResult.RemoteCert.Certificate.Version(),
  547. "initiator should see V2 cert from responder")
  548. })
  549. t.Run("responder keeps version when no match available", func(t *testing.T) {
  550. initCS := newTestCertState(
  551. t, ca2, caKey2, "init",
  552. []netip.Prefix{netip.MustParsePrefix("10.0.0.1/24")},
  553. )
  554. respCert, _, respKeyPEM, _ := ct.NewTestCert(
  555. cert.Version1, cert.Curve_CURVE25519, ca1, caKey1, "resp",
  556. ca1.NotBefore(), ca1.NotAfter(),
  557. []netip.Prefix{netip.MustParsePrefix("10.0.0.2/24")}, nil, nil,
  558. )
  559. respKey, _, _, _ := cert.UnmarshalPrivateKeyFromPEM(respKeyPEM)
  560. respHs, _ := respCert.MarshalForHandshakes()
  561. ncs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashSHA256)
  562. respCS := &testCertState{
  563. version: cert.Version1,
  564. creds: map[cert.Version]*Credential{
  565. cert.Version1: NewCredential(respCert, respHs, respKey, ncs),
  566. },
  567. }
  568. v := testVerifier(caPool)
  569. _, _, respResult, _, err := initiateHandshake(
  570. t, initCS, v,
  571. respCS, v,
  572. )
  573. require.NoError(t, err)
  574. require.NotNil(t, respResult)
  575. assert.Equal(t, cert.Version1, respResult.MyCert.Version(),
  576. "responder should keep V1 when V2 not available")
  577. })
  578. }