Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WASM-1] Implement the obfuscated MTProto transport #298

Merged
merged 2 commits into from
Dec 21, 2024

Conversation

er-azh
Copy link
Contributor

@er-azh er-azh commented Dec 20, 2024

This PR implements support for transport obfuscation. It is the first step towards a web port.

Changes I had to make to make this feasible:

  • Transport::unpack() now takes in a mutable reference to the buffer to do the decryption in-place.
  • Transport::unpack() now returns Result<Vec<UnpackedOffset>>. This is needed since a read might, and frequently will, contain multiple transport payloads.
  • Transport::unpack() now returns an empty Vec instead of Error::MissingBytes when there's more data needed.
  • ctr is a new dependency in grammers-mtsender.
  • Transport::unpack() must now be called with the same buffer and the buffer must be kept between calls.

While changing the return type of unpack to a Vec doesn't also seem good, it was the only compromise I could think of to allow this transport to work without significant performance impact while also keeping the decryption contained inside of the transport implementation.

I'm now storing the decryption state inside the transport itself. So should have almost zero overhead other than the encryption itself

Copy link
Owner

@Lonami Lonami left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work!

IIRC at some point Error::MissingBytes also reported how many bytes we were expecting. I probably removed it because it wasn't needed, but now it is.

If we bring that back, then the obfuscated transport could decrypt the first 4 bytes (which contain the entire header for all supported transports), attempt to unpack, and from the reported error we would know how much more we need to decrypt.

Then we can keep a flag "have we decrypted the header yet" (or better yet, store an Option with the last-requested size) to avoid doing the decryption on that again. And once we have enough bytes, we decrypt the rest and return that.

Now we can go back to returning a single Result<Offset> instead of a Vec with the allocations that implies. And it should also mean we don't need to undo decryptions.

WDYT?

lib/grammers-crypto/src/obfuscated.rs Outdated Show resolved Hide resolved
lib/grammers-crypto/src/obfuscated.rs Outdated Show resolved Hide resolved
lib/grammers-mtproto/src/transport/abridged.rs Outdated Show resolved Hide resolved
lib/grammers-mtproto/src/transport/abridged.rs Outdated Show resolved Hide resolved
lib/grammers-mtproto/src/transport/obfuscated.rs Outdated Show resolved Hide resolved
@er-azh
Copy link
Contributor Author

er-azh commented Dec 21, 2024

@Lonami
I've amended my commit and implemented what we discussed in telegram (storing the decryption state inside of the transport itself).
I've also applied all the suggestions you mentioned in the review.

@Lonami Lonami merged commit 173a544 into Lonami:master Dec 21, 2024
1 check passed
fn unpack(&mut self, buffer: &[u8]) -> Result<UnpackedOffset, Error>;
/// Subsequent calls to `unpack` should be made with the same buffer,
/// with the data on the ranges from previous `UnpackedOffset` removed.
fn unpack(&mut self, buffer: &mut [u8]) -> Result<UnpackedOffset, Error>;
Copy link
Contributor

@MOZGIII MOZGIII Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should probably be an explainer (in the comments) on why the buffer is mut here... As I was wondering.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pack takes a mutable input so in my view it's reasonable for unpack to do so too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By why would we alter the buffer itself? Isn't it supposed to be the read buffer?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both packing unpacking occur in-place. None of the previous transports had to modify the buffer during unpack, but obfuscated does. The caller doesn't read from the buffer contents until the calls succeed.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Forgot to add, but that said, I'm happy to have the documentation improved to leave this in a better place than a random GitHub discussion.)


fn unpack(&mut self, buffer: &mut [u8]) -> Result<UnpackedOffset, Error> {
if buffer.len() < self.decrypt_tail {
panic!("buffer is smaller than what was decrypted");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this really be a panic? If so - there should be an explainer in the comments as to where this is checked and why this case can't be handled (i.e. we should crash the whole app here).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this should really panic. Reaching here means the user has messed up their buffer in an unexpected way. And there'd be a panic further down anyways, so it's better to provide a user friendly message than to crash with an indexing error.

There's also an explainer in the comments of Transport::unpack() and this will never happen when used with grammers-mtsender. It's only panic!(...) and not unreachable!(...) because the crate can be used standalone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants