machine.go 15 KB

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