Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

client_side.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. package fake
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha256"
  6. "crypto/subtle"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "net"
  12. "slices"
  13. "time"
  14. "github.com/dolonet/mtg-multi/mtglib/internal/tls"
  15. )
  16. const (
  17. TypeHandshakeClient = 0x01
  18. RandomLen = 32
  19. // record_type(1) + version(2) + size(2) + handshake_type(1) + uint24_length(3) + client_version(2)
  20. RandomOffset = 1 + 2 + 2 + 1 + 3 + 2
  21. sniDNSNamesListType = 0
  22. // maxContinuationRecords limits the number of continuation TLS records
  23. // that reassembleTLSHandshake will read. This prevents resource exhaustion
  24. // from adversarial fragmentation.
  25. maxContinuationRecords = 10
  26. )
  27. var (
  28. emptyRandom = [RandomLen]byte{}
  29. extTypeSNI = [2]byte{}
  30. )
  31. type ClientHello struct {
  32. Random [RandomLen]byte
  33. SessionID []byte
  34. CipherSuite uint16
  35. }
  36. // ReadClientHelloResult contains the parsed ClientHello and the index of the
  37. // secret that matched the HMAC validation.
  38. type ReadClientHelloResult struct {
  39. Hello *ClientHello
  40. MatchedIndex int
  41. }
  42. func ReadClientHello(
  43. conn net.Conn,
  44. secret []byte,
  45. hostname string,
  46. tolerateTimeSkewness time.Duration,
  47. ) (*ClientHello, error) {
  48. result, err := ReadClientHelloMulti(conn, [][]byte{secret}, hostname, tolerateTimeSkewness)
  49. if err != nil {
  50. return nil, err
  51. }
  52. return result.Hello, nil
  53. }
  54. // ReadClientHelloMulti is like ReadClientHello but accepts multiple secrets.
  55. // It tries each secret until one validates the HMAC. On success it returns
  56. // the ClientHello and the index of the matched secret.
  57. func ReadClientHelloMulti(
  58. conn net.Conn,
  59. secrets [][]byte,
  60. hostname string,
  61. tolerateTimeSkewness time.Duration,
  62. ) (*ReadClientHelloResult, error) {
  63. if err := conn.SetReadDeadline(time.Now().Add(ClientHelloReadTimeout)); err != nil {
  64. return nil, fmt.Errorf("cannot set read deadline: %w", err)
  65. }
  66. defer conn.SetReadDeadline(resetDeadline) //nolint: errcheck
  67. reassembled, err := reassembleTLSHandshake(conn)
  68. if err != nil {
  69. return nil, fmt.Errorf("cannot reassemble TLS records: %w", err)
  70. }
  71. handshakeCopyBuf := &bytes.Buffer{}
  72. reader := io.TeeReader(reassembled, handshakeCopyBuf)
  73. // Skip the TLS record header (validated during reassembly).
  74. // The header still flows through TeeReader into handshakeCopyBuf for HMAC.
  75. if _, err = io.CopyN(io.Discard, reader, tls.SizeHeader); err != nil {
  76. return nil, fmt.Errorf("cannot skip tls header: %w", err)
  77. }
  78. reader, err = parseHandshakeHeader(reader)
  79. if err != nil {
  80. return nil, fmt.Errorf("cannot parse handshake header: %w", err)
  81. }
  82. hello, err := parseHandshake(reader)
  83. if err != nil {
  84. return nil, fmt.Errorf("cannot parse handshake: %w", err)
  85. }
  86. sniHostnames, err := parseSNI(reader)
  87. if err != nil {
  88. return nil, fmt.Errorf("cannot parse SNI: %w", err)
  89. }
  90. if !slices.Contains(sniHostnames, hostname) {
  91. return nil, fmt.Errorf("cannot find %s in %v", hostname, sniHostnames)
  92. }
  93. // Save the handshake bytes so we can reuse them for each secret attempt.
  94. handshakeBytes := handshakeCopyBuf.Bytes()
  95. for idx, secret := range secrets {
  96. digest := hmac.New(sha256.New, secret)
  97. // Write the handshake with client random all nullified.
  98. digest.Write(handshakeBytes[:RandomOffset])
  99. digest.Write(emptyRandom[:])
  100. digest.Write(handshakeBytes[RandomOffset+RandomLen:])
  101. computed := digest.Sum(nil)
  102. for i := range RandomLen {
  103. computed[i] ^= hello.Random[i]
  104. }
  105. if subtle.ConstantTimeCompare(emptyRandom[:RandomLen-4], computed[:RandomLen-4]) != 1 {
  106. continue
  107. }
  108. timestamp := int64(binary.LittleEndian.Uint32(computed[RandomLen-4:]))
  109. createdAt := time.Unix(timestamp, 0)
  110. if tdiff := time.Since(createdAt).Abs(); tdiff > tolerateTimeSkewness {
  111. continue
  112. }
  113. return &ReadClientHelloResult{
  114. Hello: hello,
  115. MatchedIndex: idx,
  116. }, nil
  117. }
  118. return nil, ErrBadDigest
  119. }
  120. // reassembleTLSHandshake reads one or more TLS records from conn,
  121. // validates the record type and version, and reassembles fragmented
  122. // handshake payloads into a single TLS record.
  123. //
  124. // Per RFC 5246 Section 6.2.1, handshake messages may be fragmented
  125. // across multiple TLS records. DPI bypass tools like ByeDPI use this
  126. // to evade censorship.
  127. //
  128. // The returned buffer contains the full TLS record (header + payload)
  129. // so that callers can include the header in HMAC computation.
  130. func reassembleTLSHandshake(conn io.Reader) (*bytes.Buffer, error) {
  131. header := [tls.SizeHeader]byte{}
  132. if _, err := io.ReadFull(conn, header[:]); err != nil {
  133. return nil, fmt.Errorf("cannot read record header: %w", err)
  134. }
  135. length := int64(binary.BigEndian.Uint16(header[3:]))
  136. payload := &bytes.Buffer{}
  137. if _, err := io.CopyN(payload, conn, length); err != nil {
  138. return nil, fmt.Errorf("cannot read record payload: %w", err)
  139. }
  140. if header[0] != tls.TypeHandshake {
  141. return nil, fmt.Errorf("unexpected record type %#x", header[0])
  142. }
  143. if header[1] != 3 || header[2] != 1 {
  144. return nil, fmt.Errorf("unexpected protocol version %#x %#x", header[1], header[2])
  145. }
  146. // Reassemble fragmented payload. continuationCount caps the total
  147. // number of continuation records across both phases below.
  148. continuationCount := 0
  149. // Phase 1: read continuation records until we have at least the
  150. // 4-byte handshake header (type + uint24 length) to determine the
  151. // expected total size.
  152. for ; payload.Len() < 4 && continuationCount < maxContinuationRecords; continuationCount++ {
  153. prevLen := payload.Len()
  154. if err := readContinuationRecord(conn, payload); err != nil {
  155. payload.Truncate(prevLen) // discard partial data on error
  156. if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
  157. break // no more records — let downstream parsing handle what we have
  158. }
  159. return nil, err
  160. }
  161. }
  162. // Phase 2: we know the expected handshake size — read remaining
  163. // continuation records until the payload is complete.
  164. if payload.Len() >= 4 {
  165. p := payload.Bytes()
  166. expectedTotal := 4 + (int(p[1])<<16 | int(p[2])<<8 | int(p[3]))
  167. if expectedTotal > 0xFFFF {
  168. return nil, fmt.Errorf("handshake message too large: %d bytes", expectedTotal)
  169. }
  170. for ; payload.Len() < expectedTotal && continuationCount < maxContinuationRecords; continuationCount++ {
  171. if err := readContinuationRecord(conn, payload); err != nil {
  172. return nil, err
  173. }
  174. }
  175. if payload.Len() < expectedTotal {
  176. return nil, fmt.Errorf("cannot reassemble handshake: too many continuation records")
  177. }
  178. payload.Truncate(expectedTotal)
  179. }
  180. if payload.Len() > 0xFFFF {
  181. return nil, fmt.Errorf("reassembled payload too large: %d bytes", payload.Len())
  182. }
  183. // Reconstruct a single TLS record with the reassembled payload.
  184. result := &bytes.Buffer{}
  185. result.Grow(tls.SizeHeader + payload.Len())
  186. result.Write(header[:3])
  187. binary.Write(result, binary.BigEndian, uint16(payload.Len())) //nolint:errcheck // bytes.Buffer.Write never fails
  188. result.Write(payload.Bytes())
  189. return result, nil
  190. }
  191. // readContinuationRecord reads the next TLS record header and appends its
  192. // full payload to dst. It returns an error if the record is not a handshake
  193. // record.
  194. func readContinuationRecord(conn io.Reader, dst *bytes.Buffer) error {
  195. nextHeader := [tls.SizeHeader]byte{}
  196. if _, err := io.ReadFull(conn, nextHeader[:]); err != nil {
  197. return fmt.Errorf("cannot read continuation record header: %w", err)
  198. }
  199. if nextHeader[0] != tls.TypeHandshake {
  200. return fmt.Errorf("unexpected continuation record type %#x", nextHeader[0])
  201. }
  202. if nextHeader[1] != 3 || nextHeader[2] != 1 {
  203. return fmt.Errorf("unexpected continuation record version %#x %#x", nextHeader[1], nextHeader[2])
  204. }
  205. nextLength := int64(binary.BigEndian.Uint16(nextHeader[3:]))
  206. if nextLength == 0 {
  207. return fmt.Errorf("zero-length continuation record")
  208. }
  209. if _, err := io.CopyN(dst, conn, nextLength); err != nil {
  210. return fmt.Errorf("cannot read continuation record payload: %w", err)
  211. }
  212. return nil
  213. }
  214. func parseHandshakeHeader(r io.Reader) (io.Reader, error) {
  215. // type(1) + size(3 / uint24)
  216. // 01 - handshake message type 0x01 (client hello)
  217. // 00 00 f4 - 0xF4 (244) bytes of client hello data follows
  218. header := [1 + 3]byte{}
  219. if _, err := io.ReadFull(r, header[:]); err != nil {
  220. return nil, fmt.Errorf("cannot read handshake header: %w", err)
  221. }
  222. if header[0] != TypeHandshakeClient {
  223. return nil, fmt.Errorf("incorrect handshake type: %#x", header[0])
  224. }
  225. // unfortunately there is not uint24 in golang, so we just reust header
  226. header[0] = 0
  227. length := int64(binary.BigEndian.Uint32(header[:]))
  228. buf := &bytes.Buffer{}
  229. _, err := io.CopyN(buf, r, length)
  230. return buf, err
  231. }
  232. func parseHandshake(r io.Reader) (*ClientHello, error) {
  233. // A protocol version of "3,3" (meaning TLS 1.2) is given.
  234. header := [2]byte{}
  235. if _, err := io.ReadFull(r, header[:]); err != nil {
  236. return nil, fmt.Errorf("cannot read client version: %w", err)
  237. }
  238. hello := &ClientHello{}
  239. if _, err := io.ReadFull(r, hello.Random[:]); err != nil {
  240. return nil, fmt.Errorf("cannot read client random: %w", err)
  241. }
  242. if _, err := io.ReadFull(r, header[:1]); err != nil {
  243. return nil, fmt.Errorf("cannot read session ID length: %w", err)
  244. }
  245. hello.SessionID = make([]byte, int(header[0]))
  246. if _, err := io.ReadFull(r, hello.SessionID); err != nil {
  247. return nil, fmt.Errorf("cannot read session id: %w", err)
  248. }
  249. if _, err := io.ReadFull(r, header[:]); err != nil {
  250. return nil, fmt.Errorf("cannot read cipher suite length: %w", err)
  251. }
  252. cipherSuiteLen := int64(binary.BigEndian.Uint16(header[:]))
  253. // we do not care about picking up any cipher. we pick the first one,
  254. // so it is always should be present.
  255. if _, err := io.ReadFull(r, header[:]); err != nil {
  256. return nil, fmt.Errorf("cannot read first cipher suite: %w", err)
  257. }
  258. hello.CipherSuite = binary.BigEndian.Uint16(header[:])
  259. if _, err := io.CopyN(io.Discard, r, cipherSuiteLen-2); err != nil {
  260. return nil, fmt.Errorf("cannot skip remaining cipher suites: %w", err)
  261. }
  262. if _, err := io.ReadFull(r, header[:1]); err != nil {
  263. return nil, fmt.Errorf("cannot read compression methods length: %w", err)
  264. }
  265. if _, err := io.CopyN(io.Discard, r, int64(header[0])); err != nil {
  266. return nil, fmt.Errorf("cannot skip compression methods: %w", err)
  267. }
  268. return hello, nil
  269. }
  270. func parseSNI(r io.Reader) ([]string, error) {
  271. header := [2]byte{}
  272. if _, err := io.ReadFull(r, header[:]); err != nil {
  273. return nil, fmt.Errorf("cannot read length of TLS extensions: %w", err)
  274. }
  275. extensionsLength := int64(binary.BigEndian.Uint16(header[:]))
  276. buf := &bytes.Buffer{}
  277. buf.Grow(int(extensionsLength))
  278. if _, err := io.CopyN(buf, r, extensionsLength); err != nil {
  279. return nil, fmt.Errorf("cannot read extensions: %w", err)
  280. }
  281. for buf.Len() > 0 {
  282. // 00 00 - assigned value for extension "server name"
  283. // 00 18 - 0x18 (24) bytes of "server name" extension data follows
  284. // 00 16 - 0x16 (22) bytes of first (and only) list entry follows
  285. // 00 - list entry is type 0x00 "DNS hostname"
  286. // 00 13 - 0x13 (19) bytes of hostname follows
  287. // 65 78 61 ... 6e 65 74 - "example.ulfheim.net"
  288. // 00 00 - assigned value for extension "server name"
  289. extTypeB := buf.Next(2)
  290. if len(extTypeB) != 2 {
  291. return nil, fmt.Errorf("cannot read extension type: %v", extTypeB)
  292. }
  293. // 00 18 - 0x18 (24) bytes of "server name" extension data follows
  294. lengthB := buf.Next(2)
  295. if len(lengthB) != 2 {
  296. return nil, fmt.Errorf("cannot read extension %v length: %v", extTypeB, lengthB)
  297. }
  298. length := int(binary.BigEndian.Uint16(lengthB))
  299. extDataB := buf.Next(length)
  300. if len(extDataB) != length {
  301. return nil, fmt.Errorf("cannot read extension %v data: len %d != %d", extTypeB, length, len(extDataB))
  302. }
  303. if !bytes.Equal(extTypeB, extTypeSNI[:]) {
  304. continue
  305. }
  306. buf.Reset()
  307. buf.Write(extDataB)
  308. // 00 16 - 0x16 (22) bytes of first (and only) list entry follows
  309. lengthB = buf.Next(2)
  310. if len(lengthB) != 2 {
  311. return nil, fmt.Errorf("cannot read the length of the SNI record: %v", lengthB)
  312. }
  313. length = int(binary.BigEndian.Uint16(lengthB))
  314. if length == 0 {
  315. return nil, nil
  316. }
  317. listType, err := buf.ReadByte()
  318. if err != nil {
  319. return nil, fmt.Errorf("cannot read SNI list type: %w", err)
  320. }
  321. // 00 - list entry is type 0x00 "DNS hostname"
  322. if listType != sniDNSNamesListType {
  323. return nil, fmt.Errorf("incorrect SNI list type %#x", listType)
  324. }
  325. names := []string{}
  326. for buf.Len() > 0 {
  327. // 00 13 - 0x13 (19) bytes of hostname follows
  328. lengthB = buf.Next(2)
  329. if len(lengthB) != 2 {
  330. return nil, fmt.Errorf("incorrect length of the hostname: %v", lengthB)
  331. }
  332. length = int(binary.BigEndian.Uint16(lengthB))
  333. name := buf.Next(length)
  334. if len(name) != length {
  335. return nil, fmt.Errorf("incorrect length of SNI hostname: len %d != %d", length, len(name))
  336. }
  337. names = append(names, string(name))
  338. }
  339. return names, nil
  340. }
  341. return nil, nil
  342. }