machine.go 15 KB

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