move gotd fork into repo. (#111)

- update to latest telegram layer
- remove some references to fields in tg.Entities that don't exist in
the schema
- originally added here:
https://github.com/beeper/td/commit/820929062a2ba0104397bc01235ab58a9cff780e
  - referenced here
-
https://github.com/mautrix/telegramgo/commit/124f0967ed195b5a380c9bd02e170ada9710dde3
-
https://github.com/mautrix/telegramgo/commit/4205047aab2e0639217148b5d125bfaab668bd8e
This commit is contained in:
Adam Van Ymeren
2025-06-27 20:03:37 -07:00
committed by GitHub
parent 0952df0244
commit 7a04f298d2
19264 changed files with 1539697 additions and 84 deletions
+2
View File
@@ -0,0 +1,2 @@
// Package obfuscated2 contains obfuscated2 implementation.
package obfuscated2
+75
View File
@@ -0,0 +1,75 @@
package obfuscated2
import (
"crypto/cipher"
"encoding/binary"
"io"
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
)
type keys struct {
header []byte
encrypt cipher.Stream
decrypt cipher.Stream
}
func (k *keys) createStreams(init, secret []byte) error {
// preallocate 256 bit key + 16 byte secret
const keyLength = 32 + 16
encryptKey := append(make([]byte, 0, keyLength), init[8:40]...)
encryptIV := append(make([]byte, 0, 16), init[40:56]...)
initRev := getDecryptInit(init)
decryptKey := append(make([]byte, 0, keyLength), initRev[:32]...)
decryptIV := append(make([]byte, 0, 16), initRev[32:48]...)
if len(secret) > 0 {
if len(secret) < 16 {
return errors.Errorf("invalid secret size %d", len(secret))
}
secret = secret[0:16]
encryptKey = crypto.SHA256(encryptKey, secret)
decryptKey = crypto.SHA256(decryptKey, secret)
}
var err error
k.encrypt, err = createCTR(encryptKey, encryptIV)
if err != nil {
return err
}
k.decrypt, err = createCTR(decryptKey, decryptIV)
if err != nil {
return err
}
return nil
}
func generateKeys(randSource io.Reader, protocol [4]byte, secret []byte, dc int) (keys, error) {
init, err := generateInit(randSource)
if err != nil {
return keys{}, err
}
var k keys
if err := k.createStreams(init[:], secret); err != nil {
return keys{}, err
}
copy(init[56:60], protocol[:])
binary.LittleEndian.PutUint16(init[60:62], uint16(dc))
var encryptedInit [64]byte
k.encrypt.XORKeyStream(encryptedInit[:], init[:])
k.header = make([]byte, 64)
copy(k.header, init[0:56])
copy(k.header[56:], encryptedInit[56:56+8])
return k, nil
}
+63
View File
@@ -0,0 +1,63 @@
package obfuscated2
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"io"
)
func createCTR(key, iv []byte) (cipher.Stream, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(block, iv), nil
}
func getDecryptInit(init []byte) (initRev [48]byte) {
copy(initRev[:], init[8:56])
// https://github.com/golang/go/wiki/SliceTricks#reversing
for left, right := 0, len(initRev)-1; left < right; left, right = left+1, right-1 {
initRev[left], initRev[right] = initRev[right], initRev[left]
}
return
}
// function from https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation
func generateInit(randSource io.Reader) (init [64]byte, err error) {
// init := (56 random bytes) + protocol + dc + (2 random bytes)
for {
_, err = io.ReadFull(randSource, init[:])
if err != nil {
return [64]byte{}, err
}
// Filter some start sequences
// See https://github.com/DrKLO/Telegram/blob/master/TMessagesProj/jni/tgnet/Connection.cpp#L531.
// See https://github.com/tdlib/td/blob/master/td/mtproto/TcpTransport.cpp#L157-L158.
if init[0] == 0xef { // Abridged header
continue
}
firstInt := binary.LittleEndian.Uint32(init[0:4])
if firstInt == 0x44414548 || // HEAD
firstInt == 0x54534f50 || // POST
firstInt == 0x20544547 || // GET
firstInt == 0x4954504f || // OPTI
firstInt == 0x02010316 || // ????
firstInt == 0xdddddddd || // PaddedIntermediate header
firstInt == 0xeeeeeeee /* Intermediate header */ {
continue
}
if secondInt := binary.LittleEndian.Uint32(init[4:8]); secondInt == 0 {
continue
}
break
}
return init, nil
}
@@ -0,0 +1,59 @@
package obfuscated2
import (
"io"
"github.com/go-faster/errors"
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy"
)
// Obfuscated2 implements obfuscated2 obfuscation protocol.
type Obfuscated2 struct {
rand io.Reader
conn io.ReadWriter
keys
}
// NewObfuscated2 creates new Obfuscated2.
func NewObfuscated2(r io.Reader, conn io.ReadWriter) *Obfuscated2 {
return &Obfuscated2{
rand: r,
conn: conn,
}
}
// Handshake sends obfuscated2 header.
func (o *Obfuscated2) Handshake(protocol [4]byte, dc int, s mtproxy.Secret) error {
k, err := generateKeys(o.rand, protocol, s.Secret, dc)
if err != nil {
return errors.Wrap(err, "generate keys")
}
o.keys = k
if _, err := o.conn.Write(o.header); err != nil {
return errors.Wrap(err, "write obfuscated header")
}
return nil
}
// Write implements io.Writer.
func (o *Obfuscated2) Write(b []byte) (n int, err error) {
cpyB := append([]byte(nil), b...)
o.encrypt.XORKeyStream(cpyB, b)
return o.conn.Write(cpyB)
}
// Read implements io.Reader.
func (o *Obfuscated2) Read(b []byte) (int, error) {
n, err := o.conn.Read(b)
if err != nil {
return n, err
}
if n > 0 {
o.decrypt.XORKeyStream(b, b)
}
return n, err
}
@@ -0,0 +1,85 @@
package obfuscated2
import (
"bytes"
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
type OneChar struct {
char byte
}
func (c OneChar) Read(p []byte) (n int, err error) {
for i := range p {
p[i] = c.char
}
return len(p), nil
}
func Test_generateKeys(t *testing.T) {
a := require.New(t)
secret, err := hex.DecodeString(strings.Repeat("a", 32))
a.NoError(err)
k, err := generateKeys(OneChar{char: 'a'}, [4]byte{0xdd, 0xdd, 0xdd, 0xdd}, secret, 2)
a.NoError(err)
var expectedHeader = []byte{
97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 97, 97, 171, 65, 98, 66, 79, 102, 253, 220,
}
a.Equal(expectedHeader, k.header)
}
func TestEncrypt(t *testing.T) {
a := require.New(t)
secret, err := hex.DecodeString("8a96ef6e42a18c21837580cd1c91c5a8")
a.NoError(err)
rand := []byte{
245, 118, 143, 80, 183, 49, 38, 10, 70, 190, 16, 39, 194, 238, 170,
57, 53, 6, 36, 240, 182, 218, 89, 235, 165, 108, 129, 254, 69, 16,
194, 224, 182, 29, 61, 211, 35, 238, 2, 56, 134, 51, 227, 131, 122,
12, 28, 36, 250, 111, 41, 204, 215, 36, 190, 111, 65, 111, 247, 176,
38, 246, 204, 230,
}
k, err := generateKeys(bytes.NewReader(rand), [4]byte{0xdd, 0xdd, 0xdd, 0xdd}, secret, 2)
a.NoError(err)
var expectedHeader = []byte{
245, 118, 143, 80, 183, 49, 38, 10, 70, 190, 16, 39, 194, 238, 170, 57, 53, 6, 36, 240,
182, 218, 89, 235, 165, 108, 129, 254, 69, 16, 194, 224, 182, 29, 61, 211, 35, 238,
2, 56, 134, 51, 227, 131, 122, 12, 28, 36, 250, 111, 41, 204, 215, 36, 190, 111,
190, 162, 221, 225, 109, 197, 157, 210,
}
a.Equal(expectedHeader, k.header)
var encrypted [4]byte
payload := []byte{'a', 'b', 'c', 'd'}
k.encrypt.XORKeyStream(encrypted[:], payload)
a.Equal([]byte{202, 122, 130, 38}, encrypted[:])
k.decrypt.XORKeyStream(encrypted[:], payload)
a.Equal([]byte{143, 113, 25, 130}, encrypted[:])
}
func Test_getDecryptInit(t *testing.T) {
a := require.New(t)
var input [64]byte
for i := range input {
input[i] = byte(i)
}
r := getDecryptInit(input[:])
expected := [48]byte{
55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32,
31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8,
}
a.Equal(expected, r)
}
+42
View File
@@ -0,0 +1,42 @@
package obfuscated2
import (
"encoding/binary"
"io"
)
// Metadata represents metadata received from header.
type Metadata struct {
Protocol [4]byte
DC uint16
}
// Accept creates new io.ReadWriter for server-side deobfuscation.
func Accept(conn io.ReadWriter, secret []byte) (io.ReadWriter, Metadata, error) {
var (
buf = make([]byte, 64)
meta Metadata
)
if _, err := io.ReadFull(conn, buf); err != nil {
return nil, meta, err
}
var k keys
if err := k.createStreams(buf, secret); err != nil {
return nil, meta, err
}
// Swap to match client's streams.
k.encrypt, k.decrypt = k.decrypt, k.encrypt
var decrypted [64]byte
k.decrypt.XORKeyStream(decrypted[:], buf)
copy(meta.Protocol[:], decrypted[56:60])
meta.DC = binary.LittleEndian.Uint16(decrypted[60:62])
return &Obfuscated2{
rand: nil, // Used only in Handshake.
conn: conn,
keys: k,
}, meta, nil
}