machine.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. package handshake
  2. import (
  3. "bytes"
  4. "fmt"
  5. "slices"
  6. "time"
  7. "github.com/flynn/noise"
  8. "github.com/slackhq/nebula/cert"
  9. "github.com/slackhq/nebula/header"
  10. )
  11. // IndexAllocator is called by the Machine to allocate a local index for the
  12. // handshake. It is called at most once, when the first outgoing message that
  13. // carries a payload is built.
  14. //
  15. // Implementations MUST NOT return 0. Zero is reserved as a sentinel meaning
  16. // "no index assigned" on the wire and in the payload-presence checks. If an
  17. // allocator ever returned 0, a legitimate handshake's payload could be
  18. // indistinguishable from an empty one and would be rejected.
  19. type IndexAllocator func() (uint32, error)
  20. // CertVerifier is called by the Machine after reconstructing the peer's
  21. // certificate from the handshake. The verifier performs all validation
  22. // (CA trust, expiry, policy checks, allow lists).
  23. type CertVerifier func(cert.Certificate) (*cert.CachedCertificate, error)
  24. // Result contains the results of a successful handshake.
  25. // Returned by ProcessPacket when the handshake is complete.
  26. type Result struct {
  27. EKey *noise.CipherState
  28. DKey *noise.CipherState
  29. Cipher noise.CipherFunc // identifies which post-handshake CipherState the data plane should wrap EKey/DKey in
  30. MyCert cert.Certificate
  31. RemoteCert *cert.CachedCertificate
  32. RemoteIndex uint32
  33. LocalIndex uint32
  34. HandshakeTime uint64
  35. MessageIndex uint64 // number of messages exchanged during the handshake
  36. Initiator bool
  37. // Multiport lane negotiation, from the peer's LaneDetails. All zero when
  38. // the peer did not advertise (vanilla peer or multiport disabled).
  39. // PeerTxLanes is how many lanes the peer may send on, which is how many lane
  40. // sessions we need in order to receive everything it sends.
  41. PeerPortCount uint32
  42. PeerBasePort uint32
  43. PeerTxLanes uint32
  44. }
  45. // Machine drives a Noise handshake through N messages. It handles Noise
  46. // protocol operations, certificate reconstruction, and payload encoding.
  47. // Certificate validation is delegated to the caller via CertVerifier.
  48. //
  49. // A Machine is not safe for concurrent use. The caller must ensure that
  50. // Initiate and ProcessPacket are not called concurrently.
  51. //
  52. // Error contract: when ProcessPacket or Initiate returns an error, callers
  53. // must check Failed() to decide what to do next. If Failed() is false the
  54. // underlying noise state was not advanced (the packet was rejected before
  55. // ReadMessage took effect, or the rejection is non-fatal like a stale
  56. // retransmit) and the Machine can accept another packet. If Failed() is
  57. // true the Machine is unrecoverable and the caller must abandon it.
  58. type Machine struct {
  59. hs *noise.HandshakeState
  60. getCred GetCredentialFunc
  61. allocIndex IndexAllocator
  62. verifier CertVerifier
  63. result *Result
  64. msgs []msgFlags
  65. lanes *LaneDetails // our multiport advert; nil emits a vanilla payload
  66. myVersion cert.Version
  67. subtype header.MessageSubType
  68. indexAllocated bool
  69. remoteCertSet bool
  70. payloadSet bool
  71. failed bool
  72. }
  73. // NewMachine creates a handshake state machine. The subtype determines both
  74. // the noise pattern and the per-message content layout. The credential for
  75. // `version` is fetched via getCred and used to seed the noise.HandshakeState.
  76. // IndexAllocator is called lazily when the first outgoing payload is built.
  77. // lanes, when non-nil, is emitted as this side's multiport advert on every
  78. // payload-bearing message; nil produces byte-identical vanilla payloads.
  79. func NewMachine(
  80. version cert.Version,
  81. getCred GetCredentialFunc,
  82. verifier CertVerifier,
  83. allocIndex IndexAllocator,
  84. initiator bool,
  85. subtype header.MessageSubType,
  86. lanes *LaneDetails,
  87. ) (*Machine, error) {
  88. info, err := subtypeInfoFor(subtype)
  89. if err != nil {
  90. return nil, err
  91. }
  92. cred := getCred(version)
  93. if cred == nil {
  94. return nil, fmt.Errorf("%w: %v", ErrNoCredential, version)
  95. }
  96. hs, err := cred.buildHandshakeState(initiator, info.pattern)
  97. if err != nil {
  98. return nil, fmt.Errorf("build noise state: %w", err)
  99. }
  100. return &Machine{
  101. hs: hs,
  102. subtype: subtype,
  103. msgs: info.msgs,
  104. getCred: getCred,
  105. allocIndex: allocIndex,
  106. verifier: verifier,
  107. lanes: lanes,
  108. myVersion: version,
  109. result: &Result{
  110. Initiator: initiator,
  111. Cipher: cred.cipherSuite,
  112. },
  113. }, nil
  114. }
  115. // Failed returns true if the Machine is in an unrecoverable state.
  116. func (m *Machine) Failed() bool {
  117. return m.failed
  118. }
  119. // Subtype returns the handshake subtype this Machine was built for.
  120. func (m *Machine) Subtype() header.MessageSubType {
  121. return m.subtype
  122. }
  123. // MessageIndex returns the noise handshake message index, which equals the
  124. // wire counter of the most recently sent or received message.
  125. func (m *Machine) MessageIndex() int {
  126. return m.hs.MessageIndex()
  127. }
  128. // requireComplete checks that both a peer cert and payload have been received.
  129. // Marks the machine as failed if not.
  130. func (m *Machine) requireComplete() error {
  131. if !m.payloadSet || !m.remoteCertSet {
  132. m.failed = true
  133. return ErrIncompleteHandshake
  134. }
  135. return nil
  136. }
  137. // myMsgFlags returns the flags for the current outgoing message.
  138. func (m *Machine) myMsgFlags() msgFlags {
  139. idx := m.hs.MessageIndex()
  140. if idx < len(m.msgs) {
  141. return m.msgs[idx]
  142. }
  143. return msgFlags{}
  144. }
  145. // peerMsgFlags returns the flags for the message we just read.
  146. func (m *Machine) peerMsgFlags() msgFlags {
  147. idx := m.hs.MessageIndex() - 1
  148. if idx >= 0 && idx < len(m.msgs) {
  149. return m.msgs[idx]
  150. }
  151. return msgFlags{}
  152. }
  153. // Initiate produces the first handshake message. Only valid for initiators,
  154. // and must be called exactly once before ProcessPacket.
  155. //
  156. // out is a destination buffer the message is appended to and returned. Pass
  157. // nil to allocate fresh, or pass a re-used buffer sliced to length 0 (e.g.
  158. // buf[:0]) with sufficient capacity to avoid allocation.
  159. //
  160. // An error return may not indicate a fatal condition, check Failed() to
  161. // determine if the Machine can still be used.
  162. func (m *Machine) Initiate(out []byte) ([]byte, error) {
  163. if m.failed {
  164. return nil, ErrMachineFailed
  165. }
  166. if !m.result.Initiator {
  167. m.failed = true
  168. return nil, ErrInitiateOnResponder
  169. }
  170. if m.hs.MessageIndex() != 0 {
  171. m.failed = true
  172. return nil, ErrInitiateAlreadyCalled
  173. }
  174. // At MessageIndex=0 with RemoteIndex still zero, buildResponse produces
  175. // header counter 1 and remote index 0, which is what the initial message needs.
  176. out, _, _, err := m.buildResponse(out)
  177. if err != nil {
  178. m.failed = true
  179. return nil, err
  180. }
  181. return out, nil
  182. }
  183. // ProcessPacket handles an incoming handshake message. It advances the Noise
  184. // state, validates the peer certificate via the verifier, and optionally
  185. // produces a response.
  186. //
  187. // out is a destination buffer the response is appended to and returned. Pass
  188. // nil to allocate fresh, or pass a re-used buffer sliced to length 0 (e.g.
  189. // buf[:0]) with sufficient capacity to avoid allocation. The returned slice
  190. // is nil when no outgoing message is produced (handshake complete on this
  191. // side, or final message of a multi-message pattern).
  192. //
  193. // Returns a non-nil Result when the handshake is complete.
  194. // An error return may not indicate a fatal condition, check Failed() to
  195. // determine if the Machine can still be used.
  196. func (m *Machine) ProcessPacket(out, packet []byte) ([]byte, *Result, error) {
  197. if m.failed {
  198. return nil, nil, ErrMachineFailed
  199. }
  200. if len(packet) < header.Len {
  201. return nil, nil, ErrPacketTooShort
  202. }
  203. // Reject packets whose subtype doesn't match the one this Machine was
  204. // built for. A pending handshake that suddenly receives a different
  205. // subtype on its index is either a stray packet that matched by chance
  206. // or a peer protocol violation; drop it without failing the Machine so
  207. // the legitimate retransmit can still complete.
  208. if header.MessageSubType(packet[1]) != m.subtype {
  209. return nil, nil, ErrSubtypeMismatch
  210. }
  211. if m.result.Initiator && m.hs.MessageIndex() == 0 {
  212. m.failed = true
  213. return nil, nil, ErrInitiateNotCalled
  214. }
  215. // The (eKey, dKey) ordering here is correct for IX, where the initiator
  216. // completes the handshake by reading the responder's stage-2 message.
  217. // noise returns (cs1, cs2) where cs1 is the initiator->responder cipher.
  218. // For 3-message patterns where a responder finishes by reading the final
  219. // message, this ordering would be wrong; revisit when XX/pqIX lands.
  220. msg, eKey, dKey, err := m.hs.ReadMessage(nil, packet[header.Len:])
  221. if err != nil {
  222. // Noise ReadMessage failed. The noise library checkpoints and rolls back
  223. // on failure, so the Machine is still alive. The caller can retry with
  224. // a different packet.
  225. return nil, nil, fmt.Errorf("noise ReadMessage: %w", err)
  226. }
  227. // From here on, noise state has advanced. Any error is fatal.
  228. flags := m.peerMsgFlags()
  229. if err := m.processPayload(msg, flags); err != nil {
  230. return nil, nil, err
  231. }
  232. // If ReadMessage derived keys, the handshake is complete. Noise should
  233. // always produce both keys together; asymmetry is a protocol invariant
  234. // violation.
  235. if eKey != nil || dKey != nil {
  236. if eKey == nil || dKey == nil {
  237. m.failed = true
  238. return nil, nil, ErrAsymmetricCipherKeys
  239. }
  240. if err := m.requireComplete(); err != nil {
  241. return nil, nil, err
  242. }
  243. return nil, m.completed(eKey, dKey), nil
  244. }
  245. // ReadMessage didn't complete, produce the next outgoing message
  246. out, dk, ek, err := m.buildResponse(out)
  247. if err != nil {
  248. m.failed = true
  249. return nil, nil, err
  250. }
  251. if ek != nil || dk != nil {
  252. if ek == nil || dk == nil {
  253. m.failed = true
  254. return nil, nil, ErrAsymmetricCipherKeys
  255. }
  256. if err := m.requireComplete(); err != nil {
  257. return nil, nil, err
  258. }
  259. return out, m.completed(ek, dk), nil
  260. }
  261. return out, nil, nil
  262. }
  263. func (m *Machine) completed(eKey, dKey *noise.CipherState) *Result {
  264. m.result.EKey = eKey
  265. m.result.DKey = dKey
  266. m.result.MessageIndex = uint64(m.hs.MessageIndex())
  267. return m.result
  268. }
  269. func (m *Machine) processPayload(msg []byte, flags msgFlags) error {
  270. if len(msg) == 0 {
  271. if flags.expectsPayload || flags.expectsCert {
  272. m.failed = true
  273. return ErrMissingContent
  274. }
  275. return nil
  276. }
  277. payload, err := UnmarshalPayload(msg)
  278. if err != nil {
  279. m.failed = true
  280. return fmt.Errorf("unmarshal handshake: %w", err)
  281. }
  282. // Assert the payload contains exactly what we expect
  283. hasPayloadData := payload.InitiatorIndex != 0 || payload.ResponderIndex != 0 || payload.Time != 0 ||
  284. payload.InitiatorLanes != nil || payload.ResponderLanes != nil
  285. if hasPayloadData != flags.expectsPayload {
  286. m.failed = true
  287. return ErrUnexpectedContent
  288. }
  289. hasCertData := len(payload.Cert) > 0
  290. if hasCertData != flags.expectsCert {
  291. m.failed = true
  292. return ErrUnexpectedContent
  293. }
  294. // Process payload
  295. if flags.expectsPayload {
  296. var remoteIndex uint32
  297. if m.result.Initiator {
  298. remoteIndex = payload.ResponderIndex
  299. } else {
  300. remoteIndex = payload.InitiatorIndex
  301. }
  302. // The payload presence check above can be satisfied by Time alone, so a payload
  303. // could still carry a zero index here. We need to reject it.
  304. if remoteIndex == 0 {
  305. m.failed = true
  306. return ErrInvalidRemoteIndex
  307. }
  308. m.result.RemoteIndex = remoteIndex
  309. m.result.HandshakeTime = payload.Time
  310. m.payloadSet = true
  311. // Multiport advert from the peer's side of the exchange. Out-of-range
  312. // values mean a peer we can't pair lanes with; ignore the advert
  313. // rather than failing the handshake — the tunnel itself is fine, it
  314. // just won't get lanes. Semantic policing (port-count caps, lane
  315. // clamping) belongs to the handshake manager.
  316. var peerLanes *LaneDetails
  317. if m.result.Initiator {
  318. peerLanes = payload.ResponderLanes
  319. } else {
  320. peerLanes = payload.InitiatorLanes
  321. }
  322. if peerLanes != nil && peerLanes.BasePort <= 0xffff && peerLanes.PortCount <= 0xffff && peerLanes.TxLanes <= 0xffff {
  323. m.result.PeerPortCount = peerLanes.PortCount
  324. m.result.PeerBasePort = peerLanes.BasePort
  325. m.result.PeerTxLanes = peerLanes.TxLanes
  326. }
  327. }
  328. // Process certificate
  329. if flags.expectsCert {
  330. if err := m.validateCert(payload); err != nil {
  331. return err
  332. }
  333. }
  334. return nil
  335. }
  336. func (m *Machine) validateCert(payload Payload) error {
  337. cred := m.getCred(m.myVersion)
  338. if cred == nil {
  339. m.failed = true
  340. return fmt.Errorf("%w: %v", ErrNoCredential, m.myVersion)
  341. }
  342. rc, err := cert.Recombine(
  343. cert.Version(payload.CertVersion),
  344. payload.Cert,
  345. m.hs.PeerStatic(),
  346. cred.Cert.Curve(),
  347. )
  348. if err != nil {
  349. m.failed = true
  350. return fmt.Errorf("recombine cert: %w", err)
  351. }
  352. if !bytes.Equal(rc.PublicKey(), m.hs.PeerStatic()) {
  353. m.failed = true
  354. return ErrPublicKeyMismatch
  355. }
  356. // Version negotiation, if the peer sent a different version and we have it, switch
  357. if rc.Version() != m.myVersion {
  358. if m.getCred(rc.Version()) != nil {
  359. m.myVersion = rc.Version()
  360. }
  361. }
  362. verified, err := m.verifier(rc)
  363. if err != nil {
  364. m.failed = true
  365. return fmt.Errorf("verify cert: %w", err)
  366. }
  367. m.result.RemoteCert = verified
  368. m.remoteCertSet = true
  369. return nil
  370. }
  371. func (m *Machine) marshalOutgoing(flags msgFlags) ([]byte, error) {
  372. if !flags.expectsPayload && !flags.expectsCert {
  373. return nil, nil
  374. }
  375. var p Payload
  376. if flags.expectsPayload {
  377. if !m.indexAllocated {
  378. index, err := m.allocIndex()
  379. if err != nil {
  380. return nil, fmt.Errorf("%w: %w", ErrIndexAllocation, err)
  381. }
  382. m.result.LocalIndex = index
  383. m.indexAllocated = true
  384. }
  385. if m.result.Initiator {
  386. p.InitiatorIndex = m.result.LocalIndex
  387. p.InitiatorLanes = m.lanes
  388. } else {
  389. p.ResponderIndex = m.result.LocalIndex
  390. p.InitiatorIndex = m.result.RemoteIndex
  391. p.ResponderLanes = m.lanes
  392. }
  393. p.Time = uint64(time.Now().UnixNano())
  394. }
  395. if flags.expectsCert {
  396. cred := m.getCred(m.myVersion)
  397. if cred == nil {
  398. return nil, fmt.Errorf("%w: %v", ErrNoCredential, m.myVersion)
  399. }
  400. p.Cert = cred.Bytes
  401. p.CertVersion = uint32(cred.Cert.Version())
  402. m.result.MyCert = cred.Cert
  403. }
  404. return MarshalPayload(nil, p), nil
  405. }
  406. func (m *Machine) buildResponse(out []byte) ([]byte, *noise.CipherState, *noise.CipherState, error) {
  407. flags := m.myMsgFlags()
  408. hsBytes, err := m.marshalOutgoing(flags)
  409. if err != nil {
  410. return nil, nil, nil, err
  411. }
  412. // Extend out by header.Len to make room for the header. slices.Grow is a
  413. // no-op when the cap is already sufficient (the zero-copy case where the
  414. // caller passed a pre-sized buffer). header.Encode overwrites the new
  415. // bytes, so they don't need to be zeroed.
  416. start := len(out)
  417. out = slices.Grow(out, header.Len)[:start+header.Len]
  418. header.Encode(
  419. out[start:],
  420. header.Version, header.Handshake, m.subtype,
  421. m.result.RemoteIndex,
  422. uint64(m.hs.MessageIndex()+1),
  423. )
  424. // noise.WriteMessage appends the encrypted handshake message to out,
  425. // reusing capacity when present.
  426. //
  427. // The (dKey, eKey) ordering here is correct for IX, where the responder
  428. // completes the handshake by writing the stage-2 message. noise returns
  429. // (cs1, cs2) where cs1 is the initiator->responder cipher (which is the
  430. // responder's decrypt key). For 3-message patterns where an initiator
  431. // finishes by writing the final message, this ordering would be wrong;
  432. // revisit when XX/pqIX lands.
  433. out, dKey, eKey, err := m.hs.WriteMessage(out, hsBytes)
  434. if err != nil {
  435. return nil, nil, nil, fmt.Errorf("noise WriteMessage: %w", err)
  436. }
  437. return out, dKey, eKey, nil
  438. }