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 mtproxy contains MTProxy transport implementations.
|
||||
package mtproxy
|
||||
@@ -0,0 +1,124 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/bin"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/clock"
|
||||
)
|
||||
|
||||
const clientHelloLength = 517
|
||||
|
||||
func createClientHello(b *bin.Buffer, sessionID [32]byte, domain string, key [32]byte) (randomOffset int) {
|
||||
S := func(s string) {
|
||||
b.Buf = append(b.Buf, s...)
|
||||
}
|
||||
Z := func(n int) {
|
||||
randomOffset = len(b.Buf)
|
||||
b.Expand(n)
|
||||
}
|
||||
G := func(_ int) {
|
||||
b.Expand(0)
|
||||
}
|
||||
R := func() {
|
||||
b.Buf = append(b.Buf, sessionID[:]...)
|
||||
}
|
||||
D := func() {
|
||||
b.Buf = append(b.Buf, domain...)
|
||||
}
|
||||
K := func() {
|
||||
b.Buf = append(b.Buf, key[:]...)
|
||||
}
|
||||
var stack []int
|
||||
Open := func() {
|
||||
stack = append(stack, b.Len())
|
||||
b.Expand(2)
|
||||
}
|
||||
Close := func() {
|
||||
lastIdx := len(stack) - 1
|
||||
s := stack[lastIdx]
|
||||
stack = stack[:lastIdx]
|
||||
|
||||
length := b.Len() - (s + 2)
|
||||
binary.BigEndian.PutUint16(b.Buf[s:], uint16(length))
|
||||
}
|
||||
|
||||
S("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03")
|
||||
Z(32)
|
||||
S("\x20")
|
||||
R()
|
||||
S("\x00\x20")
|
||||
G(0)
|
||||
S("\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9" +
|
||||
"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00" +
|
||||
"\x01\x93")
|
||||
G(2)
|
||||
S("\x00\x00\x00\x00")
|
||||
Open()
|
||||
Open()
|
||||
S("\x00")
|
||||
Open()
|
||||
D()
|
||||
Close()
|
||||
Close()
|
||||
Close()
|
||||
S("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08")
|
||||
G(4)
|
||||
S("\x00\x1d\x00\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00" +
|
||||
"\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31" +
|
||||
"\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x12\x00" +
|
||||
"\x10\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06" +
|
||||
"\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29")
|
||||
G(4)
|
||||
S("\x00\x01\x00\x00\x1d\x00\x20")
|
||||
K()
|
||||
S("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a")
|
||||
G(6)
|
||||
S("\x03\x04\x03\x03\x03\x02\x03\x01\x00\x1b\x00\x03\x02\x00\x02")
|
||||
G(3)
|
||||
S("\x00\x01\x00\x00\x15")
|
||||
|
||||
if pad := clientHelloLength - b.Len(); pad > 0 {
|
||||
b.Expand(pad)
|
||||
}
|
||||
return randomOffset
|
||||
}
|
||||
|
||||
// writeClientHello writes faketls ClientHello.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc5246#section-7.4.1.1.
|
||||
func writeClientHello(
|
||||
w io.Writer,
|
||||
now clock.Clock,
|
||||
sessionID [32]byte,
|
||||
domain string,
|
||||
secret []byte,
|
||||
) (r [32]byte, err error) {
|
||||
b := &bin.Buffer{
|
||||
Buf: make([]byte, 0, 576),
|
||||
}
|
||||
randomOffset := createClientHello(b, sessionID, domain, [32]byte{})
|
||||
|
||||
// https://github.com/tdlib/td/blob/27d3fdd09d90f6b77ecbcce50b1e86dc4b3dd366/td/mtproto/TlsInit.cpp#L380-L384
|
||||
mac := hmac.New(sha256.New, secret)
|
||||
if _, err := mac.Write(b.Buf); err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "hmac write")
|
||||
}
|
||||
|
||||
s := mac.Sum(nil)
|
||||
copy(b.Buf[randomOffset:randomOffset+32], s)
|
||||
// Overwrite last 4 bytes using final := original ^ timestamp.
|
||||
old := binary.LittleEndian.Uint32(b.Buf[randomOffset+28 : randomOffset+32])
|
||||
old ^= uint32(now.Now().Unix())
|
||||
binary.LittleEndian.PutUint32(b.Buf[randomOffset+28:randomOffset+32], old)
|
||||
|
||||
// Copy ClientRandom for later use.
|
||||
copy(r[:], b.Buf[randomOffset:randomOffset+32])
|
||||
_, err = w.Write(b.Buf)
|
||||
return r, err
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package faketls contains faketls implementation.
|
||||
package faketls
|
||||
@@ -0,0 +1,112 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/clock"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy"
|
||||
)
|
||||
|
||||
// FakeTLS implements FakeTLS obfuscation protocol.
|
||||
type FakeTLS struct {
|
||||
rand io.Reader
|
||||
clock clock.Clock
|
||||
conn io.ReadWriter
|
||||
|
||||
version [2]byte
|
||||
firstPacket bool
|
||||
|
||||
readBuf bytes.Buffer
|
||||
readBufMux sync.Mutex
|
||||
}
|
||||
|
||||
// NewFakeTLS creates new FakeTLS.
|
||||
func NewFakeTLS(r io.Reader, conn io.ReadWriter) *FakeTLS {
|
||||
return &FakeTLS{
|
||||
rand: r,
|
||||
clock: clock.System,
|
||||
conn: conn,
|
||||
version: Version12Bytes,
|
||||
readBuf: bytes.Buffer{},
|
||||
}
|
||||
}
|
||||
|
||||
// Handshake performs FakeTLS handshake.
|
||||
func (o *FakeTLS) Handshake(protocol [4]byte, dc int, s mtproxy.Secret) error {
|
||||
o.readBufMux.Lock()
|
||||
o.readBuf.Reset()
|
||||
o.readBufMux.Unlock()
|
||||
|
||||
var sessionID [32]byte
|
||||
if _, err := o.rand.Read(sessionID[:]); err != nil {
|
||||
return errors.Wrap(err, "generate sessionID")
|
||||
}
|
||||
|
||||
clientDigest, err := writeClientHello(o.conn, o.clock, sessionID, s.CloakHost, s.Secret)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send ClientHello")
|
||||
}
|
||||
|
||||
if err := readServerHello(o.conn, clientDigest, s.Secret); err != nil {
|
||||
return errors.Wrap(err, "receive ServerHello")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (o *FakeTLS) Write(b []byte) (n int, err error) {
|
||||
// Some proxies require sending first packet with RecordTypeChangeCipherSpec.
|
||||
//
|
||||
// See https://github.com/tdlib/td/blob/master/td/mtproto/TcpTransport.cpp#L266.
|
||||
if !o.firstPacket {
|
||||
_, err = writeRecord(o.conn, record{
|
||||
Type: RecordTypeChangeCipherSpec,
|
||||
Version: o.version,
|
||||
Data: []byte("\x01"),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "write first TLS packet")
|
||||
}
|
||||
o.firstPacket = true
|
||||
}
|
||||
n, err = writeRecord(o.conn, record{
|
||||
Type: RecordTypeApplication,
|
||||
Version: o.version,
|
||||
Data: b,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "write TLS record")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (o *FakeTLS) Read(b []byte) (n int, err error) {
|
||||
o.readBufMux.Lock()
|
||||
defer o.readBufMux.Unlock()
|
||||
|
||||
if o.readBuf.Len() > 0 {
|
||||
return o.readBuf.Read(b)
|
||||
}
|
||||
|
||||
rec, err := readRecord(o.conn)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "read TLS record")
|
||||
}
|
||||
|
||||
switch rec.Type {
|
||||
case RecordTypeChangeCipherSpec:
|
||||
case RecordTypeApplication:
|
||||
case RecordTypeHandshake:
|
||||
return 0, errors.New("unexpected record type handshake")
|
||||
default:
|
||||
return 0, errors.Errorf("unsupported record type %v", rec.Type)
|
||||
}
|
||||
o.readBuf.Write(rec.Data)
|
||||
|
||||
return o.readBuf.Read(b)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gotd/neo"
|
||||
)
|
||||
|
||||
func TestTLS(t *testing.T) {
|
||||
a := require.New(t)
|
||||
secret := [32]byte{}
|
||||
sessionID := [32]byte{}
|
||||
c := neo.NewTime(time.Date(2010, 10, 10, 1, 1, 1, 0, time.UTC))
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
_, err := writeClientHello(b, c, sessionID, "google.com", secret[:])
|
||||
a.NoError(err)
|
||||
|
||||
testVector := []byte{
|
||||
0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0xf9, 0x75, 0x5f, 0xdd, 0xb9,
|
||||
0xe3, 0x46, 0x57, 0x5a, 0x26, 0x71, 0xfa, 0x29, 0x7f, 0xab, 0xf0, 0xa1, 0xf3, 0x69, 0x4f, 0x72,
|
||||
0xe0, 0xc3, 0x8f, 0x62, 0x77, 0x5c, 0x8f, 0x5a, 0xf8, 0xa2, 0xa9, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13, 0x01,
|
||||
0x13, 0x02, 0x13, 0x03, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0, 0x30, 0xcc, 0xa9, 0xcc, 0xa8,
|
||||
0xc0, 0x13, 0xc0, 0x14, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0x01, 0x00, 0x01, 0x93,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0d, 0x00, 0x00, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a,
|
||||
0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00,
|
||||
0x00, 0x23, 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74,
|
||||
0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x05,
|
||||
0x01, 0x08, 0x06, 0x06, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x00,
|
||||
0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b,
|
||||
0x0a, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x02,
|
||||
0x00, 0x01, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
a.Equal(testVector, b.Bytes())
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
)
|
||||
|
||||
const maxTLSRecordDataLength = 16384 + 24
|
||||
|
||||
type record struct {
|
||||
Type RecordType
|
||||
Version [2]byte
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func readRecord(r io.Reader) (record, error) {
|
||||
rec := record{}
|
||||
|
||||
buf := make([]byte, 5)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
|
||||
rec.Type = RecordType(buf[0])
|
||||
versionRaw := buf[1:3]
|
||||
switch {
|
||||
case bytes.Equal(versionRaw, Version13Bytes[:]):
|
||||
rec.Version = Version13Bytes
|
||||
case bytes.Equal(versionRaw, Version12Bytes[:]):
|
||||
rec.Version = Version12Bytes
|
||||
case bytes.Equal(versionRaw, Version11Bytes[:]):
|
||||
rec.Version = Version11Bytes
|
||||
case bytes.Equal(versionRaw, Version10Bytes[:]):
|
||||
rec.Version = Version10Bytes
|
||||
default:
|
||||
return record{}, errors.Errorf("unknown protocol version %v", versionRaw)
|
||||
}
|
||||
|
||||
length := binary.BigEndian.Uint16(buf[3:])
|
||||
if length > maxTLSRecordDataLength {
|
||||
return record{}, errors.New("record length is too big")
|
||||
}
|
||||
|
||||
rec.Data = make([]byte, length)
|
||||
if _, err := io.ReadFull(r, rec.Data); err != nil {
|
||||
return record{}, err
|
||||
}
|
||||
|
||||
return rec, nil
|
||||
}
|
||||
|
||||
func writeRecord(w io.Writer, r record) (int, error) {
|
||||
buf := [...]byte{
|
||||
byte(r.Type),
|
||||
r.Version[0], r.Version[1],
|
||||
}
|
||||
|
||||
if _, err := w.Write(buf[:]); err != nil {
|
||||
return 0, errors.Wrap(err, "type and version")
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:2], uint16(len(r.Data)))
|
||||
if _, err := w.Write(buf[:2]); err != nil {
|
||||
return 0, errors.Wrap(err, "data length")
|
||||
}
|
||||
|
||||
n, err := w.Write(r.Data)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "data")
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
a := require.New(t)
|
||||
r := record{
|
||||
Type: RecordTypeApplication,
|
||||
Version: Version10Bytes,
|
||||
Data: []byte(`abcd`),
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
_, err := writeRecord(buf, r)
|
||||
a.NoError(err)
|
||||
r2, err := readRecord(buf)
|
||||
a.NoError(err)
|
||||
a.Equal(r, r2)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
)
|
||||
|
||||
// readServerHello reads faketls ServerHello.
|
||||
func readServerHello(r io.Reader, clientRandom [32]byte, secret []byte) error {
|
||||
packetBuf := bytes.NewBuffer(nil)
|
||||
r = io.TeeReader(r, packetBuf)
|
||||
|
||||
handshake, err := readRecord(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "handshake record")
|
||||
}
|
||||
if handshake.Type != RecordTypeHandshake {
|
||||
return errors.Wrap(err, "unexpected record type")
|
||||
}
|
||||
|
||||
changeCipher, err := readRecord(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "change cipher record")
|
||||
}
|
||||
if changeCipher.Type != RecordTypeChangeCipherSpec {
|
||||
return errors.Wrap(err, "unexpected record type")
|
||||
}
|
||||
|
||||
cert, err := readRecord(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cert record")
|
||||
}
|
||||
if cert.Type != RecordTypeApplication {
|
||||
return errors.Wrap(err, "unexpected record type")
|
||||
}
|
||||
|
||||
// `$record_header = type 1 byte + version 2 bytes + payload_length 2 bytes = 5 bytes`
|
||||
// `$server_hello_header = type 1 bytes + version 2 bytes + length 3 bytes = 6 bytes`
|
||||
// `$offset = $record_header + $server_hello_header = 11 bytes`
|
||||
const serverRandomOffset = 11
|
||||
packet := packetBuf.Bytes()
|
||||
// Copy original digest.
|
||||
var originalDigest [32]byte
|
||||
copy(originalDigest[:], packet[serverRandomOffset:serverRandomOffset+32])
|
||||
// Fill original digest by zeros.
|
||||
var zeros [32]byte
|
||||
copy(packet[serverRandomOffset:serverRandomOffset+32], zeros[:])
|
||||
|
||||
mac := hmac.New(sha256.New, secret)
|
||||
if _, err := mac.Write(clientRandom[:]); err != nil {
|
||||
return errors.Wrap(err, "hmac write")
|
||||
}
|
||||
if _, err := mac.Write(packet); err != nil {
|
||||
return errors.Wrap(err, "hmac write")
|
||||
}
|
||||
if !bytes.Equal(mac.Sum(nil), originalDigest[:]) {
|
||||
return errors.New("hmac digest mismatch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_readServerHello(t *testing.T) {
|
||||
p := filepath.Join("testdata", "server_hello")
|
||||
entries, err := os.ReadDir(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
clientRandom := map[string][32]uint8{
|
||||
"alexbers.hex": {
|
||||
0xa1, 0x32, 0xe3, 0x91, 0x60, 0x83, 0xb3, 0x14, 0xc1, 0xb9, 0x74, 0xd0, 0x57, 0x85, 0xe8, 0xee,
|
||||
0x70, 0x45, 0x6e, 0x5f, 0x86, 0x6d, 0x96, 0x57, 0xd5, 0x0a, 0x5c, 0x08, 0xea, 0x38, 0x31, 0x8e,
|
||||
},
|
||||
"mtgv2.hex": {
|
||||
0xca, 0x36, 0xbb, 0xa8, 0x33, 0x80, 0x9a, 0x33, 0xaa, 0x62, 0x7e, 0xbb, 0x5a, 0x32, 0xa1, 0x01,
|
||||
0x02, 0xd1, 0xa6, 0x1e, 0x1e, 0x6c, 0x58, 0xa4, 0x61, 0xd9, 0x34, 0x57, 0x4d, 0x2e, 0x2e, 0xa3,
|
||||
},
|
||||
}
|
||||
secret := []uint8{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
t.Error("Unexpected directory in testdata")
|
||||
continue
|
||||
}
|
||||
fileName := entry.Name()
|
||||
|
||||
t.Run(fileName, func(t *testing.T) {
|
||||
a := require.New(t)
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(p, fileName))
|
||||
a.NoError(err)
|
||||
|
||||
decode, err := hex.DecodeString(strings.TrimSpace(string(data)))
|
||||
a.NoError(err)
|
||||
|
||||
r := bytes.NewReader(decode)
|
||||
a.NoError(readServerHello(r, clientRandom[entry.Name()], secret))
|
||||
|
||||
a.Zero(r.Len(), "should read all data")
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
160303007a02000076030304794e0ec450abbe0de461b3088d0c487114bdf377733be767eb99cf92ad7eac200de22030bd1329ebfffc70476cf3075efbb95aea776bfa96ab6788c4bff59b2f130100002e002b0002030400330024001d0020df3a68806eecc284a06d8cd934d2a6f1486ab47759457cd0a7a75883fd7e725614030300010117030305c096a8cd3e601b32e3703565d402547894916120178d2330163d3b13e0d7bca02b49fadd55111c2a69fcc75b8e56bdf567eaab01337b19714345e4222c06067cec94dfcdfefc82d2294893ed16d5953fbfc3f7fece2e690dedf46d41b698438237bdee2cb87af0b082e7036d8d42cf9f994407af21b888583f70f5b6a6182881bce5e802b2afd6f5aa45ba043388457010fd649e2c4b98efe11eae85743cbc37b256b06d08b36110ff923febac033487ff8a7fc422b6cd95c2056fa6b15585b34dc21cf588bc2ce0b84d230ff4b97c5e15ffe8a0cc0cc29768d2a17d590e7d5cc082afac040b4e6159a67405d6164f8870704cff8d492afb79b7d93953271f7e7fbd89c8489c99d92e4f3747213deeb5db0feabad6fc1217308bb68c84282ac2cc3d2e1b7e76f86b35d06137fc90fc228995a25f87dfa369dc97b027a76d2ad14f7f1cd60d2ee634c4f3fec49ea5d1d9582b124a4dd7ad082fd2dae39cb59612d67c9eb320bf85f1d8caa72910cda30f57ca888cba453ee3604dcad3ba0768429b4c51855499aba65070c3e2fcbe020c60376e9823c43da8dc098d22eec9c05514d8dc94bc5b8e34812356d44be5d02755643a297e84d126d0cbb628da90cf88d3a0e2c2ad03d34ca3c4d0d1cd35d7f5db041ea2e1a2cb27413c6d8dbbbe3914bfd651ebcc2cadc66083143147f5b8b2534b2d87dc708a9e1fc13ae1fe7055ad3e8b267a63088b2c5e119e2a2540924e7c76e4b0740bf99c0a95be7cc110b6c2432afa1de48b33ddd8a6429dfcfa87c140c435f3c7277cac129697ac8667a952f97688cfe570af4a9ee8dee285c94b459f7836a57c58d02c16e78a900de75728e30b22d37845db718e1fcd03bf85839bbfe16bfd3c8c10e301f247e177a00eb07e439ac5be339e63f057a58ea1ad54ba311866fb977788da894c4c097f78727c72531895cdc98d1976883238c8af3f1f753f1eda49396c36093b2170fc7b577bf82b282be6843b03d74c9a6b82d9f3c6791e42e8e90612ed926b7f26d89d09c62461f9faa4c647a0218f62aa272882777624da48f66daa9d998cf4ccf80b7248f0adbfd329ffa047b514ce8d9c64a55f303438f385726946be120ad2c52c3a3d0f26f4c196994fcef3da70703e06280283a864d612c40054186b767e7fc7df68d272ff7f2f80d4cdb1ae53df04c90f944e3b00316007d69e67531e45b6a3f99cdc60a6f43c77552a1d41f341448148f30063457060a824508704ec807bb41d6706f975ba7a5d56b350e06b01600eb3c627d512a437b2033a82f92bba0fbb07ae01ca2b3a6b2a72f3a935f52feea83751fbaddf81c8bcfe38c810ca376bb1c731e57d66e3e023f975421abc5dfcf8e176bc1ae5ac8849cffe3754ea2950afc4db6baa9fbd2c7d14bbc8c1042d017d2ae766d40729f08c2ff5e32afe96325bae777f3f5e519278fb3c111b2fefc1082309d89aea356542c932c19664f4f02943f83b7e1f0db48a2e4e3ec3d7eea5718e5a9d7bf9294ca7bbf8baa8857dc6a6b04a46fb8654091a7e599f5aa5fd2084091b3024b101be86baed574a587b5b945b16af190d6e759353be612b280cac604960b690a7c831353a767cb20551b397e7ae6a3293b7db12ca996fb823716e004b3399a2b7f37520844f7fe377a345dc2a0c80b64bffdef417faa610ac0ada3bd6fbff61f8e08fbdf66a48e3434c9f46c9dcb73c48526a2caff41029406950c768dc6a215d520f1901b12024315ed5fd8de0700b55b07e545f5ffd6037e2b23433e186951320104ab534d2fcfd9d37b93172933a9f7ecc3f007b01ffab8c902287b1d70c214e4c5b54f3d3e91e740e14cac69525504fdc35ed0322561e83d5d7fa90859140342429761cc18e1ff1f133e3535368235bb1a77a6373008cbd45d4495ae858d601c31532b8f9e8e5ebbebaf73fd9d0cbddaa866b8017c415fbc37df38c34f6b2bc99354b4d52066f8a89beafe8da91275abee0531ea6a28c50a43117fa774855e9106d7177a5408f64aa2e7a58bdc89873d506ff2bf26e21a232362d76c15686dd8b19c13fdafaae357c5229e1f4
|
||||
@@ -0,0 +1,35 @@
|
||||
package faketls
|
||||
|
||||
// RecordType represents TLS record type byte.
|
||||
type RecordType uint8
|
||||
|
||||
const (
|
||||
// RecordTypeChangeCipherSpec is ChangeCipherSpec record type byte.
|
||||
RecordTypeChangeCipherSpec RecordType = 0x14
|
||||
// RecordTypeAlert is Alert record type byte.
|
||||
RecordTypeAlert RecordType = 0x15
|
||||
// RecordTypeHandshake is Handshake record type byte.
|
||||
RecordTypeHandshake RecordType = 0x16
|
||||
// RecordTypeApplication is Application record type byte.
|
||||
RecordTypeApplication RecordType = 0x17
|
||||
// RecordTypeHeartbeat is Heartbeat record type byte.
|
||||
RecordTypeHeartbeat RecordType = 0x18
|
||||
)
|
||||
|
||||
// HandshakeType represents TLS handshake record type byte.
|
||||
type HandshakeType uint8
|
||||
|
||||
const (
|
||||
// HandshakeTypeClient is client handshake message type.
|
||||
HandshakeTypeClient HandshakeType = 0x01
|
||||
// HandshakeTypeServer is server handshake message type.
|
||||
HandshakeTypeServer HandshakeType = 0x02
|
||||
)
|
||||
|
||||
// Possible versions.
|
||||
var (
|
||||
Version10Bytes = [2]byte{0x03, 0x01}
|
||||
Version11Bytes = [2]byte{0x03, 0x02}
|
||||
Version12Bytes = [2]byte{0x03, 0x03}
|
||||
Version13Bytes = [2]byte{0x03, 0x04}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package obfuscator
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy/obfuscated2"
|
||||
)
|
||||
|
||||
// Conn is net.Conn wrapper to use Obfuscator.
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
Obfuscator
|
||||
}
|
||||
|
||||
// Obfuscated2 creates new obfuscated2 connection.
|
||||
func Obfuscated2(rand io.Reader, conn net.Conn) *Conn {
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
Obfuscator: obfuscated2.NewObfuscated2(rand, conn),
|
||||
}
|
||||
}
|
||||
|
||||
// FakeTLS creates new FakeTLS connection.
|
||||
func FakeTLS(rand io.Reader, conn net.Conn) *Conn {
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
Obfuscator: newTLS(rand, conn),
|
||||
}
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (o *Conn) Write(b []byte) (n int, err error) {
|
||||
return o.Obfuscator.Write(b)
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (o *Conn) Read(b []byte) (n int, err error) {
|
||||
return o.Obfuscator.Read(b)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Package obfuscator contains some MTProxy obfuscation utilities.
|
||||
package obfuscator
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy/faketls"
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/mtproxy/obfuscated2"
|
||||
)
|
||||
|
||||
// Obfuscator represents MTProxy obfuscator.
|
||||
type Obfuscator interface {
|
||||
io.ReadWriter
|
||||
Handshake(protocol [4]byte, dc int, s mtproxy.Secret) error
|
||||
}
|
||||
|
||||
type tls struct {
|
||||
ftls *faketls.FakeTLS
|
||||
obfs2 *obfuscated2.Obfuscated2
|
||||
}
|
||||
|
||||
func newTLS(rand io.Reader, conn io.ReadWriter) tls {
|
||||
ftls := faketls.NewFakeTLS(rand, conn)
|
||||
obfs2 := obfuscated2.NewObfuscated2(rand, ftls)
|
||||
return tls{
|
||||
ftls: ftls,
|
||||
obfs2: obfs2,
|
||||
}
|
||||
}
|
||||
|
||||
func (t tls) Write(p []byte) (int, error) {
|
||||
return t.obfs2.Write(p)
|
||||
}
|
||||
|
||||
func (t tls) Read(p []byte) (int, error) {
|
||||
return t.obfs2.Read(p)
|
||||
}
|
||||
|
||||
func (t tls) Handshake(protocol [4]byte, dc int, s mtproxy.Secret) error {
|
||||
if err := t.ftls.Handshake(protocol, dc, s); err != nil {
|
||||
return errors.Wrap(err, "faketls handshake")
|
||||
}
|
||||
|
||||
if err := t.obfs2.Handshake(protocol, dc, s); err != nil {
|
||||
return errors.Wrap(err, "obfs2 handshake")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package mtproxy
|
||||
|
||||
import (
|
||||
"github.com/go-faster/errors"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/gotd/proto/codec"
|
||||
)
|
||||
|
||||
// SecretType represents MTProxy secret type.
|
||||
type SecretType int
|
||||
|
||||
const (
|
||||
// Simple is a basic MTProxy secret.
|
||||
Simple SecretType = iota + 1
|
||||
// Secured is dd-secret.
|
||||
Secured
|
||||
// TLS is fakeTLS MTProxy secret.
|
||||
// First byte should be ee.
|
||||
TLS
|
||||
)
|
||||
|
||||
// Secret represents MTProxy secret.
|
||||
type Secret struct {
|
||||
Secret []byte
|
||||
Tag byte
|
||||
CloakHost string
|
||||
Type SecretType
|
||||
}
|
||||
|
||||
// ExpectedCodec returns codec from secret tag if it exists.
|
||||
func (s Secret) ExpectedCodec() (cdc codec.Codec, _ bool) {
|
||||
switch s.Tag {
|
||||
case codec.AbridgedClientStart[0]:
|
||||
cdc = codec.Abridged{}
|
||||
case codec.IntermediateClientStart[0]:
|
||||
cdc = codec.Intermediate{}
|
||||
case codec.PaddedIntermediateClientStart[0]:
|
||||
cdc = codec.PaddedIntermediate{}
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return cdc, true
|
||||
}
|
||||
|
||||
// ParseSecret checks and parses secret.
|
||||
func ParseSecret(secret []byte) (Secret, error) {
|
||||
r := Secret{
|
||||
Secret: secret,
|
||||
}
|
||||
const simpleLength = 16
|
||||
|
||||
switch {
|
||||
case len(secret) == 1+simpleLength:
|
||||
r.Type = Secured
|
||||
|
||||
r.Tag = secret[0]
|
||||
secret = secret[1:]
|
||||
r.Secret = secret[:simpleLength]
|
||||
case len(secret) > simpleLength:
|
||||
r.Type = TLS
|
||||
|
||||
r.Tag = secret[0]
|
||||
secret = secret[1:]
|
||||
r.Secret = secret[:simpleLength]
|
||||
r.CloakHost = string(secret[simpleLength:])
|
||||
case len(secret) == simpleLength:
|
||||
r.Type = Simple
|
||||
default:
|
||||
return Secret{}, errors.Errorf("invalid secret %q", string(secret))
|
||||
}
|
||||
|
||||
if r.Type != Simple {
|
||||
switch r.Tag {
|
||||
case codec.AbridgedClientStart[0],
|
||||
codec.IntermediateClientStart[0],
|
||||
codec.PaddedIntermediateClientStart[0]:
|
||||
default:
|
||||
return Secret{}, errors.Errorf("unknown tag %+x", r.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mtproxy
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseSecret(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
secret string
|
||||
want SecretType
|
||||
wantErr bool
|
||||
}{
|
||||
{"Simple", "52a493bdfb90eea55739eabff2d92a14", Simple, false},
|
||||
{"Secured", "ddf05fb7acb549be047a7c585116581418", Secured, false},
|
||||
{"Secured", "eef05fb7acb549be047a7c585116581418", Secured, false},
|
||||
{"TLS-google.com", "ee852380f362a09343efb4690c4e17862e676f6f676c652e636f6d", TLS, false},
|
||||
{"TLS-bing.com", "eedf71035a8ed48a623d8e83e66aec4d0562696e672e636f6d", TLS, false},
|
||||
{"TLS-yandex.ru", "ee7cea5c13d65f12fd808de70ddcc8d3a979616e6465782e7275", TLS, false},
|
||||
{"Bad", "52a493bdfb90eea55739eabff2d92a", 0, true},
|
||||
{"Bad", "52a493bdfb90eea55739eabff2d92a1422", 0, true},
|
||||
{"Bad", "dd", 0, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s, err := hex.DecodeString(tt.secret)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := ParseSecret(s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseSecret() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
require.Equal(t, tt.want, got.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user