machine.go 15 KB

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