init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_abstract_socket.h"
#include "mtproto/details/mtproto_tcp_socket.h"
#include "mtproto/details/mtproto_tls_socket.h"
namespace MTP::details {
std::unique_ptr<AbstractSocket> AbstractSocket::Create(
not_null<QThread*> thread,
const bytes::vector &secret,
const QNetworkProxy &proxy,
bool protocolForFiles) {
if (secret.size() >= 21 && secret[0] == bytes::type(0xEE)) {
return std::make_unique<TlsSocket>(
thread,
secret,
proxy,
protocolForFiles);
} else {
return std::make_unique<TcpSocket>(thread, proxy, protocolForFiles);
}
}
void AbstractSocket::logError(int errorCode, const QString &errorText) {
const auto log = [&](const QString &message) {
DEBUG_LOG(("Socket %1 Error: ").arg(_debugId) + message);
};
switch (errorCode) {
case QAbstractSocket::ConnectionRefusedError:
log(u"Socket connection refused - %1."_q.arg(errorText));
break;
case QAbstractSocket::RemoteHostClosedError:
log(u"Remote host closed socket connection - %1."_q.arg(errorText));
break;
case QAbstractSocket::HostNotFoundError:
log(u"Host not found - %1."_q.arg(errorText));
break;
case QAbstractSocket::SocketTimeoutError:
log(u"Socket timeout - %1."_q.arg(errorText));
break;
case QAbstractSocket::NetworkError: {
log(u"Network - %1."_q.arg(errorText));
} break;
case QAbstractSocket::ProxyAuthenticationRequiredError:
case QAbstractSocket::ProxyConnectionRefusedError:
case QAbstractSocket::ProxyConnectionClosedError:
case QAbstractSocket::ProxyConnectionTimeoutError:
case QAbstractSocket::ProxyNotFoundError:
case QAbstractSocket::ProxyProtocolError:
log(u"Proxy (%1) - %2."_q.arg(errorCode).arg(errorText));
break;
default:
log(u"Other (%1) - %2."_q.arg(errorCode).arg(errorText));
break;
}
}
} // namespace MTP::details

View File

@@ -0,0 +1,76 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/bytes.h"
#include "base/basic_types.h"
namespace MTP::details {
class AbstractSocket : protected QObject {
public:
static std::unique_ptr<AbstractSocket> Create(
not_null<QThread*> thread,
const bytes::vector &secret,
const QNetworkProxy &proxy,
bool protocolForFiles);
void setDebugId(const QString &id) {
_debugId = id;
}
explicit AbstractSocket(not_null<QThread*> thread) {
moveToThread(thread);
}
virtual ~AbstractSocket() = default;
[[nodiscard]] rpl::producer<> connected() const {
return _connected.events();
}
[[nodiscard]] rpl::producer<> disconnected() const {
return _disconnected.events();
}
[[nodiscard]] rpl::producer<> readyRead() const {
return _readyRead.events();
}
[[nodiscard]] rpl::producer<> error() const {
return _error.events();
}
[[nodiscard]] rpl::producer<> syncTimeRequests() const {
return _syncTimeRequests.events();
}
virtual void connectToHost(const QString &address, int port) = 0;
[[nodiscard]] virtual bool isGoodStartNonce(bytes::const_span nonce) = 0;
virtual void timedOut() = 0;
[[nodiscard]] virtual bool isConnected() = 0;
[[nodiscard]] virtual bool hasBytesAvailable() = 0;
[[nodiscard]] virtual int64 read(bytes::span buffer) = 0;
virtual void write(
bytes::const_span prefix,
bytes::const_span buffer) = 0;
virtual int32 debugState() = 0;
[[nodiscard]] virtual QString debugPostfix() const = 0;
protected:
static const int kFilesSendBufferSize = 2 * 1024 * 1024;
static const int kFilesReceiveBufferSize = 2 * 1024 * 1024;
void logError(int errorCode, const QString &errorText);
QString _debugId;
rpl::event_stream<> _connected;
rpl::event_stream<> _disconnected;
rpl::event_stream<> _readyRead;
rpl::event_stream<> _error;
rpl::event_stream<> _syncTimeRequests;
};
} // namespace MTP::details

View File

@@ -0,0 +1,92 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_bound_key_creator.h"
#include "mtproto/details/mtproto_serialized_request.h"
namespace MTP::details {
BoundKeyCreator::BoundKeyCreator(DcKeyRequest request, Delegate delegate)
: _request(request)
, _delegate(std::move(delegate)) {
}
void BoundKeyCreator::start(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions) {
Expects(!_creator.has_value());
auto delegate = DcKeyCreator::Delegate();
delegate.done = _delegate.unboundReady;
delegate.sentSome = _delegate.sentSome;
delegate.receivedSome = _delegate.receivedSome;
_creator.emplace(
dcId,
protocolDcId,
connection,
dcOptions,
std::move(delegate),
_request);
}
void BoundKeyCreator::stop() {
_creator = std::nullopt;
}
void BoundKeyCreator::bind(AuthKeyPtr &&persistentKey) {
stop();
_binder.emplace(std::move(persistentKey));
}
void BoundKeyCreator::restartBinder() {
if (_binder) {
_binder.emplace(_binder->persistentKey());
}
}
bool BoundKeyCreator::readyToBind() const {
return _binder.has_value();
}
SerializedRequest BoundKeyCreator::prepareBindRequest(
const AuthKeyPtr &temporaryKey,
uint64 sessionId) {
Expects(_binder.has_value());
return _binder->prepareRequest(temporaryKey, sessionId);
}
DcKeyBindState BoundKeyCreator::handleBindResponse(
const mtpBuffer &response) {
Expects(_binder.has_value());
return _binder->handleResponse(response);
}
AuthKeyPtr BoundKeyCreator::bindPersistentKey() const {
Expects(_binder.has_value());
return _binder->persistentKey();
}
bool IsDestroyedTemporaryKeyError(const mtpBuffer &buffer) {
auto from = buffer.data();
auto error = MTPRpcError();
if (!error.read(from, from + buffer.size())) {
return false;
}
return error.match([&](const MTPDrpc_error &data) {
return (data.verror_code().v == 401)
&& (data.verror_message().v == "AUTH_KEY_PERM_EMPTY");
});
}
} // namespace MTP::details

View File

@@ -0,0 +1,56 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/details/mtproto_dc_key_creator.h"
#include "mtproto/details/mtproto_dc_key_binder.h"
namespace MTP::details {
class SerializedRequest;
class BoundKeyCreator final {
public:
struct Delegate {
Fn<void(base::expected<DcKeyResult, DcKeyError>)> unboundReady;
Fn<void(uint64)> sentSome;
Fn<void()> receivedSome;
};
BoundKeyCreator(DcKeyRequest request, Delegate delegate);
void start(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions);
void stop();
void bind(AuthKeyPtr &&persistentKey);
void restartBinder();
[[nodiscard]] bool readyToBind() const;
[[nodiscard]] SerializedRequest prepareBindRequest(
const AuthKeyPtr &temporaryKey,
uint64 sessionId);
[[nodiscard]] DcKeyBindState handleBindResponse(
const mtpBuffer &response);
[[nodiscard]] AuthKeyPtr bindPersistentKey() const;
private:
const DcKeyRequest _request;
Delegate _delegate;
std::optional<DcKeyCreator> _creator;
std::optional<DcKeyBinder> _binder;
};
[[nodiscard]] bool IsDestroyedTemporaryKeyError(const mtpBuffer &buffer);
} // namespace MTP::details

View File

@@ -0,0 +1,132 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_dc_key_binder.h"
#include "mtproto/details/mtproto_serialized_request.h"
#include "mtproto/mtp_instance.h"
#include "base/unixtime.h"
#include "base/openssl_help.h"
#include "base/random.h"
#include "scheme.h"
#include <QtCore/QPointer>
namespace MTP::details {
namespace {
[[nodiscard]] QByteArray EncryptBindAuthKeyInner(
const AuthKeyPtr &persistentKey,
mtpMsgId realMsgId,
const MTPBindAuthKeyInner &data) {
auto serialized = SerializedRequest::Serialize(data);
serialized.setMsgId(realMsgId);
serialized.setSeqNo(0);
serialized.addPadding(true);
constexpr auto kMsgIdPosition = SerializedRequest::kMessageIdPosition;
constexpr auto kMinMessageSize = 5;
const auto sizeInPrimes = serialized->size();
const auto messageSize = serialized.messageSize();
Assert(messageSize >= kMinMessageSize);
Assert(sizeInPrimes >= kMsgIdPosition + messageSize);
const auto sizeInBytes = sizeInPrimes * sizeof(mtpPrime);
const auto padding = sizeInBytes
- (kMsgIdPosition + messageSize) * sizeof(mtpPrime);
// session_id, salt - just random here.
bytes::set_random(bytes::make_span(*serialized).subspan(
0,
kMsgIdPosition * sizeof(mtpPrime)));
const auto hash = openssl::Sha1(bytes::make_span(*serialized).subspan(
0,
sizeInBytes - padding));
auto msgKey = MTPint128();
bytes::copy(
bytes::object_as_span(&msgKey),
bytes::make_span(hash).subspan(4));
constexpr auto kAuthKeyIdBytes = 2 * sizeof(mtpPrime);
constexpr auto kMessageKeyPosition = kAuthKeyIdBytes;
constexpr auto kMessageKeyBytes = 4 * sizeof(mtpPrime);
constexpr auto kPrefix = (kAuthKeyIdBytes + kMessageKeyBytes);
auto encrypted = QByteArray(kPrefix + sizeInBytes, Qt::Uninitialized);
*reinterpret_cast<uint64*>(encrypted.data()) = persistentKey->keyId();
*reinterpret_cast<MTPint128*>(encrypted.data() + kMessageKeyPosition)
= msgKey;
aesIgeEncrypt_oldmtp(
serialized->constData(),
encrypted.data() + kPrefix,
sizeInBytes,
persistentKey,
msgKey);
return encrypted;
}
} // namespace
DcKeyBinder::DcKeyBinder(AuthKeyPtr &&persistentKey)
: _persistentKey(std::move(persistentKey)) {
Expects(_persistentKey != nullptr);
}
SerializedRequest DcKeyBinder::prepareRequest(
const AuthKeyPtr &temporaryKey,
uint64 sessionId) {
Expects(temporaryKey != nullptr);
Expects(temporaryKey->expiresAt() != 0);
const auto nonce = base::RandomValue<uint64>();
const auto msgId = base::unixtime::mtproto_msg_id();
auto result = SerializedRequest::Serialize(MTPauth_BindTempAuthKey(
MTP_long(_persistentKey->keyId()),
MTP_long(nonce),
MTP_int(temporaryKey->expiresAt()),
MTP_bytes(EncryptBindAuthKeyInner(
_persistentKey,
msgId,
MTP_bind_auth_key_inner(
MTP_long(nonce),
MTP_long(temporaryKey->keyId()),
MTP_long(_persistentKey->keyId()),
MTP_long(sessionId),
MTP_int(temporaryKey->expiresAt()))))));
result.setMsgId(msgId);
return result;
}
DcKeyBindState DcKeyBinder::handleResponse(const mtpBuffer &response) {
Expects(!response.isEmpty());
auto from = response.data();
const auto end = from + response.size();
auto error = MTPRpcError();
if (response[0] == mtpc_boolTrue) {
return DcKeyBindState::Success;
} else if (response[0] == mtpc_rpc_error && error.read(from, end)) {
const auto destroyed = error.match([&](const MTPDrpc_error &data) {
return (data.verror_code().v == 400)
&& (data.verror_message().v == "ENCRYPTED_MESSAGE_INVALID");
});
return destroyed
? DcKeyBindState::DefinitelyDestroyed
: DcKeyBindState::Failed;
} else {
return DcKeyBindState::Failed;
}
}
AuthKeyPtr DcKeyBinder::persistentKey() const {
return _persistentKey;
}
} // namespace MTP::details

View File

@@ -0,0 +1,42 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/core_types.h"
#include "mtproto/mtproto_auth_key.h"
namespace MTP {
class Instance;
} // namespace MTP
namespace MTP::details {
class SerializedRequest;
enum class DcKeyBindState {
Success,
Failed,
DefinitelyDestroyed,
};
class DcKeyBinder final {
public:
explicit DcKeyBinder(AuthKeyPtr &&persistentKey);
[[nodiscard]] SerializedRequest prepareRequest(
const AuthKeyPtr &temporaryKey,
uint64 sessionId);
[[nodiscard]] DcKeyBindState handleResponse(const mtpBuffer &response);
[[nodiscard]] AuthKeyPtr persistentKey() const;
private:
AuthKeyPtr _persistentKey;
};
} // namespace MTP::details

View File

@@ -0,0 +1,855 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_dc_key_creator.h"
#include "mtproto/details/mtproto_rsa_public_key.h"
#include "mtproto/connection_abstract.h"
#include "mtproto/mtproto_dh_utils.h"
#include "base/openssl_help.h"
#include "base/random.h"
#include "base/unixtime.h"
#include "scheme.h"
#include "logs.h"
#include <cmath>
namespace MTP::details {
namespace {
struct ParsedPQ {
QByteArray p;
QByteArray q;
};
// Fast PQ factorization taken from TDLib:
// https://github.com/tdlib/td/blob/v1.7.0/tdutils/td/utils/crypto.cpp
[[nodiscard]] uint64 gcd(uint64 a, uint64 b) {
if (a == 0) {
return b;
} else if (b == 0) {
return a;
}
int shift = 0;
while ((a & 1) == 0 && (b & 1) == 0) {
a >>= 1;
b >>= 1;
shift++;
}
while (true) {
while ((a & 1) == 0) {
a >>= 1;
}
while ((b & 1) == 0) {
b >>= 1;
}
if (a > b) {
a -= b;
} else if (b > a) {
b -= a;
} else {
return a << shift;
}
}
}
[[nodiscard]] uint64 FactorizeSmallPQ(uint64 pq) {
if (pq < 2 || pq >(static_cast<uint64>(1) << 63)) {
return 1;
}
uint64 g = 0;
for (int i = 0, iter = 0; i < 3 || iter < 1000; i++) {
uint64 q = (17 + base::RandomIndex(16)) % (pq - 1);
uint64 x = base::RandomValue<uint64>() % (pq - 1) + 1;
uint64 y = x;
int lim = 1 << (std::min(5, i) + 18);
for (int j = 1; j < lim; j++) {
iter++;
uint64 a = x;
uint64 b = x;
uint64 c = q;
// c += a * b
while (b) {
if (b & 1) {
c += a;
if (c >= pq) {
c -= pq;
}
}
a += a;
if (a >= pq) {
a -= pq;
}
b >>= 1;
}
x = c;
uint64 z = x < y ? pq + x - y : x - y;
g = gcd(z, pq);
if (g != 1) {
break;
}
if (!(j & (j - 1))) {
y = x;
}
}
if (g > 1 && g < pq) {
break;
}
}
if (g != 0) {
uint64 other = pq / g;
if (other < g) {
g = other;
}
}
return g;
}
ParsedPQ FactorizeBigPQ(const QByteArray &pqStr) {
using namespace openssl;
Context context;
BigNum a;
BigNum b;
BigNum p;
BigNum q;
auto one = BigNum(1);
auto pq = BigNum(bytes::make_span(pqStr));
bool found = false;
for (int i = 0, iter = 0; !found && (i < 3 || iter < 1000); i++) {
int32 t = 17 + base::RandomIndex(16);
a.setWord(base::RandomValue<uint32>());
b = a;
int32 lim = 1 << (i + 23);
for (int j = 1; j < lim; j++) {
iter++;
a.setModMul(a, a, pq, context);
a.setAdd(a, BigNum(uint32(t)));
if (BigNum::Compare(a, pq) >= 0) {
a = BigNum::Sub(a, pq);
}
if (BigNum::Compare(a, b) > 0) {
q.setSub(a, b);
} else {
q.setSub(b, a);
}
p.setGcd(q, pq, context);
if (BigNum::Compare(p, one) != 0) {
found = true;
break;
}
if ((j & (j - 1)) == 0) {
b = a;
}
}
}
if (!found) {
return ParsedPQ();
}
BigNum::Div(&q, nullptr, pq, p, context);
if (BigNum::Compare(p, q) > 0) {
std::swap(p, q);
}
const auto pb = p.getBytes();
const auto qb = q.getBytes();
return {
QByteArray(reinterpret_cast<const char*>(pb.data()), pb.size()),
QByteArray(reinterpret_cast<const char*>(qb.data()), qb.size())
};
}
[[nodiscard]] ParsedPQ FactorizePQ(const QByteArray &pqStr) {
const auto size = pqStr.size();
if (size > 8 || (size == 8 && (uchar(pqStr[0]) & 128) != 0)) {
return FactorizeBigPQ(pqStr);
}
auto ptr = reinterpret_cast<const uchar*>(pqStr.data());
uint64 pq = 0;
for (auto i = 0; i != size; ++i) {
pq = (pq << 8) | ptr[i];
}
auto p = FactorizeSmallPQ(pq);
if (p == 0 || (pq % p) != 0) {
return ParsedPQ();
}
auto q = pq / p;
auto pStr = QByteArray(4, Qt::Uninitialized);
uchar *pChars = (uchar*)pStr.data();
for (auto i = 0; i != 4; ++i) {
*(pChars + 3 - i) = (uchar)(p & 0xFF);
p >>= 8;
}
auto qStr = QByteArray(4, Qt::Uninitialized);
uchar *qChars = (uchar*)qStr.data();
for (auto i = 0; i != 4; ++i) {
*(qChars + 3 - i) = (uchar)(q & 0xFF);
q >>= 8;
}
return { pStr, qStr };
}
[[nodiscard]] bool IsGoodEncryptedInner(
bytes::const_span keyAesEncrypted,
const RSAPublicKey &key) {
Expects(keyAesEncrypted.size() == 256);
const auto modulus = key.getN();
const auto e = key.getE();
const auto shift = (256 - int(modulus.size()));
Assert(shift >= 0);
for (auto i = 0; i != 256; ++i) {
const auto a = keyAesEncrypted[i];
const auto b = (i < shift)
? bytes::type(0)
: modulus[i - shift];
if (a > b) {
return false;
} else if (a < b) {
return true;
}
}
return false;
}
template <typename PQInnerData>
[[nodiscard]] bytes::vector EncryptPQInnerRSA(
const PQInnerData &data,
const RSAPublicKey &key) {
DEBUG_LOG(("AuthKey Info: encrypting pq inner..."));
constexpr auto kPrime = sizeof(mtpPrime);
constexpr auto kDataWithPaddingPrimes = 192 / kPrime;
constexpr auto kMaxSizeInPrimes = 144 / kPrime;
constexpr auto kDataHashPrimes = (SHA256_DIGEST_LENGTH / kPrime);
constexpr auto kKeySize = 32;
constexpr auto kIvSize = 32;
using BoxedPQInnerData = std::conditional_t<
tl::is_boxed_v<PQInnerData>,
PQInnerData,
tl::boxed<PQInnerData>>;
const auto boxed = BoxedPQInnerData(data);
const auto p_q_inner_size = tl::count_length(boxed);
const auto sizeInPrimes = (p_q_inner_size / kPrime);
if (sizeInPrimes > kMaxSizeInPrimes) {
return {};
}
auto dataWithPadding = mtpBuffer();
dataWithPadding.reserve(kDataWithPaddingPrimes);
boxed.write(dataWithPadding);
// data_with_padding := data + random_padding_bytes;
dataWithPadding.resize(kDataWithPaddingPrimes);
const auto dataWithPaddingBytes = bytes::make_span(dataWithPadding);
bytes::set_random(dataWithPaddingBytes.subspan(sizeInPrimes * kPrime));
DEBUG_LOG(("AuthKey Info: starting key generation for pq inner..."));
while (true) {
auto dataWithHash = mtpBuffer();
dataWithHash.reserve(kDataWithPaddingPrimes + kDataHashPrimes);
dataWithHash.append(dataWithPadding);
// data_pad_reversed := BYTE_REVERSE(data_with_padding);
ranges::reverse(bytes::make_span(dataWithHash));
// data_with_hash := data_pad_reversed
// + SHA256(temp_key + data_with_padding);
const auto tempKey = base::RandomValue<bytes::array<kKeySize>>();
dataWithHash.resize(kDataWithPaddingPrimes + kDataHashPrimes);
const auto dataWithHashBytes = bytes::make_span(dataWithHash);
bytes::copy(
dataWithHashBytes.subspan(kDataWithPaddingPrimes * kPrime),
openssl::Sha256(tempKey, bytes::make_span(dataWithPadding)));
auto aesEncrypted = mtpBuffer();
auto keyAesEncrypted = mtpBuffer();
aesEncrypted.resize(dataWithHash.size());
const auto aesEncryptedBytes = bytes::make_span(aesEncrypted);
DEBUG_LOG(("AuthKey Info: encrypting ige for pq inner..."));
// aes_encrypted := AES256_IGE(data_with_hash, temp_key, 0);
const auto tempIv = bytes::array<kIvSize>{ { bytes::type(0) } };
aesIgeEncryptRaw(
dataWithHashBytes.data(),
aesEncryptedBytes.data(),
dataWithHashBytes.size(),
tempKey.data(),
tempIv.data());
DEBUG_LOG(("AuthKey Info: counting hash for pq inner..."));
// temp_key_xor := temp_key XOR SHA256(aes_encrypted);
const auto fullSize = (kKeySize / kPrime) + dataWithHash.size();
keyAesEncrypted.resize(fullSize);
const auto keyAesEncryptedBytes = bytes::make_span(keyAesEncrypted);
const auto aesHash = openssl::Sha256(aesEncryptedBytes);
for (auto i = 0; i != kKeySize; ++i) {
keyAesEncryptedBytes[i] = tempKey[i] ^ aesHash[i];
}
DEBUG_LOG(("AuthKey Info: checking chosen key for pq inner..."));
// key_aes_encrypted := temp_key_xor + aes_encrypted;
bytes::copy(
keyAesEncryptedBytes.subspan(kKeySize),
aesEncryptedBytes);
if (IsGoodEncryptedInner(keyAesEncryptedBytes, key)) {
DEBUG_LOG(("AuthKey Info: chosen key for pq inner is good."));
return key.encrypt(keyAesEncryptedBytes);
}
DEBUG_LOG(("AuthKey Info: chosen key for pq inner is bad..."));
}
}
[[nodiscard]] std::string EncryptClientDHInner(
const MTPClient_DH_Inner_Data &data,
const void *aesKey,
const void *aesIV) {
constexpr auto kSkipPrimes = openssl::kSha1Size / sizeof(mtpPrime);
auto client_dh_inner_size = tl::count_length(data);
auto encSize = (client_dh_inner_size >> 2) + kSkipPrimes;
auto encFullSize = encSize;
if (encSize & 0x03) {
encFullSize += 4 - (encSize & 0x03);
}
auto encBuffer = mtpBuffer();
encBuffer.reserve(encFullSize);
encBuffer.resize(kSkipPrimes);
data.write(encBuffer);
encBuffer.resize(encFullSize);
const auto bytes = bytes::make_span(encBuffer);
const auto hash = openssl::Sha1(bytes.subspan(
kSkipPrimes * sizeof(mtpPrime),
client_dh_inner_size));
bytes::copy(bytes, hash);
bytes::set_random(bytes.subspan(encSize * sizeof(mtpPrime)));
auto sdhEncString = std::string(encFullSize * 4, ' ');
aesIgeEncryptRaw(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), aesKey, aesIV);
return sdhEncString;
}
// 128 lower-order bits of SHA1.
MTPint128 NonceDigest(bytes::const_span data) {
const auto hash = openssl::Sha1(data);
return *(MTPint128*)(hash.data() + 4);
}
} // namespace
DcKeyCreator::Attempt::~Attempt() {
const auto clearBytes = [](bytes::span bytes) {
OPENSSL_cleanse(bytes.data(), bytes.size());
};
OPENSSL_cleanse(&data, sizeof(data));
clearBytes(dhPrime);
clearBytes(g_a);
clearBytes(authKey);
}
DcKeyCreator::DcKeyCreator(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions,
Delegate delegate,
DcKeyRequest request)
: _connection(connection)
, _dcOptions(dcOptions)
, _dcId(dcId)
, _protocolDcId(protocolDcId)
, _request(request)
, _delegate(std::move(delegate)) {
Expects(_request.temporaryExpiresIn > 0);
Expects(_delegate.done != nullptr);
QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
answered();
});
if (_request.persistentNeeded) {
pqSend(&_persistent, 0);
} else {
pqSend(&_temporary, _request.temporaryExpiresIn);
}
}
DcKeyCreator::~DcKeyCreator() {
if (_delegate.done) {
stopReceiving();
}
}
template <typename RequestType>
void DcKeyCreator::sendNotSecureRequest(const RequestType &request) {
auto packet = _connection->prepareNotSecurePacket(
request,
base::unixtime::mtproto_msg_id());
DEBUG_LOG(("AuthKey Info: sending request, size: %1, time: %3"
).arg(packet.size() - 8
).arg(packet[5]));
const auto bytesSize = packet.size() * sizeof(mtpPrime);
_connection->sendData(std::move(packet));
if (_delegate.sentSome) {
_delegate.sentSome(bytesSize);
}
}
template <typename RequestType, typename Response>
std::optional<Response> DcKeyCreator::readNotSecureResponse(
gsl::span<const mtpPrime> answer) {
auto from = answer.data();
auto result = Response();
if (result.read(from, from + answer.size())) {
return result;
}
return std::nullopt;
}
void DcKeyCreator::answered() {
if (_delegate.receivedSome) {
_delegate.receivedSome();
}
if (_connection->received().empty()) {
LOG(("AuthKey Error: "
"trying to read response from empty received list"));
return failed();
}
const auto buffer = std::move(_connection->received().front());
_connection->received().pop_front();
const auto answer = _connection->parseNotSecureResponse(buffer);
if (answer.empty()) {
return failed();
}
handleAnswer(answer);
}
DcKeyCreator::Attempt *DcKeyCreator::attemptByNonce(const MTPint128 &nonce) {
if (_temporary.data.nonce == nonce) {
DEBUG_LOG(("AuthKey Info: receiving answer for temporary..."));
return &_temporary;
} else if (_persistent.data.nonce == nonce) {
DEBUG_LOG(("AuthKey Info: receiving answer for persistent..."));
return &_persistent;
}
LOG(("AuthKey Error: attempt by nonce not found."));
return nullptr;
}
void DcKeyCreator::handleAnswer(gsl::span<const mtpPrime> answer) {
if (const auto resPQ = readNotSecureResponse<MTPReq_pq>(answer)) {
const auto nonce = resPQ->match([](const auto &data) {
return data.vnonce();
});
if (const auto attempt = attemptByNonce(nonce)) {
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
return pqAnswered(attempt, *resPQ);
}
} else if (const auto resDH = readNotSecureResponse<MTPReq_DH_params>(answer)) {
const auto nonce = resDH->match([](const auto &data) {
return data.vnonce();
});
if (const auto attempt = attemptByNonce(nonce)) {
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
return dhParamsAnswered(attempt, *resDH);
}
} else if (const auto result = readNotSecureResponse<MTPSet_client_DH_params>(answer)) {
const auto nonce = result->match([](const auto &data) {
return data.vnonce();
});
if (const auto attempt = attemptByNonce(nonce)) {
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
return dhClientParamsAnswered(attempt, *result);
}
}
LOG(("AuthKey Error: Unknown answer received."));
failed();
}
void DcKeyCreator::pqSend(not_null<Attempt*> attempt, TimeId expiresIn) {
DEBUG_LOG(("AuthKey Info: sending Req_pq for %1..."
).arg(expiresIn ? "temporary" : "persistent"));
attempt->stage = Stage::WaitingPQ;
attempt->expiresIn = expiresIn;
attempt->data.nonce = base::RandomValue<MTPint128>();
sendNotSecureRequest(MTPReq_pq_multi(attempt->data.nonce));
}
void DcKeyCreator::pqAnswered(
not_null<Attempt*> attempt,
const MTPresPQ &data) {
data.match([&](const MTPDresPQ &data) {
Expects(data.vnonce() == attempt->data.nonce);
if (attempt->stage != Stage::WaitingPQ) {
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
return failed();
}
DEBUG_LOG(("AuthKey Info: getting dc RSA key..."));
const auto rsaKey = _dcOptions->getDcRSAKey(
_dcId,
data.vserver_public_key_fingerprints().v);
if (!rsaKey.valid()) {
DEBUG_LOG(("AuthKey Error: unknown public key."));
return failed(DcKeyError::UnknownPublicKey);
}
attempt->data.server_nonce = data.vserver_nonce();
attempt->data.new_nonce = base::RandomValue<MTPint256>();
DEBUG_LOG(("AuthKey Info: parsing pq..."));
const auto &pq = data.vpq().v;
const auto parsed = FactorizePQ(data.vpq().v);
if (parsed.p.isEmpty() || parsed.q.isEmpty()) {
LOG(("AuthKey Error: could not factor pq!"));
DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str()));
return failed();
}
DEBUG_LOG(("AuthKey Info: parse pq done."));
const auto dhEncString = [&] {
return (attempt->expiresIn == 0)
? EncryptPQInnerRSA(
MTP_p_q_inner_data_dc(
data.vpq(),
MTP_bytes(parsed.p),
MTP_bytes(parsed.q),
attempt->data.nonce,
attempt->data.server_nonce,
attempt->data.new_nonce,
MTP_int(_protocolDcId)),
rsaKey)
: EncryptPQInnerRSA(
MTP_p_q_inner_data_temp_dc(
data.vpq(),
MTP_bytes(parsed.p),
MTP_bytes(parsed.q),
attempt->data.nonce,
attempt->data.server_nonce,
attempt->data.new_nonce,
MTP_int(_protocolDcId),
MTP_int(attempt->expiresIn)),
rsaKey);
}();
if (dhEncString.empty()) {
DEBUG_LOG(("AuthKey Error: could not encrypt pq inner."));
return failed();
}
attempt->stage = Stage::WaitingDH;
DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
sendNotSecureRequest(MTPReq_DH_params(
attempt->data.nonce,
attempt->data.server_nonce,
MTP_bytes(parsed.p),
MTP_bytes(parsed.q),
MTP_long(rsaKey.fingerprint()),
MTP_bytes(dhEncString)));
});
}
void DcKeyCreator::dhParamsAnswered(
not_null<Attempt*> attempt,
const MTPserver_DH_Params &data) {
if (attempt->stage != Stage::WaitingDH) {
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
return failed();
}
data.match([&](const MTPDserver_DH_params_ok &data) {
Expects(data.vnonce() == attempt->data.nonce);
if (data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
auto &encDHStr = data.vencrypted_answer().v;
uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2;
if ((encDHLen & 0x03) || encDHBufLen < 6) {
LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen));
DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
return failed();
}
const auto nlen = sizeof(attempt->data.new_nonce);
const auto slen = sizeof(attempt->data.server_nonce);
auto tmp_aes_buffer = bytes::array<1024>();
const auto tmp_aes = bytes::make_span(tmp_aes_buffer);
bytes::copy(tmp_aes, bytes::object_as_span(&attempt->data.new_nonce));
bytes::copy(tmp_aes.subspan(nlen), bytes::object_as_span(&attempt->data.server_nonce));
bytes::copy(tmp_aes.subspan(nlen + slen), bytes::object_as_span(&attempt->data.new_nonce));
bytes::copy(tmp_aes.subspan(nlen + slen + nlen), bytes::object_as_span(&attempt->data.new_nonce));
const auto sha1ns = openssl::Sha1(tmp_aes.subspan(0, nlen + slen));
const auto sha1sn = openssl::Sha1(tmp_aes.subspan(nlen, nlen + slen));
const auto sha1nn = openssl::Sha1(tmp_aes.subspan(nlen + slen, nlen + nlen));
mtpBuffer decBuffer;
decBuffer.resize(encDHBufLen);
const auto aesKey = bytes::make_span(attempt->data.aesKey);
const auto aesIV = bytes::make_span(attempt->data.aesIV);
bytes::copy(aesKey, bytes::make_span(sha1ns).subspan(0, 20));
bytes::copy(aesKey.subspan(20), bytes::make_span(sha1sn).subspan(0, 12));
bytes::copy(aesIV, bytes::make_span(sha1sn).subspan(12, 8));
bytes::copy(aesIV.subspan(8), bytes::make_span(sha1nn).subspan(0, 20));
bytes::copy(aesIV.subspan(28), bytes::object_as_span(&attempt->data.new_nonce).subspan(0, 4));
aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, aesKey.data(), aesIV.data());
const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5));
MTPServer_DH_inner_data dh_inner;
if (!dh_inner.read(to, end)) {
LOG(("AuthKey Error: could not decrypt server_DH_inner_data!"));
return failed();
}
const auto &dh_inner_data(dh_inner.c_server_DH_inner_data());
if (dh_inner_data.vnonce() != attempt->data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
return failed();
}
if (dh_inner_data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
const auto sha1Buffer = openssl::Sha1(
bytes::make_span(decBuffer).subspan(
5 * sizeof(mtpPrime),
(to - from) * sizeof(mtpPrime)));
const auto sha1Dec = bytes::make_span(decBuffer).subspan(
0,
openssl::kSha1Size);
if (bytes::compare(sha1Dec, sha1Buffer)) {
LOG(("AuthKey Error: sha1 hash of encrypted part did not match!"));
DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&attempt->data.server_nonce, 16).str(), Logs::mb(&attempt->data.new_nonce, 16).str(), Logs::mb(encDHStr.constData(), encDHLen).str()));
return failed();
}
base::unixtime::update(dh_inner_data.vserver_time().v);
// check that dhPrime and (dhPrime - 1) / 2 are really prime
if (!IsPrimeAndGood(bytes::make_span(dh_inner_data.vdh_prime().v), dh_inner_data.vg().v)) {
LOG(("AuthKey Error: bad dh_prime primality!"));
return failed();
}
attempt->dhPrime = bytes::make_vector(
dh_inner_data.vdh_prime().v);
attempt->data.g = dh_inner_data.vg().v;
attempt->g_a = bytes::make_vector(dh_inner_data.vg_a().v);
attempt->data.retry_id = MTP_long(0);
attempt->retries = 0;
dhClientParamsSend(attempt);
}, [&](const MTPDserver_DH_params_fail &data) {
Expects(data.vnonce() == attempt->data.nonce);
if (data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
if (data.vnew_nonce_hash() != NonceDigest(bytes::object_as_span(&attempt->data.new_nonce))) {
LOG(("AuthKey Error: received new_nonce_hash did not match!"));
DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&data.vnew_nonce_hash(), 16).str(), Logs::mb(&attempt->data.new_nonce, 32).str()));
return failed();
}
LOG(("AuthKey Error: server_DH_params_fail received!"));
failed();
});
}
void DcKeyCreator::dhClientParamsSend(not_null<Attempt*> attempt) {
if (++attempt->retries > 5) {
LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(attempt->retries - 1));
return failed();
}
// gen rand 'b'
auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
bytes::set_random(randomSeed);
auto g_b_data = CreateModExp(attempt->data.g, attempt->dhPrime, randomSeed);
if (g_b_data.modexp.empty()) {
LOG(("AuthKey Error: could not generate good g_b."));
return failed();
}
auto computedAuthKey = CreateAuthKey(attempt->g_a, g_b_data.randomPower, attempt->dhPrime);
if (computedAuthKey.empty()) {
LOG(("AuthKey Error: could not generate auth_key."));
return failed();
}
AuthKey::FillData(attempt->authKey, computedAuthKey);
auto auth_key_sha = openssl::Sha1(attempt->authKey);
memcpy(&attempt->data.auth_key_aux_hash.v, auth_key_sha.data(), 8);
memcpy(&attempt->data.auth_key_hash.v, auth_key_sha.data() + 12, 8);
const auto client_dh_inner = MTP_client_DH_inner_data(
attempt->data.nonce,
attempt->data.server_nonce,
attempt->data.retry_id,
MTP_bytes(g_b_data.modexp));
auto sdhEncString = EncryptClientDHInner(
client_dh_inner,
attempt->data.aesKey.data(),
attempt->data.aesIV.data());
attempt->stage = Stage::WaitingDone;
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
sendNotSecureRequest(MTPSet_client_DH_params(
attempt->data.nonce,
attempt->data.server_nonce,
MTP_string(std::move(sdhEncString))));
}
void DcKeyCreator::dhClientParamsAnswered(
not_null<Attempt*> attempt,
const MTPset_client_DH_params_answer &data) {
if (attempt->stage != Stage::WaitingDone) {
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
return failed();
}
data.match([&](const MTPDdh_gen_ok &data) {
if (data.vnonce() != attempt->data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
return failed();
}
if (data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
attempt->data.new_nonce_buf[32] = bytes::type(1);
if (data.vnew_nonce_hash1() != NonceDigest(attempt->data.new_nonce_buf)) {
LOG(("AuthKey Error: received new_nonce_hash1 did not match!"));
DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash1(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
return failed();
}
uint64 salt1 = attempt->data.new_nonce.l.l, salt2 = attempt->data.server_nonce.l;
attempt->data.doneSalt = salt1 ^ salt2;
attempt->stage = Stage::Ready;
done();
}, [&](const MTPDdh_gen_retry &data) {
if (data.vnonce() != attempt->data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
return failed();
}
if (data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
attempt->data.new_nonce_buf[32] = bytes::type(2);
if (data.vnew_nonce_hash2() != NonceDigest(attempt->data.new_nonce_buf)) {
LOG(("AuthKey Error: received new_nonce_hash2 did not match!"));
DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash2(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
return failed();
}
attempt->data.retry_id = attempt->data.auth_key_aux_hash;
dhClientParamsSend(attempt);
}, [&](const MTPDdh_gen_fail &data) {
if (data.vnonce() != attempt->data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
return failed();
}
if (data.vserver_nonce() != attempt->data.server_nonce) {
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!"));
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
return failed();
}
attempt->data.new_nonce_buf[32] = bytes::type(3);
if (data.vnew_nonce_hash3() != NonceDigest(attempt->data.new_nonce_buf)) {
LOG(("AuthKey Error: received new_nonce_hash3 did not match!"));
DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash3(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
return failed();
}
LOG(("AuthKey Error: dh_gen_fail received!"));
failed();
});
}
void DcKeyCreator::failed(DcKeyError error) {
stopReceiving();
auto onstack = base::take(_delegate.done);
onstack(tl::unexpected(error));
}
void DcKeyCreator::done() {
if (_temporary.stage == Stage::None) {
pqSend(&_temporary, _request.temporaryExpiresIn);
return;
}
Assert(_temporary.stage == Stage::Ready);
Assert(_persistent.stage == Stage::Ready || _persistent.stage == Stage::None);
auto result = DcKeyResult();
result.temporaryKey = std::make_shared<AuthKey>(
AuthKey::Type::Temporary,
_dcId,
_temporary.authKey);
result.temporaryServerSalt = _temporary.data.doneSalt;
if (_persistent.stage == Stage::Ready) {
result.persistentKey = std::make_shared<AuthKey>(
AuthKey::Type::Generated,
_dcId,
_persistent.authKey);
result.persistentServerSalt = _persistent.data.doneSalt;
}
stopReceiving();
auto onstack = base::take(_delegate.done);
onstack(std::move(result));
}
void DcKeyCreator::stopReceiving() {
QObject::disconnect(
_connection,
&AbstractConnection::receivedData,
nullptr,
nullptr);
}
} // namespace MTP::details

View File

@@ -0,0 +1,139 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/core_types.h"
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/connection_abstract.h"
#include "base/basic_types.h"
#include "base/expected.h"
namespace MTP {
class DcOptions;
} // namespace MTP
namespace MTP::details {
struct DcKeyRequest {
TimeId temporaryExpiresIn = 0;
bool persistentNeeded = false;
};
enum class DcKeyError {
UnknownPublicKey,
Other,
};
struct DcKeyResult {
AuthKeyPtr persistentKey;
AuthKeyPtr temporaryKey;
uint64 temporaryServerSalt = 0;
uint64 persistentServerSalt = 0;
};
class DcKeyCreator final {
public:
struct Delegate {
Fn<void(base::expected<DcKeyResult, DcKeyError>)> done;
Fn<void(uint64)> sentSome;
Fn<void()> receivedSome;
};
DcKeyCreator(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions,
Delegate delegate,
DcKeyRequest request);
~DcKeyCreator();
private:
enum class Stage {
None,
WaitingPQ,
WaitingDH,
WaitingDone,
Ready,
};
struct Data {
Data()
: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf.data()))
, auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf.data() + 33)) {
}
MTPint128 nonce, server_nonce;
// 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash.
bytes::array<41> new_nonce_buf{};
MTPint256 &new_nonce;
MTPlong &auth_key_aux_hash;
MTPlong retry_id;
int32 g = 0;
bytes::array<32> aesKey;
bytes::array<32> aesIV;
MTPlong auth_key_hash;
uint64 doneSalt = 0;
};
struct Attempt {
~Attempt();
Data data;
bytes::vector dhPrime;
bytes::vector g_a;
AuthKey::Data authKey = { { gsl::byte{} } };
TimeId expiresIn = 0;
uint32 retries = 0;
Stage stage = Stage::None;
};
template <typename RequestType>
void sendNotSecureRequest(const RequestType &request);
template <
typename RequestType,
typename Response = typename RequestType::ResponseType>
[[nodiscard]] std::optional<Response> readNotSecureResponse(
gsl::span<const mtpPrime> answer);
Attempt *attemptByNonce(const MTPint128 &nonce);
void answered();
void handleAnswer(gsl::span<const mtpPrime> answer);
void pqSend(not_null<Attempt*> attempt, TimeId expiresIn);
void pqAnswered(
not_null<Attempt*> attempt,
const MTPresPQ &data);
void dhParamsAnswered(
not_null<Attempt*> attempt,
const MTPserver_DH_Params &data);
void dhClientParamsSend(not_null<Attempt*> attempt);
void dhClientParamsAnswered(
not_null<Attempt*> attempt,
const MTPset_client_DH_params_answer &data);
void stopReceiving();
void failed(DcKeyError error = DcKeyError::Other);
void done();
const not_null<AbstractConnection*> _connection;
const not_null<DcOptions*> _dcOptions;
const DcId _dcId = 0;
const int16 _protocolDcId = 0;
const DcKeyRequest _request;
Delegate _delegate;
Attempt _temporary;
Attempt _persistent;
};
} // namespace MTP::details

View File

@@ -0,0 +1,166 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_dcenter.h"
#include "mtproto/facade.h"
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/mtproto_dc_options.h"
#include "mtproto/mtp_instance.h"
#include "mtproto/special_config_request.h"
namespace MTP {
namespace details {
namespace {
int IndexByType(TemporaryKeyType type) {
switch (type) {
case TemporaryKeyType::Regular: return 0;
case TemporaryKeyType::MediaCluster: return 1;
}
Unexpected("Type value in IndexByType.");
}
int IndexByType(CreatingKeyType type) {
switch (type) {
case CreatingKeyType::Persistent:
case CreatingKeyType::TemporaryRegular: return 0;
case CreatingKeyType::TemporaryMediaCluster: return 1;
}
Unexpected("Creating type value in IndexByType.");
}
const char *NameOfType(CreatingKeyType type) {
switch (type) {
case CreatingKeyType::Persistent: return "persistent";
case CreatingKeyType::TemporaryRegular: return "regular";
case CreatingKeyType::TemporaryMediaCluster: return "media";
}
Unexpected("Type value in NameOfType.");
}
} // namespace
TemporaryKeyType TemporaryKeyTypeByDcType(DcType type) {
return (type == DcType::MediaCluster)
? TemporaryKeyType::MediaCluster
: TemporaryKeyType::Regular;
}
Dcenter::Dcenter(DcId dcId, AuthKeyPtr &&key)
: _id(dcId)
, _persistentKey(std::move(key)) {
}
DcId Dcenter::id() const {
return _id;
}
AuthKeyPtr Dcenter::getTemporaryKey(TemporaryKeyType type) const {
QReadLocker lock(&_mutex);
return _temporaryKeys[IndexByType(type)];
}
AuthKeyPtr Dcenter::getPersistentKey() const {
QReadLocker lock(&_mutex);
return _persistentKey;
}
bool Dcenter::destroyTemporaryKey(uint64 keyId) {
QWriteLocker lock(&_mutex);
for (auto &key : _temporaryKeys) {
if (key && key->keyId() == keyId) {
key = nullptr;
_connectionInited = false;
return true;
}
}
return false;
}
bool Dcenter::destroyConfirmedForgottenKey(uint64 keyId) {
QWriteLocker lock(&_mutex);
if (!_persistentKey || _persistentKey->keyId() != keyId) {
return false;
}
for (auto &key : _temporaryKeys) {
key = nullptr;
}
_persistentKey = nullptr;
_connectionInited = false;
return true;
}
bool Dcenter::connectionInited() const {
QReadLocker lock(&_mutex);
return _connectionInited;
}
void Dcenter::setConnectionInited(bool connectionInited) {
QWriteLocker lock(&_mutex);
_connectionInited = connectionInited;
}
CreatingKeyType Dcenter::acquireKeyCreation(DcType type) {
QReadLocker lock(&_mutex);
const auto keyType = TemporaryKeyTypeByDcType(type);
const auto index = IndexByType(keyType);
auto &key = _temporaryKeys[index];
if (key != nullptr) {
return CreatingKeyType::None;
}
auto expected = false;
const auto regular = IndexByType(TemporaryKeyType::Regular);
if (keyType == TemporaryKeyType::MediaCluster && _temporaryKeys[regular]) {
return !_creatingKeys[index].compare_exchange_strong(expected, true)
? CreatingKeyType::None
: CreatingKeyType::TemporaryMediaCluster;
}
return !_creatingKeys[regular].compare_exchange_strong(expected, true)
? CreatingKeyType::None
: (type != DcType::Cdn && !_persistentKey)
? CreatingKeyType::Persistent
: CreatingKeyType::TemporaryRegular;
}
bool Dcenter::releaseKeyCreationOnDone(
CreatingKeyType type,
const AuthKeyPtr &temporaryKey,
const AuthKeyPtr &persistentKeyUsedForBind) {
Expects(_creatingKeys[IndexByType(type)]);
Expects(_temporaryKeys[IndexByType(type)] == nullptr);
Expects(temporaryKey != nullptr);
QWriteLocker lock(&_mutex);
if (type == CreatingKeyType::Persistent) {
_persistentKey = persistentKeyUsedForBind;
} else if (_persistentKey != persistentKeyUsedForBind) {
return false;
}
_temporaryKeys[IndexByType(type)] = temporaryKey;
_creatingKeys[IndexByType(type)] = false;
_connectionInited = false;
DEBUG_LOG(("AuthKey Info: Dcenter::releaseKeyCreationOnDone(%1, %2, %3)."
).arg(NameOfType(type)
).arg(temporaryKey ? temporaryKey->keyId() : 0
).arg(persistentKeyUsedForBind
? persistentKeyUsedForBind->keyId()
: 0));
return true;
}
void Dcenter::releaseKeyCreationOnFail(CreatingKeyType type) {
Expects(_creatingKeys[IndexByType(type)]);
Expects(_temporaryKeys[IndexByType(type)] == nullptr);
_creatingKeys[IndexByType(type)] = false;
}
} // namespace details
} // namespace MTP

View File

@@ -0,0 +1,70 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include <QtCore/QReadWriteLock>
namespace MTP {
class Instance;
class AuthKey;
using AuthKeyPtr = std::shared_ptr<AuthKey>;
enum class DcType;
namespace details {
enum class TemporaryKeyType {
Regular,
MediaCluster
};
enum class CreatingKeyType {
None,
Persistent,
TemporaryRegular,
TemporaryMediaCluster
};
[[nodiscard]] TemporaryKeyType TemporaryKeyTypeByDcType(DcType type);
class Dcenter : public QObject {
public:
// Main thread.
Dcenter(DcId dcId, AuthKeyPtr &&key);
// Thread-safe.
[[nodiscard]] DcId id() const;
[[nodiscard]] AuthKeyPtr getPersistentKey() const;
[[nodiscard]] AuthKeyPtr getTemporaryKey(TemporaryKeyType type) const;
[[nodiscard]] CreatingKeyType acquireKeyCreation(DcType type);
bool releaseKeyCreationOnDone(
CreatingKeyType type,
const AuthKeyPtr &temporaryKey,
const AuthKeyPtr &persistentKeyUsedForBind);
void releaseKeyCreationOnFail(CreatingKeyType type);
bool destroyTemporaryKey(uint64 keyId);
bool destroyConfirmedForgottenKey(uint64 keyId);
[[nodiscard]] bool connectionInited() const;
void setConnectionInited(bool connectionInited = true);
private:
static constexpr auto kTemporaryKeysCount = 2;
const DcId _id = 0;
mutable QReadWriteLock _mutex;
AuthKeyPtr _temporaryKeys[kTemporaryKeysCount];
AuthKeyPtr _persistentKey;
bool _connectionInited = false;
std::atomic<bool> _creatingKeys[kTemporaryKeysCount] = { false };
};
} // namespace details
} // namespace MTP

View File

@@ -0,0 +1,367 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_domain_resolver.h"
#include "base/random.h"
#include "base/invoke_queued.h"
#include "base/call_delayed.h"
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <range/v3/algorithm/shuffle.hpp>
#include <range/v3/algorithm/reverse.hpp>
#include <range/v3/algorithm/remove.hpp>
#include <random>
namespace MTP::details {
namespace {
constexpr auto kSendNextTimeout = crl::time(800);
constexpr auto kMinTimeToLive = 10 * crl::time(1000);
constexpr auto kMaxTimeToLive = 300 * crl::time(1000);
} // namespace
const std::vector<QString> &DnsDomains() {
static const auto kResult = std::vector<QString>{
"google.com",
"www.google.com",
"google.ru",
"www.google.ru",
};
return kResult;
}
QString GenerateDnsRandomPadding() {
constexpr char kValid[] = "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
auto result = QString();
const auto count = [&] {
constexpr auto kMinPadding = 13;
constexpr auto kMaxPadding = 128;
while (true) {
const auto result = 1 + (base::RandomValue<uchar>() / 2);
Assert(result <= kMaxPadding);
if (result >= kMinPadding) {
return result;
}
}
}();
result.resize(count);
for (auto &ch : result) {
ch = kValid[base::RandomValue<uchar>() % (sizeof(kValid) - 1)];
}
return result;
}
QByteArray DnsUserAgent() {
static const auto kResult = QByteArray(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/142.0.0.0 Safari/537.36");
return kResult;
}
std::vector<DnsEntry> ParseDnsResponse(
const QByteArray &bytes,
std::optional<int> typeRestriction) {
if (bytes.isEmpty()) {
return {};
}
// Read and store to "result" all the data bytes from the response:
// { ..,
// "Answer": [
// { .., "data": "bytes1", "TTL": int, .. },
// { .., "data": "bytes2", "TTL": int, .. }
// ],
// .. }
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(bytes, &error);
if (error.error != QJsonParseError::NoError) {
LOG(("Config Error: Failed to parse dns response JSON, error: %1"
).arg(error.errorString()));
return {};
} else if (!document.isObject()) {
LOG(("Config Error: Not an object received in dns response JSON."));
return {};
}
const auto response = document.object();
const auto answerIt = response.find("Answer");
if (answerIt == response.constEnd()) {
LOG(("Config Error: Could not find Answer in dns response JSON."));
return {};
} else if (!(*answerIt).isArray()) {
LOG(("Config Error: Not an array received "
"in Answer in dns response JSON."));
return {};
}
const auto array = (*answerIt).toArray();
auto result = std::vector<DnsEntry>();
for (const auto elem : array) {
if (!elem.isObject()) {
LOG(("Config Error: Not an object found "
"in Answer array in dns response JSON."));
continue;
}
const auto object = elem.toObject();
if (typeRestriction) {
const auto typeIt = object.find("type");
const auto type = int(base::SafeRound((*typeIt).toDouble()));
if (!(*typeIt).isDouble()) {
LOG(("Config Error: Not a number in type field "
"in Answer array in dns response JSON."));
continue;
} else if (type != *typeRestriction) {
continue;
}
}
const auto dataIt = object.find("data");
if (dataIt == object.constEnd()) {
LOG(("Config Error: Could not find data "
"in Answer array entry in dns response JSON."));
continue;
} else if (!(*dataIt).isString()) {
LOG(("Config Error: Not a string data found "
"in Answer array entry in dns response JSON."));
continue;
}
const auto ttlIt = object.find("TTL");
const auto ttl = (ttlIt != object.constEnd())
? crl::time(base::SafeRound((*ttlIt).toDouble()))
: crl::time(0);
result.push_back({ (*dataIt).toString(), ttl });
}
return result;
}
ServiceWebRequest::ServiceWebRequest(not_null<QNetworkReply*> reply)
: reply(reply.get()) {
}
ServiceWebRequest::ServiceWebRequest(ServiceWebRequest &&other)
: reply(base::take(other.reply)) {
}
ServiceWebRequest &ServiceWebRequest::operator=(ServiceWebRequest &&other) {
if (reply != other.reply) {
destroy();
reply = base::take(other.reply);
}
return *this;
}
void ServiceWebRequest::destroy() {
if (const auto value = base::take(reply)) {
value->disconnect(
value,
&QNetworkReply::finished,
nullptr,
nullptr);
value->abort();
value->deleteLater();
}
}
ServiceWebRequest::~ServiceWebRequest() {
if (reply) {
reply->deleteLater();
}
}
DomainResolver::DomainResolver(Fn<void(
const QString &host,
const QStringList &ips,
crl::time expireAt)> callback)
: _callback(std::move(callback)) {
_manager.setProxy(QNetworkProxy::NoProxy);
}
void DomainResolver::resolve(const QString &domain) {
resolve({ domain, false });
resolve({ domain, true });
}
void DomainResolver::resolve(const AttemptKey &key) {
if (_attempts.find(key) != end(_attempts)) {
return;
} else if (_requests.find(key) != end(_requests)) {
return;
}
const auto i = _cache.find(key);
_lastTimestamp = crl::now();
if (i != end(_cache) && i->second.expireAt > _lastTimestamp) {
checkExpireAndPushResult(key.domain);
return;
}
auto attempts = std::vector<Attempt>();
auto domains = DnsDomains();
std::random_device rd;
ranges::shuffle(domains, std::mt19937(rd()));
const auto takeDomain = [&] {
const auto result = domains.back();
domains.pop_back();
return result;
};
const auto shuffle = [&](int from, int till) {
Expects(till > from);
ranges::shuffle(
begin(attempts) + from,
begin(attempts) + till,
std::mt19937(rd()));
};
attempts.push_back({ Type::Google, "dns.google.com" });
attempts.push_back({ Type::Google, takeDomain(), "dns" });
attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
while (!domains.empty()) {
attempts.push_back({ Type::Google, takeDomain(), "dns" });
}
shuffle(0, 2);
ranges::reverse(attempts); // We go from last to first.
_attempts.emplace(key, Attempts{ std::move(attempts) });
sendNextRequest(key);
}
void DomainResolver::checkExpireAndPushResult(const QString &domain) {
const auto ipv4 = _cache.find({ domain, false });
if (ipv4 == end(_cache) || ipv4->second.expireAt <= _lastTimestamp) {
return;
}
auto result = ipv4->second;
const auto ipv6 = _cache.find({ domain, true });
if (ipv6 != end(_cache) && ipv6->second.expireAt > _lastTimestamp) {
result.ips.append(ipv6->second.ips);
accumulate_min(result.expireAt, ipv6->second.expireAt);
}
InvokeQueued(this, [=] {
_callback(domain, result.ips, result.expireAt);
});
}
void DomainResolver::sendNextRequest(const AttemptKey &key) {
auto i = _attempts.find(key);
if (i == end(_attempts)) {
return;
}
auto &attempts = i->second;
auto &list = attempts.list;
const auto attempt = list.back();
list.pop_back();
if (!list.empty()) {
base::call_delayed(kSendNextTimeout, &attempts.guard, [=] {
sendNextRequest(key);
});
}
performRequest(key, attempt);
}
void DomainResolver::performRequest(
const AttemptKey &key,
const Attempt &attempt) {
auto url = QUrl();
url.setScheme("https");
auto request = QNetworkRequest();
switch (attempt.type) {
case Type::Mozilla: {
url.setHost(attempt.data);
url.setPath("/dns-query");
url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
).arg(key.domain
).arg(key.ipv6 ? 28 : 1
).arg(GenerateDnsRandomPadding()));
request.setRawHeader("accept", "application/dns-json");
} break;
case Type::Google: {
url.setHost(attempt.data);
url.setPath("/resolve");
url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
).arg(key.domain
).arg(key.ipv6 ? 28 : 1
).arg(GenerateDnsRandomPadding()));
if (!attempt.host.isEmpty()) {
const auto host = attempt.host + ".google.com";
request.setRawHeader("Host", host.toLatin1());
}
} break;
default: Unexpected("Type in DomainResolver::performRequest.");
}
request.setUrl(url);
request.setRawHeader("User-Agent", DnsUserAgent());
const auto i = _requests.emplace(
key,
std::vector<ServiceWebRequest>()).first;
const auto reply = i->second.emplace_back(
_manager.get(request)
).reply;
connect(reply, &QNetworkReply::finished, this, [=] {
requestFinished(key, reply);
});
}
void DomainResolver::requestFinished(
const AttemptKey &key,
not_null<QNetworkReply*> reply) {
const auto result = finalizeRequest(key, reply);
const auto response = ParseDnsResponse(result);
if (response.empty()) {
return;
}
_requests.erase(key);
_attempts.erase(key);
auto entry = CacheEntry();
auto ttl = kMaxTimeToLive;
for (const auto &item : response) {
entry.ips.push_back(item.data);
ttl = std::min(
ttl,
std::max(item.TTL * crl::time(1000), kMinTimeToLive));
}
_lastTimestamp = crl::now();
entry.expireAt = _lastTimestamp + ttl;
_cache[key] = std::move(entry);
checkExpireAndPushResult(key.domain);
}
QByteArray DomainResolver::finalizeRequest(
const AttemptKey &key,
not_null<QNetworkReply*> reply) {
if (reply->error() != QNetworkReply::NoError) {
DEBUG_LOG(("Resolve Error: Failed to get response, error: %2 (%3)"
).arg(reply->errorString()
).arg(reply->error()));
}
const auto result = reply->readAll();
const auto i = _requests.find(key);
if (i != end(_requests)) {
auto &requests = i->second;
const auto from = ranges::remove(
requests,
reply,
[](const ServiceWebRequest &request) { return request.reply; });
requests.erase(from, end(requests));
if (requests.empty()) {
_requests.erase(i);
}
}
return result;
}
} // namespace MTP::details

View File

@@ -0,0 +1,106 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/weak_ptr.h"
#include <QtCore/QPointer>
#include <QtNetwork/QNetworkReply>
#include <optional>
namespace MTP::details {
[[nodiscard]] const std::vector<QString> &DnsDomains();
[[nodiscard]] QString GenerateDnsRandomPadding();
[[nodiscard]] QByteArray DnsUserAgent();
struct DnsEntry {
QString data;
crl::time TTL = 0;
};
[[nodiscard]] std::vector<DnsEntry> ParseDnsResponse(
const QByteArray &bytes,
std::optional<int> typeRestriction = std::nullopt);
struct ServiceWebRequest {
ServiceWebRequest(not_null<QNetworkReply*> reply);
ServiceWebRequest(ServiceWebRequest &&other);
ServiceWebRequest &operator=(ServiceWebRequest &&other);
~ServiceWebRequest();
void destroy();
QPointer<QNetworkReply> reply;
};
class DomainResolver : public QObject {
public:
DomainResolver(Fn<void(
const QString &domain,
const QStringList &ips,
crl::time expireAt)> callback);
void resolve(const QString &domain);
private:
enum class Type {
Mozilla,
Google,
};
struct Attempt {
Type type;
QString data;
QString host;
};
struct AttemptKey {
QString domain;
bool ipv6 = false;
inline bool operator<(const AttemptKey &other) const {
return (domain < other.domain)
|| (domain == other.domain && !ipv6 && other.ipv6);
}
inline bool operator==(const AttemptKey &other) const {
return (domain == other.domain) && (ipv6 == other.ipv6);
}
};
struct CacheEntry {
QStringList ips;
crl::time expireAt = 0;
};
struct Attempts {
std::vector<Attempt> list;
base::has_weak_ptr guard;
};
void resolve(const AttemptKey &key);
void sendNextRequest(const AttemptKey &key);
void performRequest(const AttemptKey &key, const Attempt &attempt);
void checkExpireAndPushResult(const QString &domain);
void requestFinished(
const AttemptKey &key,
not_null<QNetworkReply*> reply);
QByteArray finalizeRequest(
const AttemptKey &key,
not_null<QNetworkReply*> reply);
Fn<void(
const QString &domain,
const QStringList &ips,
crl::time expireAt)> _callback;
QNetworkAccessManager _manager;
std::map<AttemptKey, Attempts> _attempts;
std::map<AttemptKey, std::vector<ServiceWebRequest>> _requests;
std::map<AttemptKey, CacheEntry> _cache;
crl::time _lastTimestamp = 0;
};
} // namespace MTP::details

View File

@@ -0,0 +1,169 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_dump_to_text.h"
#include "scheme-dump_to_text.h"
#include "scheme.h"
#include <zlib.h>
namespace MTP::details {
bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) {
switch (mtpTypeId(cons)) {
case mtpc_int: {
MTPint value;
if (value.read(from, end, cons)) {
to.add(QString::number(value.v)).add(" [INT]");
return true;
}
} break;
case mtpc_long: {
MTPlong value;
if (value.read(from, end, cons)) {
to.add(QString::number(value.v)).add(" [LONG]");
return true;
}
} break;
case mtpc_int128: {
MTPint128 value;
if (value.read(from, end, cons)) {
to.add(QString::number(value.h)).add(" * 2^64 + ").add(QString::number(value.l)).add(" [INT128]");
return true;
}
} break;
case mtpc_int256: {
MTPint256 value;
if (value.read(from, end, cons)) {
to.add(QString::number(value.h.h)).add(" * 2^192 + ").add(QString::number(value.h.l)).add(" * 2^128 + ").add(QString::number(value.l.h)).add(" * 2 ^ 64 + ").add(QString::number(value.l.l)).add(" [INT256]");
return true;
}
} break;
case mtpc_double: {
MTPdouble value;
if (value.read(from, end, cons)) {
to.add(QString::number(value.v)).add(" [DOUBLE]");
return true;
}
} break;
case mtpc_string: {
MTPstring value;
if (value.read(from, end, cons)) {
auto strUtf8 = value.v;
auto str = QString::fromUtf8(strUtf8);
if (str.toUtf8() == strUtf8) {
to.add("\"").add(str.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")).add("\" [STRING]");
} else if (strUtf8.size() < 64) {
to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(QString::number(strUtf8.size())).add(" BYTES]");
} else {
to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(QString::number(strUtf8.size())).add(" BYTES]");
}
return true;
}
} break;
case mtpc_vector: {
if (from < end) {
int32 cnt = *(from++);
to.add("[ vector<0x").add(QString::number(vcons, 16)).add("> (").add(QString::number(cnt)).add(")");
if (cnt) {
to.add("\n").addSpaces(level);
for (int32 i = 0; i < cnt; ++i) {
to.add(" ");
if (!DumpToTextType(to, from, end, vcons, level + 1)) {
return false;
}
to.add(",\n").addSpaces(level);
}
} else {
to.add(" ");
}
to.add("]");
return true;
}
} break;
case mtpc_gzip_packed: {
MTPstring packed;
// read packed string as serialized mtp string type
if (!packed.read(from, end)) {
return false;
}
uint32 packedLen = packed.v.size(), unpackedChunk = packedLen;
mtpBuffer result; // * 4 because of mtpPrime type
result.resize(0);
z_stream stream;
stream.zalloc = nullptr;
stream.zfree = nullptr;
stream.opaque = nullptr;
stream.avail_in = 0;
stream.next_in = nullptr;
int res = inflateInit2(&stream, 16 + MAX_WBITS);
if (res != Z_OK) {
return false;
}
stream.avail_in = packedLen;
stream.next_in = reinterpret_cast<Bytef*>(packed.v.data());
stream.avail_out = 0;
while (!stream.avail_out) {
result.resize(result.size() + unpackedChunk);
stream.avail_out = unpackedChunk * sizeof(mtpPrime);
stream.next_out = (Bytef*)&result[result.size() - unpackedChunk];
int res = inflate(&stream, Z_NO_FLUSH);
if (res != Z_OK && res != Z_STREAM_END) {
inflateEnd(&stream);
return false;
}
}
if (stream.avail_out & 0x03) {
return false;
}
result.resize(result.size() - (stream.avail_out >> 2));
inflateEnd(&stream);
if (result.empty()) {
return false;
}
const mtpPrime *newFrom = result.constData(), *newEnd = result.constData() + result.size();
to.add("[GZIPPED] ");
return DumpToTextType(to, newFrom, newEnd, 0, level);
} break;
default: {
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
if (cons == mtpLayers[i]) {
to.add("[LAYER").add(QString::number(i + 1)).add("] ");
return DumpToTextType(to, from, end, 0, level);
}
}
if (cons == mtpc_invokeWithLayer) {
if (from >= end) {
return false;
}
int32 layer = *(from++);
to.add("[LAYER").add(QString::number(layer)).add("] ");
return DumpToTextType(to, from, end, 0, level);
}
} break;
}
return false;
}
QString DumpToText(const mtpPrime *&from, const mtpPrime *end) {
DumpToTextBuffer to;
[[maybe_unused]] bool result = DumpToTextType(to, from, end, mtpc_core_message);
return QString::fromUtf8(to.p, to.size);
}
} // namespace MTP::details

View File

@@ -0,0 +1,82 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/core_types.h"
#include "mtproto/details/mtproto_serialized_request.h"
namespace MTP::details {
// Human-readable text serialization
QString DumpToText(const mtpPrime *&from, const mtpPrime *end);
struct DumpToTextBuffer {
static constexpr auto kBufferSize = 1024 * 1024; // 1 mb start size
DumpToTextBuffer()
: p(new char[kBufferSize])
, alloced(kBufferSize) {
}
~DumpToTextBuffer() {
delete[] p;
}
DumpToTextBuffer &add(const QString &data) {
auto d = data.toUtf8();
return add(d.constData(), d.size());
}
DumpToTextBuffer &add(const char *data, int32 len = -1) {
if (len < 0) len = strlen(data);
if (!len) return (*this);
ensureLength(len);
memcpy(p + size, data, len);
size += len;
return (*this);
}
DumpToTextBuffer &addSpaces(int32 level) {
int32 len = level * 2;
if (!len) return (*this);
ensureLength(len);
for (char *ptr = p + size, *end = ptr + len; ptr != end; ++ptr) {
*ptr = ' ';
}
size += len;
return (*this);
}
DumpToTextBuffer &error(const char *problem = "could not decode type") {
return add("[ERROR] (").add(problem).add(")");
}
void ensureLength(int32 add) {
if (size + add <= alloced) return;
int32 newsize = size + add;
if (newsize % kBufferSize) {
newsize += kBufferSize - (newsize % kBufferSize);
}
char *b = new char[newsize];
memcpy(b, p, size);
alloced = newsize;
delete[] p;
p = b;
}
char *p = nullptr;
int size = 0;
int alloced = 0;
};
[[nodiscard]] bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons = 0);
} // namespace MTP::details

View File

@@ -0,0 +1,55 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_received_ids_manager.h"
namespace MTP::details {
ReceivedIdsManager::Result ReceivedIdsManager::registerMsgId(
mtpMsgId msgId,
bool needAck) {
const auto i = _idsNeedAck.find(msgId);
if (i != _idsNeedAck.end()) {
MTP_LOG(-1, ("No need to handle - %1 already is in map").arg(msgId));
return Result::Duplicate;
} else if (_idsNeedAck.size() < kIdsBufferSize || msgId > min()) {
_idsNeedAck.emplace(msgId, needAck);
return Result::Success;
}
MTP_LOG(-1, ("Reset on too old - %1 < min = %2").arg(msgId).arg(min()));
return Result::TooOld;
}
mtpMsgId ReceivedIdsManager::min() const {
return _idsNeedAck.empty() ? 0 : _idsNeedAck.begin()->first;
}
mtpMsgId ReceivedIdsManager::max() const {
auto end = _idsNeedAck.end();
return _idsNeedAck.empty() ? 0 : (--end)->first;
}
ReceivedIdsManager::State ReceivedIdsManager::lookup(mtpMsgId msgId) const {
auto i = _idsNeedAck.find(msgId);
if (i == _idsNeedAck.end()) {
return State::NotFound;
}
return i->second ? State::NeedsAck : State::NoAckNeeded;
}
void ReceivedIdsManager::shrink() {
auto size = _idsNeedAck.size();
while (size-- > kIdsBufferSize) {
_idsNeedAck.erase(_idsNeedAck.begin());
}
}
void ReceivedIdsManager::clear() {
_idsNeedAck.clear();
}
} // namespace MTP::details

View File

@@ -0,0 +1,43 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/flat_map.h"
namespace MTP::details {
// Received msgIds and wereAcked msgIds count stored.
inline constexpr auto kIdsBufferSize = 400;
class ReceivedIdsManager final {
public:
enum class State {
NotFound,
NeedsAck,
NoAckNeeded,
};
enum class Result {
Success,
Duplicate,
TooOld,
};
[[nodiscard]] Result registerMsgId(mtpMsgId msgId, bool needAck);
[[nodiscard]] mtpMsgId min() const;
[[nodiscard]] mtpMsgId max() const;
[[nodiscard]] State lookup(mtpMsgId msgId) const;
void shrink();
void clear();
private:
base::flat_map<mtpMsgId, bool> _idsNeedAck;
};
} // namespace MTP::details

View File

@@ -0,0 +1,266 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_rsa_public_key.h"
#include "base/openssl_help.h"
namespace MTP::details {
namespace {
enum class Format {
RSAPublicKey,
RSA_PUBKEY,
Unknown,
};
struct BIODeleter {
void operator()(BIO *value) {
BIO_free(value);
}
};
Format GuessFormat(bytes::const_span key) {
const auto array = QByteArray::fromRawData(
reinterpret_cast<const char*>(key.data()),
key.size());
if (array.indexOf("BEGIN RSA PUBLIC KEY") >= 0) {
return Format::RSAPublicKey;
} else if (array.indexOf("BEGIN PUBLIC KEY") >= 0) {
return Format::RSA_PUBKEY;
}
return Format::Unknown;
}
RSA *CreateRaw(bytes::const_span key) {
const auto format = GuessFormat(key);
const auto bio = std::unique_ptr<BIO, BIODeleter>{
BIO_new_mem_buf(
const_cast<gsl::byte*>(key.data()),
key.size()),
};
switch (format) {
case Format::RSAPublicKey:
return PEM_read_bio_RSAPublicKey(bio.get(), nullptr, nullptr, nullptr);
case Format::RSA_PUBKEY:
return PEM_read_bio_RSA_PUBKEY(bio.get(), nullptr, nullptr, nullptr);
}
Unexpected("format in RSAPublicKey::Private::Create.");
}
} // namespace
class RSAPublicKey::Private {
public:
explicit Private(bytes::const_span key);
Private(bytes::const_span nBytes, bytes::const_span eBytes);
~Private();
[[nodiscard]] bool valid() const;
[[nodiscard]] uint64 fingerprint() const;
[[nodiscard]] bytes::vector getN() const;
[[nodiscard]] bytes::vector getE() const;
[[nodiscard]] bytes::vector encrypt(bytes::const_span data) const;
[[nodiscard]] bytes::vector decrypt(bytes::const_span data) const;
[[nodiscard]] bytes::vector encryptOAEPpadding(
bytes::const_span data) const;
private:
void computeFingerprint();
[[nodiscard]] static bytes::vector ToBytes(const BIGNUM *number);
RSA *_rsa = nullptr;
uint64 _fingerprint = 0;
};
RSAPublicKey::Private::Private(bytes::const_span key)
: _rsa(CreateRaw(key)) {
if (_rsa) {
computeFingerprint();
}
}
RSAPublicKey::Private::Private(bytes::const_span nBytes, bytes::const_span eBytes)
: _rsa(RSA_new()) {
if (_rsa) {
const auto n = openssl::BigNum(nBytes).takeRaw();
const auto e = openssl::BigNum(eBytes).takeRaw();
const auto valid = (n != nullptr) && (e != nullptr);
// We still pass both values to RSA_set0_key() so that even
// if only one of them is valid RSA would take ownership of it.
if (!RSA_set0_key(_rsa, n, e, nullptr) || !valid) {
RSA_free(base::take(_rsa));
} else {
computeFingerprint();
}
}
}
bool RSAPublicKey::Private::valid() const {
return _rsa != nullptr;
}
uint64 RSAPublicKey::Private::fingerprint() const {
return _fingerprint;
}
bytes::vector RSAPublicKey::Private::getN() const {
Expects(valid());
const BIGNUM *n;
RSA_get0_key(_rsa, &n, nullptr, nullptr);
return ToBytes(n);
}
bytes::vector RSAPublicKey::Private::getE() const {
Expects(valid());
const BIGNUM *e;
RSA_get0_key(_rsa, nullptr, &e, nullptr);
return ToBytes(e);
}
bytes::vector RSAPublicKey::Private::encrypt(bytes::const_span data) const {
Expects(valid());
constexpr auto kEncryptSize = 256;
auto result = bytes::vector(kEncryptSize, gsl::byte{});
auto res = RSA_public_encrypt(kEncryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING);
if (res < 0 || res > kEncryptSize) {
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(fingerprint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
return {};
} else if (auto zeroBytes = kEncryptSize - res) {
auto resultBytes = gsl::make_span(result);
bytes::move(resultBytes.subspan(zeroBytes, res), resultBytes.subspan(0, res));
bytes::set_with_const(resultBytes.subspan(0, zeroBytes), gsl::byte{});
}
return result;
}
bytes::vector RSAPublicKey::Private::decrypt(bytes::const_span data) const {
Expects(valid());
constexpr auto kDecryptSize = 256;
auto result = bytes::vector(kDecryptSize, gsl::byte{});
auto res = RSA_public_decrypt(kDecryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING);
if (res < 0 || res > kDecryptSize) {
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(fingerprint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
return {};
} else if (auto zeroBytes = kDecryptSize - res) {
auto resultBytes = gsl::make_span(result);
bytes::move(resultBytes.subspan(zeroBytes - res, res), resultBytes.subspan(0, res));
bytes::set_with_const(resultBytes.subspan(0, zeroBytes - res), gsl::byte{});
}
return result;
}
bytes::vector RSAPublicKey::Private::encryptOAEPpadding(bytes::const_span data) const {
Expects(valid());
const auto resultSize = RSA_size(_rsa);
auto result = bytes::vector(resultSize, gsl::byte{});
const auto encryptedSize = RSA_public_encrypt(
data.size(),
reinterpret_cast<const unsigned char*>(data.data()),
reinterpret_cast<unsigned char*>(result.data()),
_rsa,
RSA_PKCS1_OAEP_PADDING);
if (encryptedSize != resultSize) {
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
LOG(("RSA Error: RSA_public_encrypt failed, "
"key fp: %1, result: %2, error: %3"
).arg(fingerprint()
).arg(encryptedSize
).arg(ERR_error_string(ERR_get_error(), 0)
));
return {};
}
return result;
}
RSAPublicKey::Private::~Private() {
RSA_free(_rsa);
}
void RSAPublicKey::Private::computeFingerprint() {
Expects(valid());
const BIGNUM *n, *e;
mtpBuffer string;
RSA_get0_key(_rsa, &n, &e, nullptr);
MTP_bytes(ToBytes(n)).write(string);
MTP_bytes(ToBytes(e)).write(string);
bytes::array<20> sha1Buffer;
openssl::Sha1To(sha1Buffer, bytes::make_span(string));
_fingerprint = *(uint64*)(sha1Buffer.data() + 12);
}
bytes::vector RSAPublicKey::Private::ToBytes(const BIGNUM *number) {
auto size = BN_num_bytes(number);
auto result = bytes::vector(size, gsl::byte{});
BN_bn2bin(number, reinterpret_cast<unsigned char*>(result.data()));
return result;
}
RSAPublicKey::RSAPublicKey(bytes::const_span key)
: _private(std::make_shared<Private>(key)) {
}
RSAPublicKey::RSAPublicKey(
bytes::const_span nBytes,
bytes::const_span eBytes)
: _private(std::make_shared<Private>(nBytes, eBytes)) {
}
bool RSAPublicKey::empty() const {
return !_private;
}
bool RSAPublicKey::valid() const {
return !empty() && _private->valid();
}
uint64 RSAPublicKey::fingerprint() const {
Expects(valid());
return _private->fingerprint();
}
bytes::vector RSAPublicKey::getN() const {
Expects(valid());
return _private->getN();
}
bytes::vector RSAPublicKey::getE() const {
Expects(valid());
return _private->getE();
}
bytes::vector RSAPublicKey::encrypt(bytes::const_span data) const {
Expects(valid());
return _private->encrypt(data);
}
bytes::vector RSAPublicKey::decrypt(bytes::const_span data) const {
Expects(valid());
return _private->decrypt(data);
}
bytes::vector RSAPublicKey::encryptOAEPpadding(
bytes::const_span data) const {
return _private->encryptOAEPpadding(data);
}
} // namespace MTP::details

View File

@@ -0,0 +1,49 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/bytes.h"
namespace MTP::details {
// this class holds an RSA public key and can encrypt fixed-size messages with it
class RSAPublicKey final {
public:
RSAPublicKey() = default;
RSAPublicKey(bytes::const_span nBytes, bytes::const_span eBytes);
RSAPublicKey(RSAPublicKey &&other) = default;
RSAPublicKey(const RSAPublicKey &other) = default;
RSAPublicKey &operator=(RSAPublicKey &&other) = default;
RSAPublicKey &operator=(const RSAPublicKey &other) = default;
// key in "-----BEGIN RSA PUBLIC KEY----- ..." format
// or in "-----BEGIN PUBLIC KEY----- ..." format
explicit RSAPublicKey(bytes::const_span key);
[[nodiscard]] bool empty() const;
[[nodiscard]] bool valid() const;
[[nodiscard]] uint64 fingerprint() const;
[[nodiscard]] bytes::vector getN() const;
[[nodiscard]] bytes::vector getE() const;
// data has exactly 256 chars to be encrypted
[[nodiscard]] bytes::vector encrypt(bytes::const_span data) const;
// data has exactly 256 chars to be decrypted
[[nodiscard]] bytes::vector decrypt(bytes::const_span data) const;
// data has lequal than 215 chars to be decrypted
[[nodiscard]] bytes::vector encryptOAEPpadding(bytes::const_span data) const;
private:
class Private;
std::shared_ptr<Private> _private;
};
} // namespace MTP::details

View File

@@ -0,0 +1,156 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_serialized_request.h"
#include "base/random.h"
namespace MTP::details {
namespace {
uint32 CountPaddingPrimesCount(
uint32 requestSize,
bool forAuthKeyInner) {
if (forAuthKeyInner) {
return ((8 + requestSize) & 0x03)
? (4 - ((8 + requestSize) & 0x03))
: 0;
}
auto result = ((8 + requestSize) & 0x03)
? (4 - ((8 + requestSize) & 0x03))
: 0;
// At least 12 bytes of random padding.
if (result < 3) {
result += 4;
}
// Some more random padding.
return result + ((base::RandomValue<uchar>() & 0x0F) << 2);
}
} // namespace
SerializedRequest::SerializedRequest(const RequestConstructHider::Tag &tag)
: _data(std::make_shared<RequestData>(tag)) {
}
SerializedRequest SerializedRequest::Prepare(
uint32 size,
uint32 reserveSize) {
Expects(size > 0);
const auto finalSize = std::max(size, reserveSize);
auto result = SerializedRequest(RequestConstructHider::Tag{});
result->reserve(kMessageBodyPosition + finalSize);
result->resize(kMessageBodyPosition);
result->back() = (size << 2);
result->lastSentTime = crl::now();
return result;
}
RequestData *SerializedRequest::operator->() const {
Expects(_data != nullptr);
return _data.get();
}
RequestData &SerializedRequest::operator*() const {
Expects(_data != nullptr);
return *_data;
}
SerializedRequest::operator bool() const {
return (_data != nullptr);
}
void SerializedRequest::setMsgId(mtpMsgId msgId) {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
memcpy(_data->data() + kMessageIdPosition, &msgId, sizeof(mtpMsgId));
}
mtpMsgId SerializedRequest::getMsgId() const {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
return *(mtpMsgId*)(_data->constData() + kMessageIdPosition);
}
void SerializedRequest::setSeqNo(uint32 seqNo) {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
(*_data)[kSeqNoPosition] = mtpPrime(seqNo);
}
uint32 SerializedRequest::getSeqNo() const {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
return uint32((*_data)[kSeqNoPosition]);
}
void SerializedRequest::addPadding(bool forAuthKeyInner) {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
const auto requestSize = (tl::count_length(*this) >> 2);
const auto padding = CountPaddingPrimesCount(
requestSize,
forAuthKeyInner);
const auto fullSize = kMessageBodyPosition + requestSize + padding;
if (uint32(_data->size()) != fullSize) {
_data->resize(fullSize);
if (padding > 0) {
bytes::set_random(bytes::make_span(*_data).subspan(
(fullSize - padding) * sizeof(mtpPrime)));
}
}
}
uint32 SerializedRequest::messageSize() const {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
const auto ints = (tl::count_length(*this) >> 2);
return kMessageIdInts + kSeqNoInts + kMessageLengthInts + ints;
}
bool SerializedRequest::needAck() const {
Expects(_data != nullptr);
Expects(_data->size() > kMessageBodyPosition);
const auto type = mtpTypeId((*_data)[kMessageBodyPosition]);
switch (type) {
case mtpc_msg_container:
case mtpc_msgs_ack:
case mtpc_http_wait:
case mtpc_bad_msg_notification:
case mtpc_msgs_all_info:
case mtpc_msgs_state_info:
case mtpc_msg_detailed_info:
case mtpc_msg_new_detailed_info:
return false;
}
return true;
}
size_t SerializedRequest::sizeInBytes() const {
Expects(!_data || _data->size() > kMessageBodyPosition);
return _data ? (*_data)[kMessageLengthPosition] : 0;
}
const void *SerializedRequest::dataInBytes() const {
Expects(!_data || _data->size() > kMessageBodyPosition);
return _data ? (_data->constData() + kMessageBodyPosition) : nullptr;
}
} // namespace MTP

View File

@@ -0,0 +1,107 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/core_types.h"
#include <crl/crl_time.h>
namespace MTP {
namespace details {
class RequestData;
class SerializedRequest;
class RequestConstructHider {
struct Tag {};
friend class RequestData;
friend class SerializedRequest;
};
class SerializedRequest {
public:
SerializedRequest() = default;
static constexpr auto kSaltInts = 2;
static constexpr auto kSessionIdInts = 2;
static constexpr auto kMessageIdPosition = kSaltInts + kSessionIdInts;
static constexpr auto kMessageIdInts = 2;
static constexpr auto kSeqNoPosition = kMessageIdPosition
+ kMessageIdInts;
static constexpr auto kSeqNoInts = 1;
static constexpr auto kMessageLengthPosition = kSeqNoPosition
+ kSeqNoInts;
static constexpr auto kMessageLengthInts = 1;
static constexpr auto kMessageBodyPosition = kMessageLengthPosition
+ kMessageLengthInts;
static SerializedRequest Prepare(uint32 size, uint32 reserveSize = 0);
template <
typename Request,
typename = std::enable_if_t<tl::is_boxed_v<Request>>>
static SerializedRequest Serialize(const Request &request);
// For template MTP requests and MTPBoxed instantiation.
template <typename Accumulator>
void write(Accumulator &to) const {
if (const auto size = sizeInBytes()) {
tl::Writer<Accumulator>::PutBytes(to, dataInBytes(), size);
}
}
RequestData *operator->() const;
RequestData &operator*() const;
explicit operator bool() const;
void setMsgId(mtpMsgId msgId);
[[nodiscard]] mtpMsgId getMsgId() const;
void setSeqNo(uint32 seqNo);
[[nodiscard]] uint32 getSeqNo() const;
void addPadding(bool forAuthKeyInner);
[[nodiscard]] uint32 messageSize() const;
[[nodiscard]] bool needAck() const;
using ResponseType = void; // don't know real response type =(
private:
explicit SerializedRequest(const RequestConstructHider::Tag &);
[[nodiscard]] size_t sizeInBytes() const;
[[nodiscard]] const void *dataInBytes() const;
std::shared_ptr<RequestData> _data;
};
class RequestData : public mtpBuffer {
public:
explicit RequestData(const RequestConstructHider::Tag &) {
}
SerializedRequest after;
crl::time lastSentTime = 0;
mtpRequestId requestId = 0;
bool needsLayer = false;
bool forceSendInContainer = false;
};
template <typename Request, typename>
SerializedRequest SerializedRequest::Serialize(const Request &request) {
const auto requestSize = tl::count_length(request) >> 2;
auto serialized = Prepare(requestSize);
request.template write<mtpBuffer>(*serialized);
return serialized;
}
} // namespace details
} // namespace MTP

View File

@@ -0,0 +1,125 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_tcp_socket.h"
#include "base/invoke_queued.h"
namespace MTP::details {
TcpSocket::TcpSocket(
not_null<QThread*> thread,
const QNetworkProxy &proxy,
bool protocolForFiles)
: AbstractSocket(thread) {
_socket.moveToThread(thread);
_socket.setProxy(proxy);
if (protocolForFiles) {
_socket.setSocketOption(
QAbstractSocket::SendBufferSizeSocketOption,
kFilesSendBufferSize);
_socket.setSocketOption(
QAbstractSocket::ReceiveBufferSizeSocketOption,
kFilesReceiveBufferSize);
}
const auto wrap = [&](auto handler) {
return [=](auto &&...args) {
InvokeQueued(this, [=] { handler(args...); });
};
};
using Error = QAbstractSocket::SocketError;
connect(
&_socket,
&QTcpSocket::connected,
wrap([=] { _connected.fire({}); }));
connect(
&_socket,
&QTcpSocket::disconnected,
wrap([=] { _disconnected.fire({}); }));
connect(
&_socket,
&QTcpSocket::readyRead,
wrap([=] { _readyRead.fire({}); }));
connect(
&_socket,
&QAbstractSocket::errorOccurred,
wrap([=](Error e) { handleError(e); }));
}
void TcpSocket::connectToHost(const QString &address, int port) {
_socket.connectToHost(address, port);
}
bool TcpSocket::isGoodStartNonce(bytes::const_span nonce) {
Expects(nonce.size() >= 2 * sizeof(uint32));
const auto bytes = nonce.data();
const auto zero = *reinterpret_cast<const uchar*>(bytes);
const auto first = *reinterpret_cast<const uint32*>(bytes);
const auto second = *(reinterpret_cast<const uint32*>(bytes) + 1);
const auto reserved01 = 0x000000EFU;
const auto reserved11 = 0x44414548U;
const auto reserved12 = 0x54534F50U;
const auto reserved13 = 0x20544547U;
const auto reserved14 = 0xEEEEEEEEU;
const auto reserved15 = 0xDDDDDDDDU;
const auto reserved16 = 0x02010316U;
const auto reserved21 = 0x00000000U;
return (zero != reserved01)
&& (first != reserved11)
&& (first != reserved12)
&& (first != reserved13)
&& (first != reserved14)
&& (first != reserved15)
&& (first != reserved16)
&& (second != reserved21);
}
void TcpSocket::timedOut() {
}
bool TcpSocket::isConnected() {
return (_socket.state() == QAbstractSocket::ConnectedState);
}
bool TcpSocket::hasBytesAvailable() {
return _socket.bytesAvailable() > 0;
}
int64 TcpSocket::read(bytes::span buffer) {
return _socket.read(
reinterpret_cast<char*>(buffer.data()),
buffer.size());
}
void TcpSocket::write(bytes::const_span prefix, bytes::const_span buffer) {
Expects(!buffer.empty());
if (!prefix.empty()) {
_socket.write(
reinterpret_cast<const char*>(prefix.data()),
prefix.size());
}
_socket.write(
reinterpret_cast<const char*>(buffer.data()),
buffer.size());
}
int32 TcpSocket::debugState() {
return _socket.state();
}
QString TcpSocket::debugPostfix() const {
return QString();
}
void TcpSocket::handleError(int errorCode) {
logError(errorCode, _socket.errorString());
_error.fire({});
}
} // namespace MTP::details

View File

@@ -0,0 +1,39 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/details/mtproto_abstract_socket.h"
namespace MTP::details {
class TcpSocket final : public AbstractSocket {
public:
TcpSocket(
not_null<QThread*> thread,
const QNetworkProxy &proxy,
bool protocolForFiles);
void connectToHost(const QString &address, int port) override;
bool isGoodStartNonce(bytes::const_span nonce) override;
void timedOut() override;
bool isConnected() override;
bool hasBytesAvailable() override;
int64 read(bytes::span buffer) override;
void write(bytes::const_span prefix, bytes::const_span buffer) override;
int32 debugState() override;
QString debugPostfix() const override;
private:
void handleError(int errorCode);
QTcpSocket _socket;
};
} // namespace MTP::details

View File

@@ -0,0 +1,944 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/details/mtproto_tls_socket.h"
#include "mtproto/details/mtproto_tcp_socket.h"
#include "base/openssl_help.h"
#include "base/bytes.h"
#include "base/invoke_queued.h"
#include "base/random.h"
#include "base/unixtime.h"
#include <QtCore/QtEndian>
#include <range/v3/algorithm/reverse.hpp>
namespace MTP::details {
namespace {
constexpr auto kMaxGrease = 8;
constexpr auto kClientHelloLimit = 2048;
constexpr auto kHelloDigestLength = 32;
constexpr auto kLengthSize = sizeof(uint16);
const auto kServerHelloPart1 = qstr("\x16\x03\x03");
const auto kServerHelloPart3 = qstr("\x14\x03\x03\x00\x01\x01\x17\x03\x03");
constexpr auto kServerHelloDigestPosition = 11;
const auto kServerHeader = qstr("\x17\x03\x03");
constexpr auto kClientPartSize = 2878;
const auto kClientPrefix = qstr("\x14\x03\x03\x00\x01\x01");
const auto kClientHeader = qstr("\x17\x03\x03");
using BigNum = openssl::BigNum;
using BigNumContext = openssl::Context;
[[nodiscard]] MTPTlsClientHello PrepareClientHelloRules() {
using Scope = QVector<MTPTlsBlock>;
using Permutation = std::vector<Scope>;
using StackElement = std::variant<Scope, Permutation>;
auto stack = std::vector<StackElement>();
const auto pushToBack = [&](MTPTlsBlock &&block) {
Expects(!stack.empty());
if (const auto scope = std::get_if<Scope>(&stack.back())) {
scope->push_back(std::move(block));
} else {
auto &permutation = v::get<Permutation>(stack.back());
Assert(!permutation.empty());
permutation.back().push_back(std::move(block));
}
};
const auto S = [&](QByteArray data) {
pushToBack(MTP_tlsBlockString(MTP_bytes(data)));
};
const auto Z = [&](int length) {
pushToBack(MTP_tlsBlockZero(MTP_int(length)));
};
const auto G = [&](int seed) {
pushToBack(MTP_tlsBlockGrease(MTP_int(seed)));
};
const auto R = [&](int length) {
pushToBack(MTP_tlsBlockRandom(MTP_int(length)));
};
const auto D = [&] {
pushToBack(MTP_tlsBlockDomain());
};
const auto K = [&] {
pushToBack(MTP_tlsBlockPublicKey());
};
const auto M = [&] {
pushToBack(MTP_tlsBlockM());
};
const auto E = [&] {
pushToBack(MTP_tlsBlockE());
};
const auto P = [&] {
pushToBack(MTP_tlsBlockPadding());
};
const auto OpenScope = [&] {
stack.emplace_back(Scope());
};
const auto CloseScope = [&] {
Expects(stack.size() > 1);
Expects(v::is<Scope>(stack.back()));
const auto blocks = std::move(v::get<Scope>(stack.back()));
stack.pop_back();
pushToBack(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(blocks)));
};
const auto OpenPermutation = [&] {
stack.emplace_back(Permutation());
};
const auto ClosePermutation = [&] {
Expects(stack.size() > 1);
Expects(v::is<Permutation>(stack.back()));
const auto list = std::move(v::get<Permutation>(stack.back()));
stack.pop_back();
const auto wrapped = list | ranges::views::transform([](
const QVector<MTPTlsBlock> &elements) {
return MTP_vector<MTPTlsBlock>(elements);
}) | ranges::to<QVector<MTPVector<MTPTlsBlock>>>();
pushToBack(MTP_tlsBlockPermutation(
MTP_vector<MTPVector<MTPTlsBlock>>(wrapped)));
};
const auto StartPermutationElement = [&] {
Expects(stack.size() > 1);
Expects(v::is<Permutation>(stack.back()));
v::get<Permutation>(stack.back()).emplace_back();
};
const auto Finish = [&] {
Expects(stack.size() == 1);
Expects(v::is<Scope>(stack.back()));
return v::get<Scope>(stack.back());
};
stack.emplace_back(Scope());
S("\x16\x03\x01"_q);
OpenScope();
S("\x01\x00"_q);
OpenScope();
S("\x03\x03"_q);
Z(32);
S("\x20"_q);
R(32);
S("\x00\x20"_q);
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"
""_q);
OpenScope();
G(2);
S("\x00\x00"_q);
OpenPermutation(); {
StartPermutationElement(); {
S("\x00\x00"_q);
OpenScope();
OpenScope();
S("\x00"_q);
OpenScope();
D();
CloseScope();
CloseScope();
CloseScope();
}
StartPermutationElement(); {
S("\x00\x05\x00\x05\x01\x00\x00\x00\x00"_q);
}
StartPermutationElement(); {
S("\x00\x0a\x00\x0c\x00\x0a"_q);
G(4);
S("\x11\xec\x00\x1d\x00\x17\x00\x18"_q);
}
StartPermutationElement(); {
S("\x00\x0b\x00\x02\x01\x00"_q);
}
StartPermutationElement(); {
S(""
"\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03"
"\x08\x05\x05\x01\x08\x06\x06\x01"_q);
}
StartPermutationElement(); {
S(""
"\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70"
"\x2f\x31\x2e\x31"_q);
}
StartPermutationElement(); {
S("\x00\x12\x00\x00"_q);
}
StartPermutationElement(); {
S("\x00\x17\x00\x00"_q);
}
StartPermutationElement(); {
S("\x00\x1b\x00\x03\x02\x00\x02"_q);
}
StartPermutationElement(); {
S("\x00\x23\x00\x00"_q);
}
StartPermutationElement(); {
S("\x00\x2b\x00\x07\x06"_q);
G(6);
S("\x03\x04\x03\x03"_q);
}
StartPermutationElement(); {
S("\x00\x2d\x00\x02\x01\x01"_q);
}
StartPermutationElement(); {
S("\x00\x33\x04\xef\x04\xed"_q);
G(4);
S("\x00\x01\x00\x11\xec\x04\xc0"_q);
M();
K();
S("\x00\x1d\x00\x20"_q);
K();
}
StartPermutationElement(); {
S("\x44\xcd\x00\x05\x00\x03\x02\x68\x32"_q);
}
StartPermutationElement(); {
S("\xfe\x02"_q);
OpenScope();
S("\x00\x00\x01\x00\x01"_q);
R(1);
S("\x00\x20"_q);
R(20);
OpenScope();
E();
CloseScope();
CloseScope();
}
StartPermutationElement(); {
S("\xff\x01\x00\x01\x00"_q);
}
} ClosePermutation();
G(3);
S("\x00\x01\x00"_q);
P();
CloseScope();
CloseScope();
CloseScope();
return MTP_tlsClientHello(MTP_vector<MTPTlsBlock>(Finish()));
}
[[nodiscard]] bytes::vector PrepareGreases() {
auto result = bytes::vector(kMaxGrease);
bytes::set_random(result);
for (auto &byte : result) {
byte = bytes::type((uchar(byte) & 0xF0) + 0x0A);
}
static_assert(kMaxGrease % 2 == 0);
for (auto i = 0; i != kMaxGrease; i += 2) {
if (result[i] == result[i + 1]) {
result[i + 1] = bytes::type(uchar(result[i + 1]) ^ 0x10);
}
}
return result;
}
[[nodiscard]] bytes::vector GeneratePublicKey() {
const auto context = EVP_PKEY_CTX_new_id(NID_ED25519, nullptr);
if (!context) {
return {};
}
const auto guardContext = gsl::finally([&] {
EVP_PKEY_CTX_free(context);
});
if (EVP_PKEY_keygen_init(context) <= 0) {
return {};
}
auto key = (EVP_PKEY*)nullptr;
if (EVP_PKEY_keygen(context, &key) <= 0) {
return {};
}
const auto guardKey = gsl::finally([&] {
EVP_PKEY_free(key);
});
auto length = size_t(0);
if (!EVP_PKEY_get_raw_public_key(key, nullptr, &length)) {
return {};
}
Assert(length == 32);
auto result = bytes::vector(length);
const auto code = EVP_PKEY_get_raw_public_key(
key,
reinterpret_cast<unsigned char *>(result.data()),
&length);
if (!code) {
return {};
}
return result;
}
struct ClientHello {
QByteArray data;
QByteArray digest;
};
class Generator {
public:
Generator(
const MTPTlsClientHello &rules,
bytes::const_span domain,
bytes::const_span key);
[[nodiscard]] ClientHello take();
private:
class Part final {
public:
explicit Part(
bytes::const_span domain,
const bytes::vector &greases);
[[nodiscard]] bytes::span grow(int size);
void writeBlocks(const QVector<MTPTlsBlock> &blocks);
void writeBlock(const MTPTlsBlock &data);
void writeBlock(const MTPDtlsBlockString &data);
void writeBlock(const MTPDtlsBlockZero &data);
void writeBlock(const MTPDtlsBlockGrease &data);
void writeBlock(const MTPDtlsBlockRandom &data);
void writeBlock(const MTPDtlsBlockDomain &data);
void writeBlock(const MTPDtlsBlockPublicKey &data);
void writeBlock(const MTPDtlsBlockScope &data);
void writeBlock(const MTPDtlsBlockPermutation &data);
void writeBlock(const MTPDtlsBlockM &data);
void writeBlock(const MTPDtlsBlockE &data);
void writeBlock(const MTPDtlsBlockPadding &data);
void finalize(bytes::const_span key);
[[nodiscard]] QByteArray extractDigest() const;
[[nodiscard]] bool error() const;
[[nodiscard]] QByteArray take();
private:
void writeDigest(bytes::const_span key);
void injectTimestamp();
bytes::const_span _domain;
const bytes::vector &_greases;
QByteArray _result;
const char *_data = nullptr;
int _digestPosition = -1;
bool _error = false;
};
bytes::vector _greases;
Part _result;
QByteArray _digest;
};
Generator::Part::Part(
bytes::const_span domain,
const bytes::vector &greases)
: _domain(domain)
, _greases(greases) {
_result.reserve(kClientHelloLimit);
_data = _result.constData();
}
bool Generator::Part::error() const {
return _error;
}
QByteArray Generator::Part::take() {
Expects(_error || _result.constData() == _data);
return _error ? QByteArray() : std::move(_result);
}
bytes::span Generator::Part::grow(int size) {
if (_error
|| size <= 0
|| _result.size() + size > kClientHelloLimit) {
_error = true;
return bytes::span();
}
const auto offset = _result.size();
_result.resize(offset + size);
return bytes::make_detached_span(_result).subspan(offset);
}
void Generator::Part::writeBlocks(const QVector<MTPTlsBlock> &blocks) {
for (const auto &block : blocks) {
writeBlock(block);
}
}
void Generator::Part::writeBlock(const MTPTlsBlock &data) {
data.match([&](const auto &data) {
writeBlock(data);
});
}
void Generator::Part::writeBlock(const MTPDtlsBlockString &data) {
const auto &bytes = data.vdata().v;
const auto storage = grow(bytes.size());
if (storage.empty()) {
return;
}
bytes::copy(storage, bytes::make_span(bytes));
}
void Generator::Part::writeBlock(const MTPDtlsBlockZero &data) {
const auto length = data.vlength().v;
const auto already = _result.size();
const auto storage = grow(length);
if (storage.empty()) {
return;
}
if (length == kHelloDigestLength && _digestPosition < 0) {
_digestPosition = already;
}
bytes::set_with_const(storage, bytes::type(0));
}
void Generator::Part::writeBlock(const MTPDtlsBlockGrease &data) {
const auto seed = data.vseed().v;
if (seed < 0 || seed >= _greases.size()) {
_error = true;
return;
}
const auto storage = grow(2);
if (storage.empty()) {
return;
}
bytes::set_with_const(storage, _greases[seed]);
}
void Generator::Part::writeBlock(const MTPDtlsBlockRandom &data) {
const auto length = data.vlength().v;
const auto storage = grow(length);
if (storage.empty()) {
return;
}
bytes::set_random(storage);
}
void Generator::Part::writeBlock(const MTPDtlsBlockDomain &data) {
const auto storage = grow(_domain.size());
if (storage.empty()) {
return;
}
bytes::copy(storage, _domain);
}
void Generator::Part::writeBlock(const MTPDtlsBlockPublicKey &data) {
const auto key = GeneratePublicKey();
const auto storage = grow(key.size());
if (storage.empty()) {
return;
}
bytes::copy(storage, key);
}
void Generator::Part::writeBlock(const MTPDtlsBlockScope &data) {
const auto storage = grow(kLengthSize);
if (storage.empty()) {
return;
}
const auto already = _result.size();
writeBlocks(data.ventries().v);
const auto length = qToBigEndian(uint16(_result.size() - already));
bytes::copy(storage, bytes::object_as_span(&length));
}
void Generator::Part::writeBlock(const MTPDtlsBlockPermutation &data) {
auto list = std::vector<QByteArray>();
list.reserve(data.ventries().v.size());
for (const auto &inner : data.ventries().v) {
auto part = Part(_domain, _greases);
part.writeBlocks(inner.v);
if (part.error()) {
_error = true;
return;
}
list.push_back(part.take());
}
ranges::shuffle(list);
for (const auto &element : list) {
const auto storage = grow(element.size());
if (storage.empty()) {
return;
}
bytes::copy(storage, bytes::make_span(element));
}
}
void Generator::Part::writeBlock(const MTPDtlsBlockM &data) {
constexpr auto kElements = 384;
constexpr auto kAdded = 32;
const auto storage = grow(kElements * 3 + kAdded);
if (storage.empty()) {
return;
}
auto random = bytes::vector(kElements * 8 + kAdded);
bytes::set_random(random);
auto chars = reinterpret_cast<char*>(storage.data());
const auto ints = reinterpret_cast<const uint32*>(random.data());
for (auto i = 0; i < kElements; ++i) {
const auto a = int(ints[i * 2] % 3329);
const auto b = int(ints[i * 2 + 1] % 3329);
*chars++ = (char)(a & 255);
*chars++ = (char)((a >> 8) + ((b & 15) << 4));
*chars++ = (char)(b >> 4);
}
bytes::set_random(storage.subspan(kElements * 3));
}
void Generator::Part::writeBlock(const MTPDtlsBlockE &data) {
const auto lengths = std::array{ 144, 176, 208, 240 };
const auto length = lengths[base::RandomIndex(lengths.size())];
writeBlock(MTP_tlsBlockRandom(MTP_int(length)));
}
void Generator::Part::writeBlock(const MTPDtlsBlockPadding &data) {
const auto length = int(_result.size());
if (length < 513) {
const auto zero = MTP_tlsBlockZero(MTP_int(513 - length));
writeBlock(MTP_tlsBlockString(MTP_bytes("\x00\x15"_q)));
writeBlock(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(1, zero)));
}
}
void Generator::Part::finalize(bytes::const_span key) {
if (_error) {
return;
} else if (_digestPosition < 0) {
_error = true;
return;
}
writeDigest(key);
injectTimestamp();
}
QByteArray Generator::Part::extractDigest() const {
if (_digestPosition < 0) {
return {};
}
return _result.mid(_digestPosition, kHelloDigestLength);
}
void Generator::Part::writeDigest(bytes::const_span key) {
Expects(_digestPosition >= 0);
bytes::copy(
bytes::make_detached_span(_result).subspan(_digestPosition),
openssl::HmacSha256(key, bytes::make_span(_result)));
}
void Generator::Part::injectTimestamp() {
Expects(_digestPosition >= 0);
const auto storage = bytes::make_detached_span(_result).subspan(
_digestPosition + kHelloDigestLength - sizeof(int32),
sizeof(int32));
auto already = int32();
bytes::copy(bytes::object_as_span(&already), storage);
already ^= qToLittleEndian(int32(base::unixtime::http_now()));
bytes::copy(storage, bytes::object_as_span(&already));
}
Generator::Generator(
const MTPTlsClientHello &rules,
bytes::const_span domain,
bytes::const_span key)
: _greases(PrepareGreases())
, _result(domain, _greases) {
_result.writeBlocks(rules.data().vblocks().v);
_result.finalize(key);
}
ClientHello Generator::take() {
auto digest = _result.extractDigest();
return { _result.take(), std::move(digest) };
}
[[nodiscard]] ClientHello PrepareClientHello(
const MTPTlsClientHello &rules,
bytes::const_span domain,
bytes::const_span key) {
return Generator(rules, domain, key).take();
}
[[nodiscard]] bool CheckPart(bytes::const_span data, QLatin1String check) {
if (data.size() < check.size()) {
return false;
}
return !bytes::compare(
data.subspan(0, check.size()),
bytes::make_span(check.data(), check.size()));
}
[[nodiscard]] int ReadPartLength(bytes::const_span data, int offset) {
const auto storage = data.subspan(offset, kLengthSize);
return qFromBigEndian(
*reinterpret_cast<const uint16*>(storage.data()));
}
} // namespace
TlsSocket::TlsSocket(
not_null<QThread*> thread,
const bytes::vector &secret,
const QNetworkProxy &proxy,
bool protocolForFiles)
: AbstractSocket(thread)
, _secret(secret) {
Expects(_secret.size() >= 21 && _secret[0] == bytes::type(0xEE));
_socket.moveToThread(thread);
_socket.setProxy(proxy);
if (protocolForFiles) {
_socket.setSocketOption(
QAbstractSocket::SendBufferSizeSocketOption,
kFilesSendBufferSize);
_socket.setSocketOption(
QAbstractSocket::ReceiveBufferSizeSocketOption,
kFilesReceiveBufferSize);
}
const auto wrap = [&](auto handler) {
return [=](auto &&...args) {
InvokeQueued(this, [=] { handler(args...); });
};
};
using Error = QAbstractSocket::SocketError;
connect(
&_socket,
&QTcpSocket::connected,
wrap([=] { plainConnected(); }));
connect(
&_socket,
&QTcpSocket::disconnected,
wrap([=] { plainDisconnected(); }));
connect(
&_socket,
&QTcpSocket::readyRead,
wrap([=] { plainReadyRead(); }));
connect(
&_socket,
&QAbstractSocket::errorOccurred,
wrap([=](Error e) { handleError(e); }));
}
bytes::const_span TlsSocket::domainFromSecret() const {
return bytes::make_span(_secret).subspan(17);
}
bytes::const_span TlsSocket::keyFromSecret() const {
return bytes::make_span(_secret).subspan(1, 16);
}
void TlsSocket::plainConnected() {
if (_state != State::Connecting) {
return;
}
static const auto kClientHelloRules = PrepareClientHelloRules();
const auto hello = PrepareClientHello(
kClientHelloRules,
domainFromSecret(),
keyFromSecret());
if (hello.data.isEmpty()) {
logError(888, "Could not generate Client Hello.");
_state = State::Error;
_error.fire({});
} else {
_state = State::WaitingHello;
_incoming = hello.digest;
_socket.write(hello.data);
}
}
void TlsSocket::plainDisconnected() {
_state = State::NotConnected;
_incoming = QByteArray();
_serverHelloLength = 0;
_incomingGoodDataOffset = 0;
_incomingGoodDataLimit = 0;
_disconnected.fire({});
}
void TlsSocket::plainReadyRead() {
switch (_state) {
case State::WaitingHello: return readHello();
case State::Connected: return readData();
}
}
bool TlsSocket::requiredHelloPartReady() const {
return _incoming.size() >= kHelloDigestLength + _serverHelloLength;
}
void TlsSocket::readHello() {
const auto parts1Size = kServerHelloPart1.size() + kLengthSize;
if (!_serverHelloLength) {
_serverHelloLength = parts1Size;
}
while (!requiredHelloPartReady()) {
if (!_socket.bytesAvailable()) {
return;
}
_incoming.append(_socket.readAll());
}
checkHelloParts12(parts1Size);
}
void TlsSocket::checkHelloParts12(int parts1Size) {
const auto data = bytes::make_span(_incoming).subspan(
kHelloDigestLength,
parts1Size);
const auto part2Size = ReadPartLength(data, parts1Size - kLengthSize);
const auto parts123Size = parts1Size
+ part2Size
+ kServerHelloPart3.size()
+ kLengthSize;
if (_serverHelloLength == parts1Size) {
const auto part1Offset = parts1Size
- kLengthSize
- kServerHelloPart1.size();
if (!CheckPart(data.subspan(part1Offset), kServerHelloPart1)) {
logError(888, "Bad Server Hello part1.");
handleError();
return;
}
_serverHelloLength = parts123Size;
if (!requiredHelloPartReady()) {
readHello();
return;
}
}
checkHelloParts34(parts123Size);
}
void TlsSocket::checkHelloParts34(int parts123Size) {
const auto data = bytes::make_span(_incoming).subspan(
kHelloDigestLength,
parts123Size);
const auto part4Size = ReadPartLength(data, parts123Size - kLengthSize);
const auto full = parts123Size + part4Size;
if (_serverHelloLength == parts123Size) {
const auto part3Offset = parts123Size
- kLengthSize
- kServerHelloPart3.size();
if (!CheckPart(data.subspan(part3Offset), kServerHelloPart3)) {
logError(888, "Bad Server Hello part.");
handleError();
return;
}
_serverHelloLength = full;
if (!requiredHelloPartReady()) {
readHello();
return;
}
}
checkHelloDigest();
}
void TlsSocket::checkHelloDigest() {
const auto fulldata = bytes::make_detached_span(_incoming).subspan(
0,
kHelloDigestLength + _serverHelloLength);
const auto digest = fulldata.subspan(
kHelloDigestLength + kServerHelloDigestPosition,
kHelloDigestLength);
const auto digestCopy = bytes::make_vector(digest);
bytes::set_with_const(digest, bytes::type(0));
const auto check = openssl::HmacSha256(keyFromSecret(), fulldata);
if (bytes::compare(digestCopy, check) != 0) {
logError(888, "Bad Server Hello digest.");
handleError();
return;
}
shiftIncomingBy(fulldata.size());
if (!_incoming.isEmpty()) {
InvokeQueued(this, [=] {
if (!checkNextPacket()) {
handleError();
}
});
}
_incomingGoodDataOffset = _incomingGoodDataLimit = 0;
_state = State::Connected;
_connected.fire({});
}
void TlsSocket::readData() {
if (!isConnected()) {
return;
}
_incoming.append(_socket.readAll());
if (!checkNextPacket()) {
handleError();
} else if (hasBytesAvailable()) {
_readyRead.fire({});
}
}
bool TlsSocket::checkNextPacket() {
auto offset = 0;
const auto incoming = bytes::make_span(_incoming);
while (!_incomingGoodDataLimit) {
const auto fullHeader = kServerHeader.size() + kLengthSize;
if (incoming.size() <= offset + fullHeader) {
return true;
}
if (!CheckPart(incoming.subspan(offset), kServerHeader)) {
logError(888, "Bad packet header.");
return false;
}
const auto length = ReadPartLength(
incoming,
offset + kServerHeader.size());
if (length > 0) {
if (offset > 0) {
shiftIncomingBy(offset);
}
_incomingGoodDataOffset = fullHeader;
_incomingGoodDataLimit = length;
} else {
offset += kServerHeader.size() + kLengthSize + length;
}
}
return true;
}
void TlsSocket::shiftIncomingBy(int amount) {
Expects(_incomingGoodDataOffset == 0);
Expects(_incomingGoodDataLimit == 0);
const auto incoming = bytes::make_detached_span(_incoming);
if (incoming.size() > amount) {
bytes::move(incoming, incoming.subspan(amount));
_incoming.chop(amount);
} else {
_incoming.clear();
}
}
void TlsSocket::connectToHost(const QString &address, int port) {
Expects(_state == State::NotConnected);
_state = State::Connecting;
_socket.connectToHost(address, port);
}
bool TlsSocket::isGoodStartNonce(bytes::const_span nonce) {
return true;
}
void TlsSocket::timedOut() {
_syncTimeRequests.fire({});
}
bool TlsSocket::isConnected() {
return (_state == State::Connected);
}
bool TlsSocket::hasBytesAvailable() {
return (_incomingGoodDataLimit > 0)
&& (_incomingGoodDataOffset < _incoming.size());
}
int64 TlsSocket::read(bytes::span buffer) {
auto written = int64(0);
while (_incomingGoodDataLimit) {
const auto available = std::min(
_incomingGoodDataLimit,
int(_incoming.size()) - _incomingGoodDataOffset);
if (available <= 0) {
return written;
}
const auto write = std::min(std::size_t(available), buffer.size());
if (write <= 0) {
return written;
}
bytes::copy(
buffer,
bytes::make_span(_incoming).subspan(
_incomingGoodDataOffset,
write));
written += write;
buffer = buffer.subspan(write);
_incomingGoodDataLimit -= write;
_incomingGoodDataOffset += write;
if (_incomingGoodDataLimit) {
return written;
}
shiftIncomingBy(base::take(_incomingGoodDataOffset));
if (!checkNextPacket()) {
_state = State::Error;
InvokeQueued(this, [=] { handleError(); });
return written;
}
}
return written;
}
void TlsSocket::write(bytes::const_span prefix, bytes::const_span buffer) {
Expects(!buffer.empty());
if (!isConnected()) {
return;
}
if (!prefix.empty()) {
_socket.write(kClientPrefix.data(), kClientPrefix.size());
}
while (!buffer.empty()) {
const auto write = std::min(
kClientPartSize - prefix.size(),
buffer.size());
_socket.write(kClientHeader.data(), kClientHeader.size());
const auto size = qToBigEndian(uint16(prefix.size() + write));
_socket.write(reinterpret_cast<const char*>(&size), sizeof(size));
if (!prefix.empty()) {
_socket.write(
reinterpret_cast<const char*>(prefix.data()),
prefix.size());
prefix = bytes::const_span();
}
_socket.write(
reinterpret_cast<const char*>(buffer.data()),
write);
buffer = buffer.subspan(write);
}
}
int32 TlsSocket::debugState() {
return _socket.state();
}
QString TlsSocket::debugPostfix() const {
return u"_ee"_q;
}
void TlsSocket::handleError(int errorCode) {
if (_state != State::Connected) {
_syncTimeRequests.fire({});
}
if (errorCode) {
logError(errorCode, _socket.errorString());
}
_state = State::Error;
_error.fire({});
}
} // namespace MTP::details

View File

@@ -0,0 +1,68 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/details/mtproto_abstract_socket.h"
namespace MTP::details {
class TlsSocket final : public AbstractSocket {
public:
TlsSocket(
not_null<QThread*> thread,
const bytes::vector &secret,
const QNetworkProxy &proxy,
bool protocolForFiles);
void connectToHost(const QString &address, int port) override;
bool isGoodStartNonce(bytes::const_span nonce) override;
void timedOut() override;
bool isConnected() override;
bool hasBytesAvailable() override;
int64 read(bytes::span buffer) override;
void write(bytes::const_span prefix, bytes::const_span buffer) override;
int32 debugState() override;
QString debugPostfix() const override;
private:
enum class State {
NotConnected,
Connecting,
WaitingHello,
Connected,
Error,
};
[[nodiscard]] bytes::const_span domainFromSecret() const;
[[nodiscard]] bytes::const_span keyFromSecret() const;
void plainConnected();
void plainDisconnected();
void plainReadyRead();
void handleError(int errorCode = 0);
[[nodiscard]] bool requiredHelloPartReady() const;
void readHello();
void checkHelloParts12(int parts1Size);
void checkHelloParts34(int parts123Size);
void checkHelloDigest();
void readData();
[[nodiscard]] bool checkNextPacket();
void shiftIncomingBy(int amount);
const bytes::vector _secret;
QTcpSocket _socket;
State _state = State::NotConnected;
QByteArray _incoming;
int _incomingGoodDataOffset = 0;
int _incomingGoodDataLimit = 0;
int16 _serverHelloLength = 0;
};
} // namespace MTP::details