summaryrefslogtreecommitdiff
path: root/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp')
-rw-r--r--protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp323
1 files changed, 323 insertions, 0 deletions
diff --git a/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp b/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
new file mode 100644
index 0000000000..0e65c82ee7
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/td/mtproto/Transport.cpp
@@ -0,0 +1,323 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/mtproto/Transport.h"
+
+#include "td/mtproto/AuthKey.h"
+#include "td/mtproto/crypto.h"
+
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Status.h"
+
+#include <array>
+
+namespace td {
+namespace mtproto {
+
+// mtproto v1.0
+template <class HeaderT>
+std::tuple<uint32, UInt128> Transport::calc_message_ack_and_key(const HeaderT &head, size_t data_size) {
+ Slice part(head.encrypt_begin(), head.data + data_size);
+ UInt<160> message_sha1;
+ sha1(part, message_sha1.raw);
+ return std::make_tuple(as<uint32>(message_sha1.raw) | (1u << 31), as<UInt128>(message_sha1.raw + 4));
+}
+
+template <class HeaderT>
+size_t Transport::calc_crypto_size(size_t data_size) {
+ size_t enc_size = HeaderT::encrypted_header_size();
+ size_t raw_size = sizeof(HeaderT) - enc_size;
+ return raw_size + ((enc_size + data_size + 15) & ~15);
+}
+
+// mtproto v2.0
+std::tuple<uint32, UInt128> Transport::calc_message_key2(const AuthKey &auth_key, int X, Slice to_encrypt) {
+ // msg_key_large = SHA256 (substr (auth_key, 88+x, 32) + plaintext + random_padding);
+ Sha256State state;
+ sha256_init(&state);
+ sha256_update(Slice(auth_key.key()).substr(88 + X, 32), &state);
+ sha256_update(to_encrypt, &state);
+
+ uint8 msg_key_large_raw[32];
+ MutableSlice msg_key_large(msg_key_large_raw, sizeof(msg_key_large_raw));
+ sha256_final(&state, msg_key_large);
+
+ // msg_key = substr (msg_key_large, 8, 16);
+ UInt128 res_raw;
+ MutableSlice res(res_raw.raw, sizeof(res_raw.raw));
+ res.copy_from(msg_key_large.substr(8, 16));
+
+ return std::make_tuple(as<uint32>(msg_key_large_raw) | (1u << 31), res_raw);
+}
+
+template <class HeaderT>
+size_t Transport::calc_crypto_size2(size_t data_size) {
+ size_t enc_size = HeaderT::encrypted_header_size();
+ size_t raw_size = sizeof(HeaderT) - enc_size;
+ size_t encrypted_size = (enc_size + data_size + 12 + 15) & ~15;
+ std::array<size_t, 10> sizes{{64, 128, 192, 256, 384, 512, 768, 1024, 1280}};
+ for (auto size : sizes) {
+ if (encrypted_size <= size) {
+ return raw_size + size;
+ }
+ }
+ encrypted_size = (encrypted_size - 1280 + 447) / 448 * 448 + 1280;
+
+ return raw_size + encrypted_size;
+}
+
+size_t Transport::calc_no_crypto_size(size_t data_size) {
+ return sizeof(NoCryptoHeader) + data_size;
+}
+
+Status Transport::read_no_crypto(MutableSlice message, PacketInfo *info, MutableSlice *data) {
+ if (message.size() < sizeof(NoCryptoHeader)) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: too small [message.size()=" << message.size()
+ << "] < [sizeof(NoCryptoHeader) = " << sizeof(NoCryptoHeader) << "]");
+ }
+ auto &header = as<NoCryptoHeader>(message.begin());
+ size_t data_size = message.size() - sizeof(NoCryptoHeader);
+ CHECK(message.size() == calc_no_crypto_size(data_size));
+ *data = MutableSlice(header.data, data_size);
+ return Status::OK();
+}
+
+template <class HeaderT, class PrefixT>
+Status Transport::read_crypto_impl(int X, MutableSlice message, const AuthKey &auth_key, HeaderT **header_ptr,
+ PrefixT **prefix_ptr, MutableSlice *data, PacketInfo *info) {
+ if (message.size() < sizeof(HeaderT)) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: too small [message.size()=" << message.size()
+ << "] < [sizeof(HeaderT) = " << sizeof(HeaderT) << "]");
+ }
+ auto *header = &as<HeaderT>(message.begin());
+ *header_ptr = header;
+ auto to_decrypt = MutableSlice(header->encrypt_begin(), message.uend());
+ if (to_decrypt.size() % 16 != 0) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: size of encrypted part is not multiple of 16 [size="
+ << to_decrypt.size() << "]");
+ }
+
+ if (header->auth_key_id != auth_key.id()) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: auth_key_id mismatch [found="
+ << format::as_hex(header->auth_key_id)
+ << "] [expected=" << format::as_hex(auth_key.id()) << "]");
+ }
+
+ UInt256 aes_key;
+ UInt256 aes_iv;
+ if (info->version == 1) {
+ KDF(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
+ } else {
+ KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
+ }
+
+ aes_ige_decrypt(aes_key, &aes_iv, to_decrypt, to_decrypt);
+
+ size_t tail_size = message.end() - reinterpret_cast<char *>(header->data);
+ if (tail_size < sizeof(PrefixT)) {
+ return Status::Error("Too small encrypted part");
+ }
+ auto *prefix = &as<PrefixT>(header->data);
+ *prefix_ptr = prefix;
+ size_t data_size = prefix->message_data_length + sizeof(PrefixT);
+ bool is_length_ok = prefix->message_data_length % 4 == 0;
+ UInt128 real_message_key;
+
+ if (info->version == 1) {
+ auto expected_size = calc_crypto_size<HeaderT>(data_size);
+ is_length_ok = (is_length_ok & (expected_size == message.size())) != 0;
+ auto check_size = data_size * is_length_ok + tail_size * (1 - is_length_ok);
+ std::tie(info->message_ack, real_message_key) = calc_message_ack_and_key(*header, check_size);
+ } else {
+ size_t pad_size = tail_size - data_size;
+ is_length_ok = (is_length_ok & (tail_size - sizeof(PrefixT) >= prefix->message_data_length) & (12 <= pad_size) &
+ (pad_size <= 1024)) != 0;
+ std::tie(info->message_ack, real_message_key) = calc_message_key2(auth_key, X, to_decrypt);
+ }
+
+ bool is_key_ok = true;
+ for (size_t i = 0; i < sizeof(real_message_key.raw); i++) {
+ is_key_ok &= real_message_key.raw[i] == header->message_key.raw[i];
+ }
+
+ if (!is_key_ok) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: message_key mismatch [found="
+ << format::as_hex_dump(header->message_key)
+ << "] [expected=" << format::as_hex_dump(real_message_key) << "]");
+ }
+ if (!is_length_ok) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: invalid length " << tag("total_size", message.size())
+ << tag("message_data_length", prefix->message_data_length));
+ }
+
+ *data = MutableSlice(header->data, data_size);
+ return Status::OK();
+}
+
+Status Transport::read_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data) {
+ CryptoHeader *header = nullptr;
+ CryptoPrefix *prefix = nullptr;
+ TRY_STATUS(read_crypto_impl(8, message, auth_key, &header, &prefix, data, info));
+ CHECK(header != nullptr);
+ CHECK(prefix != nullptr);
+ CHECK(info != nullptr);
+ info->type = PacketInfo::Common;
+ info->salt = header->salt;
+ info->session_id = header->session_id;
+ info->message_id = prefix->message_id;
+ info->seq_no = prefix->seq_no;
+ return Status::OK();
+}
+Status Transport::read_e2e_crypto(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data) {
+ EndToEndHeader *header = nullptr;
+ EndToEndPrefix *prefix = nullptr;
+ TRY_STATUS(read_crypto_impl(info->is_creator && info->version != 1 ? 8 : 0, message, auth_key, &header, &prefix, data,
+ info));
+ CHECK(header != nullptr);
+ CHECK(prefix != nullptr);
+ CHECK(info != nullptr);
+ info->type = PacketInfo::EndToEnd;
+ return Status::OK();
+}
+
+size_t Transport::write_no_crypto(const Storer &storer, PacketInfo *info, MutableSlice dest) {
+ size_t size = calc_no_crypto_size(storer.size());
+ if (size > dest.size()) {
+ return size;
+ }
+ auto &header = as<NoCryptoHeader>(dest.begin());
+ header.auth_key_id = 0;
+ storer.store(header.data);
+ return size;
+}
+
+template <class HeaderT>
+void Transport::write_crypto_impl(int X, const Storer &storer, const AuthKey &auth_key, PacketInfo *info,
+ HeaderT *header, size_t data_size) {
+ storer.store(header->data);
+ VLOG(raw_mtproto) << "SEND" << format::as_hex_dump<4>(Slice(header->data, data_size));
+ // LOG(ERROR) << "SEND" << format::as_hex_dump<4>(Slice(header->data, data_size)) << info->version;
+
+ size_t size = 0;
+ if (info->version == 1) {
+ size = calc_crypto_size<HeaderT>(data_size);
+ } else {
+ size = calc_crypto_size2<HeaderT>(data_size);
+ }
+
+ size_t pad_size = size - (sizeof(HeaderT) + data_size);
+ MutableSlice pad(header->data + data_size, pad_size);
+ Random::secure_bytes(pad.ubegin(), pad.size());
+ MutableSlice to_encrypt = MutableSlice(header->encrypt_begin(), pad.uend());
+
+ if (info->version == 1) {
+ std::tie(info->message_ack, info->message_key) = calc_message_ack_and_key(*header, data_size);
+ } else {
+ std::tie(info->message_ack, info->message_key) = calc_message_key2(auth_key, X, to_encrypt);
+ }
+
+ header->message_key = info->message_key;
+
+ UInt256 aes_key;
+ UInt256 aes_iv;
+ if (info->version == 1) {
+ KDF(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
+ } else {
+ KDF2(auth_key.key(), header->message_key, X, &aes_key, &aes_iv);
+ }
+
+ aes_ige_encrypt(aes_key, &aes_iv, to_encrypt, to_encrypt);
+}
+
+size_t Transport::write_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) {
+ size_t data_size = storer.size();
+ size_t size;
+ if (info->version == 1) {
+ size = calc_crypto_size<CryptoHeader>(data_size);
+ } else {
+ size = calc_crypto_size2<CryptoHeader>(data_size);
+ }
+ if (size > dest.size()) {
+ return size;
+ }
+
+ auto &header = as<CryptoHeader>(dest.begin());
+ header.auth_key_id = auth_key.id();
+ header.salt = info->salt;
+ header.session_id = info->session_id;
+
+ write_crypto_impl(0, storer, auth_key, info, &header, data_size);
+
+ return size;
+}
+
+size_t Transport::write_e2e_crypto(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) {
+ size_t data_size = storer.size();
+ size_t size;
+ if (info->version == 1) {
+ size = calc_crypto_size<EndToEndHeader>(data_size);
+ } else {
+ size = calc_crypto_size2<EndToEndHeader>(data_size);
+ }
+ if (size > dest.size()) {
+ return size;
+ }
+
+ auto &header = as<EndToEndHeader>(dest.begin());
+ header.auth_key_id = auth_key.id();
+
+ write_crypto_impl(info->is_creator || info->version == 1 ? 0 : 8, storer, auth_key, info, &header, data_size);
+
+ return size;
+}
+
+Result<uint64> Transport::read_auth_key_id(Slice message) {
+ if (message.size() < 8) {
+ return Status::Error(PSLICE() << "Invalid mtproto message: smaller than 8 bytes [size=" << message.size() << "]");
+ }
+ return as<uint64>(message.begin());
+}
+
+Status Transport::read(MutableSlice message, const AuthKey &auth_key, PacketInfo *info, MutableSlice *data,
+ int32 *error_code) {
+ if (message.size() < 8) {
+ if (message.size() == 4) {
+ *error_code = as<int32>(message.begin());
+ return Status::OK();
+ }
+ return Status::Error(PSLICE() << "Invalid mtproto message: smaller than 8 bytes [size=" << message.size() << "]");
+ }
+ info->auth_key_id = as<int64>(message.begin());
+ info->no_crypto_flag = info->auth_key_id == 0;
+ if (info->type == PacketInfo::EndToEnd) {
+ return read_e2e_crypto(message, auth_key, info, data);
+ }
+ if (info->no_crypto_flag) {
+ return read_no_crypto(message, info, data);
+ } else {
+ if (auth_key.empty()) {
+ return Status::Error("Failed to decrypt mtproto message: auth key is empty");
+ }
+ return read_crypto(message, auth_key, info, data);
+ }
+}
+
+size_t Transport::write(const Storer &storer, const AuthKey &auth_key, PacketInfo *info, MutableSlice dest) {
+ if (info->type == PacketInfo::EndToEnd) {
+ return write_e2e_crypto(storer, auth_key, info, dest);
+ }
+ if (info->no_crypto_flag) {
+ return write_no_crypto(storer, info, dest);
+ } else {
+ CHECK(!auth_key.empty());
+ return write_crypto(storer, auth_key, info, dest);
+ }
+}
+} // namespace mtproto
+} // namespace td