ccb349f3d2
Read called XORKeyStream(b, b) — XOR-ing the entire caller buffer even when the underlying transport returned fewer bytes. AES-CTR's keystream position is then advanced by len(b), but the peer only consumed n bytes' worth of keystream. After a single short read the two keystreams diverge for the lifetime of the connection, every subsequent MTProto message decrypts to garbage, and the engine fails with "consume message: decrypt: msg_key is invalid". The faketls layer makes short reads routine: each Read returns at most one TLS Application record's payload, regardless of how big the caller buffer is. So in practice the stream desynced almost immediately on high-traffic clients (active supergroups, post-relogin catch-up) and intermittently on quiet ones. Match the upstream gotd/td fix and only XOR the n bytes that came out of the transport. Add a regression test (chunkConn delivers ciphertext in 7-byte chunks; client reads through Obfuscated2.Read with a 128-byte buffer; plaintext must round-trip). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
1.4 KiB
Go
65 lines
1.4 KiB
Go
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 {
|
|
// IMPORTANT: only XOR the n bytes that were actually read.
|
|
// XOR-ing the full b advances the CTR keystream past where the
|
|
// server is and permanently desyncs the stream — every later
|
|
// MTProto message decrypts to garbage and the engine fails
|
|
// with "msg_key is invalid".
|
|
o.decrypt.XORKeyStream(b[:n], b[:n])
|
|
}
|
|
return n, err
|
|
}
|