machine_test.go 23 KB

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