protocol.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package aerotun
  2. import (
  3. "crypto/cipher"
  4. "crypto/hmac"
  5. "crypto/rand"
  6. "crypto/sha256"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "sync"
  11. "golang.org/x/crypto/chacha20poly1305"
  12. )
  13. // Version 2 binds data to fresh sender AND receiver boot identities. Directional
  14. // keys and XChaCha nonces cannot overlap between peers or restarts.
  15. const (
  16. headerSize = 44 // magic(2), version(1), flags(1), sender(16), receiver(16), counter(8)
  17. flagHello = 4
  18. flagAck = 8
  19. flagConfirm = 16
  20. maxPacketSize = 65535
  21. )
  22. type sessionID [16]byte
  23. var zeroSession sessionID
  24. var errReplay = errors.New("replayed or stale packet")
  25. var errSession = errors.New("peer session is not established")
  26. type replayWindow struct{ max, bits uint64 }
  27. func (w *replayWindow) accept(n uint64) bool {
  28. if n == 0 {
  29. return false
  30. }
  31. if n > w.max {
  32. d := n - w.max
  33. if d >= 64 {
  34. w.bits = 1
  35. } else {
  36. w.bits = (w.bits << d) | 1
  37. }
  38. w.max = n
  39. return true
  40. }
  41. d := w.max - n
  42. if d >= 64 || w.bits&(uint64(1)<<d) != 0 {
  43. return false
  44. }
  45. w.bits |= uint64(1) << d
  46. return true
  47. }
  48. type protocolState struct {
  49. mu sync.Mutex
  50. secret [32]byte
  51. local, remote sessionID
  52. counter uint64
  53. replay replayWindow
  54. retired map[sessionID]bool
  55. tx, rx cipher.AEAD
  56. }
  57. func newProtocol(psk string) (*protocolState, error) {
  58. p := &protocolState{secret: sha256.Sum256([]byte(psk)), retired: make(map[sessionID]bool)}
  59. if _, err := rand.Read(p.local[:]); err != nil {
  60. return nil, err
  61. }
  62. return p, nil
  63. }
  64. func (p *protocolState) aead(sender, receiver sessionID) cipher.AEAD {
  65. mac := hmac.New(sha256.New, p.secret[:])
  66. mac.Write([]byte("AeroTUN-v2-directional-key"))
  67. mac.Write(sender[:])
  68. mac.Write(receiver[:])
  69. a, _ := chacha20poly1305.NewX(mac.Sum(nil))
  70. return a
  71. }
  72. func packetNonce(sender sessionID, counter uint64) []byte {
  73. nonce := make([]byte, chacha20poly1305.NonceSizeX)
  74. copy(nonce, sender[:])
  75. binary.BigEndian.PutUint64(nonce[16:], counter)
  76. return nonce
  77. }
  78. func (p *protocolState) seal(flags byte, payload []byte, target sessionID) ([]byte, error) {
  79. p.mu.Lock()
  80. defer p.mu.Unlock()
  81. if target == zeroSession && flags != flagHello {
  82. target = p.remote
  83. }
  84. if target == zeroSession && flags != flagHello {
  85. return nil, errSession
  86. }
  87. if p.counter == ^uint64(0) {
  88. return nil, fmt.Errorf("packet counter exhausted; restart tunnel")
  89. }
  90. p.counter++
  91. b := make([]byte, headerSize, headerSize+len(payload)+chacha20poly1305.Overhead)
  92. binary.BigEndian.PutUint16(b, MagicHeader)
  93. b[2] = ProtocolVersion
  94. b[3] = flags
  95. copy(b[4:20], p.local[:])
  96. copy(b[20:36], target[:])
  97. binary.BigEndian.PutUint64(b[36:], p.counter)
  98. a := p.tx
  99. if target != p.remote || flags == flagHello || a == nil {
  100. a = p.aead(p.local, target)
  101. }
  102. return a.Seal(b, packetNonce(p.local, p.counter), payload, b), nil
  103. }
  104. // Authenticate before changing replay or peer state. Only an ACK addressed to
  105. // our fresh boot ID (ACK or CONFIRM) establishes a session; HELLOs cannot
  106. // replace a working peer.
  107. func (p *protocolState) open(b []byte) (payload []byte, flags byte, sender sessionID, changed bool, err error) {
  108. p.mu.Lock()
  109. defer p.mu.Unlock()
  110. if len(b) < headerSize+chacha20poly1305.Overhead || binary.BigEndian.Uint16(b) != MagicHeader || b[2] != ProtocolVersion {
  111. err = errors.New("invalid AeroTUN v2 packet")
  112. return
  113. }
  114. flags = b[3]
  115. if flags != 0 && flags != FlagCompressedLZ4 && flags != FlagKeepalive && flags != flagHello && flags != flagAck && flags != flagConfirm {
  116. err = errors.New("invalid packet flags")
  117. return
  118. }
  119. copy(sender[:], b[4:20])
  120. var receiver sessionID
  121. copy(receiver[:], b[20:36])
  122. if sender == zeroSession || sender == p.local || (flags == flagHello && receiver != zeroSession) || (flags != flagHello && receiver != p.local) {
  123. err = errSession
  124. return
  125. }
  126. n := binary.BigEndian.Uint64(b[36:44])
  127. if n == 0 {
  128. err = errReplay
  129. return
  130. }
  131. a := p.rx
  132. if sender != p.remote || flags == flagHello || a == nil {
  133. a = p.aead(sender, receiver)
  134. }
  135. payload, err = a.Open(nil, packetNonce(sender, n), b[headerSize:], b[:headerSize])
  136. if err != nil {
  137. return
  138. }
  139. if flags != 0 && flags != FlagCompressedLZ4 && (len(payload) != 2 || binary.BigEndian.Uint16(payload) < 576 || binary.BigEndian.Uint16(payload) > 9000) {
  140. err = errSession
  141. return
  142. }
  143. if flags == flagHello {
  144. return
  145. }
  146. if sender != p.remote {
  147. if (flags != flagAck && flags != flagConfirm) || p.retired[sender] || len(p.retired) >= 1024 {
  148. err = errSession
  149. return
  150. }
  151. if p.remote != zeroSession {
  152. p.retired[p.remote] = true
  153. }
  154. p.remote = sender
  155. p.replay = replayWindow{}
  156. p.tx = p.aead(p.local, sender)
  157. p.rx = a
  158. changed = true
  159. }
  160. if !p.replay.accept(n) {
  161. err = errReplay
  162. return
  163. }
  164. return
  165. }