machine.go 13 KB

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