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:
@@ -0,0 +1,2 @@
|
||||
// Package obfuscated2 contains obfuscated2 implementation.
|
||||
package obfuscated2
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user