1
0

machine.go 14 KB

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