meek.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /*
  2. * Copyright (c) 2025, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package protocol
  20. import (
  21. "bytes"
  22. "crypto/aes"
  23. "crypto/cipher"
  24. "crypto/sha256"
  25. "encoding/binary"
  26. std_errors "errors"
  27. "io"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  29. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  30. "golang.org/x/crypto/hkdf"
  31. )
  32. const (
  33. meekPayloadPaddingPrefixNoPadding = 0
  34. meekPayloadPaddingPrefixPadding = 1
  35. meekPayloadPaddingDirectionRequests = "meek-payload-padding-requests"
  36. meekPayloadPaddingDirectionResponses = "meek-payload-padding-responses"
  37. meekPayloadPaddingReceiverConsumeStatePrefix = 0
  38. meekPayloadPaddingReceiverConsumeStateSizeByte1 = 1
  39. meekPayloadPaddingReceiverConsumeStateSizeByte2 = 2
  40. meekPayloadPaddingReceiverConsumeStatePadding = 3
  41. MeekPayloadPaddingPrefixSize = 1
  42. )
  43. // MeekPayloadPaddingState provides support for padding empty meek payloads,
  44. // to vary request and response traffic shapes.
  45. //
  46. // The padding is to be prepended directly to the payloads, the request and
  47. // response bodies, and is intended to be indistinguishable from the fully
  48. // encrypted OSSH payload which is observable in HTTP and decrypted HTTPS.
  49. // The padding header prefix and size are obfuscated with a stream cipher,
  50. // while the padding itself is plain random bytes.
  51. //
  52. // Both the meek client and server will use two MeekPayloadPaddingState
  53. // instances in payload padding mode: one for request padding and one for
  54. // response padding. Each client/server pair requires the obfuscation cipher
  55. // state to be kept in sync; the caller is responsible for handling meek
  56. // retries in such a way that this synchronization is maintained.
  57. //
  58. // MeekPayloadPaddingState also supports omitting padding entirely, with some
  59. // probability, to further vary traffic shapes.
  60. //
  61. // Each MeekPayloadPaddingState instance may only be used for one direction
  62. // only, sending or receiving.
  63. type MeekPayloadPaddingState struct {
  64. stream cipher.Stream
  65. // Sender state
  66. omitPaddingProbability float64
  67. minPaddingSize int
  68. maxPaddingSize int
  69. senderPaddingHeaderBuffer bytes.Buffer
  70. // Receiver state
  71. receiverConsumeState int
  72. receiverPaddingBytesRemaining int
  73. receiverPaddingPreamble [3]byte
  74. receiverConsumeBuffer [1024]byte
  75. }
  76. // NewMeekRequestPayloadPaddingState initializes a MeekPayloadPaddingState for
  77. // sending or receiving padded requests.
  78. func NewMeekRequestPayloadPaddingState(
  79. obfuscatedMeekKey string,
  80. obfuscatedMeekCookie string,
  81. omitPaddingProbability float64,
  82. minPaddingSize int,
  83. maxPaddingSize int) (*MeekPayloadPaddingState, error) {
  84. state, err := newMeekPayloadPaddingState(
  85. meekPayloadPaddingDirectionRequests,
  86. obfuscatedMeekKey,
  87. obfuscatedMeekCookie,
  88. omitPaddingProbability,
  89. minPaddingSize,
  90. maxPaddingSize)
  91. if err != nil {
  92. return state, errors.Trace(err)
  93. }
  94. return state, nil
  95. }
  96. // NewMeekResponsePayloadPaddingState initializes a MeekPayloadPaddingState for
  97. // sending or receiving padded responses.
  98. func NewMeekResponsePayloadPaddingState(
  99. obfuscatedMeekKey string,
  100. obfuscatedMeekCookie string,
  101. omitPaddingProbability float64,
  102. minPaddingSize int,
  103. maxPaddingSize int) (*MeekPayloadPaddingState, error) {
  104. state, err := newMeekPayloadPaddingState(
  105. meekPayloadPaddingDirectionResponses,
  106. obfuscatedMeekKey,
  107. obfuscatedMeekCookie,
  108. omitPaddingProbability,
  109. minPaddingSize,
  110. maxPaddingSize)
  111. if err != nil {
  112. return state, errors.Trace(err)
  113. }
  114. return state, nil
  115. }
  116. func newMeekPayloadPaddingState(
  117. direction string,
  118. obfuscatedMeekKey string,
  119. obfuscatedMeekCookie string,
  120. omitPaddingProbability float64,
  121. minPaddingSize int,
  122. maxPaddingSize int) (*MeekPayloadPaddingState, error) {
  123. // Maximum padding length of 65533 is the max meek request size, 65536,
  124. // less 3 byte padding header with prefix and length bytes.
  125. if minPaddingSize < 0 ||
  126. minPaddingSize > maxPaddingSize ||
  127. maxPaddingSize > 65533 {
  128. return nil, errors.TraceNew("invalid padding size")
  129. }
  130. state := &MeekPayloadPaddingState{
  131. omitPaddingProbability: omitPaddingProbability,
  132. minPaddingSize: minPaddingSize,
  133. maxPaddingSize: maxPaddingSize,
  134. }
  135. // For the cipher stream applied to the padding header, derive a unique
  136. // key using a value unknown to an adversary (obfuscatedMeekKey), a
  137. // unique nonce per flow (obfuscatedMeekCookie), and a unique salt or
  138. // context for the direction (request/response), all ensuring that the
  139. // adversary observing the stream cannot distinguish the encrypted
  140. // padding header from random bytes either by directly decrypting it or
  141. // xoring various bytes together.
  142. //
  143. // A stream cipher is used to minimize payload overhead. Given the unique
  144. // key per flow and direction, an all zeroes IV suffices, saving payload
  145. // bytes. There's no authentication, also to save payload bytes and
  146. // maximize shape distribution; The underlying SSH layer provides proper
  147. // authentication and full transport security for actual tunneled traffic.
  148. var key [32]byte
  149. var iv [aes.BlockSize]byte
  150. _, err := io.ReadFull(
  151. hkdf.New(
  152. sha256.New,
  153. []byte(obfuscatedMeekKey),
  154. []byte(obfuscatedMeekCookie),
  155. []byte(direction)),
  156. key[:])
  157. if err != nil {
  158. return nil, errors.Trace(err)
  159. }
  160. block, err := aes.NewCipher(key[:])
  161. if err != nil {
  162. return nil, errors.Trace(err)
  163. }
  164. state.stream = cipher.NewCTR(block, iv[:])
  165. return state, nil
  166. }
  167. // SenderGetNextPadding returns the next obfuscated padding header and padding
  168. // bytes. When addPadding is false, the returned header contains only the
  169. // NoPadding prefix. Otherwise, a full padding header and padding bytes are
  170. // returned. With omitPaddingProbability, in the addPadding true case an
  171. // empty header may be returned, allowing for zero byte payloads, saving some
  172. // data and further varying the traffic shape.
  173. //
  174. // The returned slice is only valid until the next SenderGetNextPadding call.
  175. //
  176. // Not safe for concurrent use.
  177. func (state *MeekPayloadPaddingState) SenderGetNextPadding(
  178. addPadding bool) ([]byte, error) {
  179. // As a future enhancement, consider adding a new state and prefix,
  180. // meekPayloadPaddingPrefixEndPadding. After sufficient packets, this
  181. // prefix is sent, and no further padding, including prefix, will be
  182. // added. The challenge with this is that meek resiliency and
  183. // MeekRedialTLSProbability both result in new TCP flows for the same
  184. // meek session, flow which would presumably need to start adding padding
  185. // again, requiring some mechanism to signal this; with an intermediary
  186. // such as a CDN, the server won't be able to infer new TCP flows simply
  187. // at the socket
  188. state.senderPaddingHeaderBuffer.Reset()
  189. if addPadding && prng.FlipWeightedCoin(state.omitPaddingProbability) {
  190. // With the given probability, select no padding header at all.
  191. return state.senderPaddingHeaderBuffer.Bytes(), nil
  192. }
  193. if !addPadding {
  194. var preamble [1]byte
  195. preamble[0] = meekPayloadPaddingPrefixNoPadding
  196. state.stream.XORKeyStream(preamble[:], preamble[:])
  197. state.senderPaddingHeaderBuffer.Write(preamble[:])
  198. return state.senderPaddingHeaderBuffer.Bytes(), nil
  199. }
  200. paddingSize := prng.Range(state.minPaddingSize, state.maxPaddingSize)
  201. var preamble [3]byte
  202. preamble[0] = meekPayloadPaddingPrefixPadding
  203. binary.BigEndian.PutUint16(preamble[1:3], uint16(paddingSize))
  204. state.stream.XORKeyStream(preamble[:], preamble[:])
  205. state.senderPaddingHeaderBuffer.Write(preamble[:])
  206. state.senderPaddingHeaderBuffer.Write(prng.Bytes(paddingSize))
  207. return state.senderPaddingHeaderBuffer.Bytes(), nil
  208. }
  209. var ErrMeekPaddingStateImmediateEOF = std_errors.New("immediate EOF")
  210. // ReceiverConsumePadding reads and consumes payload padding from the input
  211. // reader.
  212. //
  213. // The padding consists of a preamble with a 1 byte prefix, an optional 2 byte
  214. // size; followed the specified number of padding bytes, if any. The padding
  215. // header is deobfuscated using a cipher stream that should be kept in sync
  216. // with the corresponding sender state.
  217. //
  218. // ReceiverConsumePadding supports reading as little as 1 byte at a time from the
  219. // reader and statefully resuming on subsequent calls. retContinue true and a
  220. // non-nil retErr indicates a partial read; the caller should call
  221. // ReceiverConsumePadding to resume. There is no retContinue true and nil retErr
  222. // case.
  223. //
  224. // A special return value for retErr of ErrMeekPaddingStateImmediateEOF
  225. // indicates that the reader had 0 bytes, and this may be treated as an "omit
  226. // padding" case.
  227. //
  228. // retBytesRead is the number of bytes read from reader, even in error cases.
  229. //
  230. // Not safe for concurrent use.
  231. func (state *MeekPayloadPaddingState) ReceiverConsumePadding(
  232. reader io.Reader) (retBytesRead int64, retContinue bool, retErr error) {
  233. bytesRead := int64(0)
  234. for {
  235. // Use a state machine and read one byte at a time. This allows
  236. // MeekPayloadPaddingState.ReceiverConsumePadding to handle meek payload
  237. // partial reads which may return as little as one byte and an error.
  238. switch state.receiverConsumeState {
  239. case meekPayloadPaddingReceiverConsumeStatePrefix:
  240. n, err := io.ReadFull(reader, state.receiverPaddingPreamble[0:1])
  241. // Only 1 byte is requested, so there's no partial-read-with-error
  242. // case to handle.
  243. if err != nil {
  244. if err == io.EOF {
  245. // If the request/response body is empty, ReadFull will
  246. // immediately return io.EOF. The caller can use the
  247. // special ErrImmediateEOF return value to treat this
  248. // case as a success, allowing for actual empty payloads
  249. // in addition to padded payloads.
  250. return 0, false, ErrMeekPaddingStateImmediateEOF
  251. }
  252. return bytesRead, true, errors.TraceReader(err)
  253. }
  254. bytesRead += int64(n)
  255. state.stream.XORKeyStream(
  256. state.receiverPaddingPreamble[0:1],
  257. state.receiverPaddingPreamble[0:1])
  258. switch state.receiverPaddingPreamble[0] {
  259. case meekPayloadPaddingPrefixNoPadding:
  260. // With NoPadding, there's only 1 byte to read, so go back to
  261. // the start state.
  262. state.receiverConsumeState = meekPayloadPaddingReceiverConsumeStatePrefix
  263. case meekPayloadPaddingPrefixPadding:
  264. // Next states: read the 2 padding size bytes.
  265. state.receiverConsumeState = meekPayloadPaddingReceiverConsumeStateSizeByte1
  266. default:
  267. return bytesRead, false, errors.TraceNew("unknown padding prefix")
  268. }
  269. case meekPayloadPaddingReceiverConsumeStateSizeByte1:
  270. n, err := io.ReadFull(reader, state.receiverPaddingPreamble[1:2])
  271. if err != nil {
  272. return bytesRead, true, errors.TraceReader(err)
  273. }
  274. bytesRead += int64(n)
  275. state.stream.XORKeyStream(
  276. state.receiverPaddingPreamble[1:2],
  277. state.receiverPaddingPreamble[1:2])
  278. state.receiverConsumeState = meekPayloadPaddingReceiverConsumeStateSizeByte2
  279. case meekPayloadPaddingReceiverConsumeStateSizeByte2:
  280. n, err := io.ReadFull(reader, state.receiverPaddingPreamble[2:3])
  281. if err != nil {
  282. return bytesRead, true, errors.TraceReader(err)
  283. }
  284. bytesRead += int64(n)
  285. state.stream.XORKeyStream(
  286. state.receiverPaddingPreamble[2:3],
  287. state.receiverPaddingPreamble[2:3])
  288. // Since the obfuscation cipher has no authentication, we may
  289. // proceed with a corrupt or manipulated padding size; but the 2
  290. // bytes can only represent up to the max padding size of ~64K anyway.
  291. state.receiverPaddingBytesRemaining = int(
  292. binary.BigEndian.Uint16(state.receiverPaddingPreamble[1:3]))
  293. state.receiverConsumeState = meekPayloadPaddingReceiverConsumeStatePadding
  294. case meekPayloadPaddingReceiverConsumeStatePadding:
  295. // The size of receiverConsumeBuffer is chosen as a tradeoff
  296. // between memory overhead and I/O calls.
  297. for state.receiverPaddingBytesRemaining > 0 {
  298. m := state.receiverPaddingBytesRemaining
  299. if m > len(state.receiverConsumeBuffer) {
  300. m = len(state.receiverConsumeBuffer)
  301. }
  302. n, err := io.ReadFull(reader, state.receiverConsumeBuffer[0:m])
  303. bytesRead += int64(n)
  304. state.receiverPaddingBytesRemaining -= n
  305. if err != nil {
  306. return bytesRead, true, errors.TraceReader(err)
  307. }
  308. }
  309. // After all padding bytes are read, go back to the start state.
  310. state.receiverConsumeState = meekPayloadPaddingReceiverConsumeStatePrefix
  311. default:
  312. return bytesRead, false, errors.TraceNew("unknown consume padding state")
  313. }
  314. if state.receiverConsumeState == meekPayloadPaddingReceiverConsumeStatePrefix {
  315. // Done when back to the start state.
  316. break
  317. }
  318. // Else loop and read the next byte(s) for the next state.
  319. }
  320. return bytesRead, false, nil
  321. }