Commit Graph

5 Commits

Author SHA1 Message Date
Igor Artamonov ccb349f3d2 obfuscated2: only XOR bytes actually delivered on Read
Go / Lint (old) (push) Failing after 4m40s
Go / Lint (latest) (push) Failing after 4m39s
Go / Lint (old) (pull_request) Failing after 4m41s
Go / Lint (latest) (pull_request) Failing after 4m40s
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>
2026-05-01 12:33:44 +03:00
Igor Artamonov 4768065e72 faketls: skip ChangeCipherSpec records on read
Go / Lint (old) (push) Failing after 4m40s
Go / Lint (latest) (push) Failing after 4m40s
Go / Lint (old) (pull_request) Failing after 4m42s
Go / Lint (latest) (pull_request) Failing after 4m39s
The Read path treated every TLS record's payload as application data
and wrote it into readBuf — including the 1-byte payload (0x01) of
ChangeCipherSpec records. mtg sends those records intermittently as a
TLS-compat keep-alive; once one arrived inside the data stream it
desynced the obfuscated2 CTR keystream by one byte. From that point
on every MTProto message decrypted to garbage and the engine failed
with "decrypt: msg_key is invalid", forcibly closed the connection,
and looped.

The Go switch cases for ChangeCipherSpec and Application were both
empty (no fallthrough, no continue), so control reached the
o.readBuf.Write(rec.Data) call below the switch for both — exactly
the wrong behaviour for CCS.

Reshape the loop so that:
  - ChangeCipherSpec records are silently dropped
  - Application records are written to readBuf and returned
  - Handshake / unsupported types still error out

This matches tdlib's TlsTransport (CCS is skipped at the TLS framing
layer and never reaches the MTProto decoder).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 12:17:27 +03:00
Igor Artamonov 64bf6bfe90 faketls: emit GREASE bytes and a real padding extension
Go / Lint (old) (push) Failing after 4m39s
Go / Lint (latest) (push) Failing after 4m39s
Go / Lint (old) (pull_request) Failing after 4m39s
Go / Lint (latest) (pull_request) Failing after 4m42s
The ClientHello builder used a closure G(n) that was supposed to insert
two random GREASE bytes (RFC 8701, 0x?A?A pattern) at known positions
but expanded the buffer by zero. Every grease slot was therefore
omitted, and the trailing padding extension was written as a bare ext
id 0x0015 followed by raw zeros — its length field was never set.

Concretely, the old output looked structurally invalid to mtg's faketls
validator: the cipher list was off by two, supported_groups declared a
list_length larger than its body, and what should have been the padding
extension parsed as a stream of empty server_name extensions. mtg
responded with a fatal TLS Alert (description 50, decode_error) and
shut the connection.

Fix:
- generate seven distinct GREASE bytes per ClientHello, with the
  tdlib constraint grease[3] != grease[4]
- thread an io.Reader through writeClientHello so generation is
  deterministic in tests and keyed off the FakeTLS rand source in prod
- replace the trailing zero-pad with a proper padding extension whose
  length field is computed so the ClientHello is exactly 517 bytes

Add a regression test (structure_test.go) that feeds the result to
crypto/tls.Server: it must not return decode_error / malformed /
syntax errors. The previous output failed this; the new output passes.

The TestTLS golden vector is regenerated for the new layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 11:15:03 +03:00
Igor Artamonov aab48f0dbe faketls: include real record type and peek bytes on handshake errors
Go / Lint (old) (push) Failing after 4m43s
Go / Lint (latest) (push) Failing after 4m40s
Go / Lint (old) (pull_request) Failing after 4m40s
Go / Lint (latest) (pull_request) Failing after 4m43s
The previous error path used errors.Wrap(err, "unexpected record type")
inside type-mismatch branches where err was already nil. With
go-faster/errors that produced a wrapError with no cause and no detail,
making the user-visible message "unexpected record type" useless for
diagnostics — there was no way to tell what mtg actually sent.

Switch to errors.Errorf with the actual received byte and a 32-byte
hex peek of the read buffer. Also wrap the read-error path with the
same peek so a partial response is visible.

This is a diagnostic-only change; the parser semantics are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:51:50 +03:00
Adam Van Ymeren 7a04f298d2 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
2025-06-27 20:03:37 -07:00