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
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
132
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
Normal file
132
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
Normal 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
|
||||
42
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
Normal file
42
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
Normal 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
|
||||
855
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal file
855
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal 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
|
||||
139
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal file
139
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal 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
|
||||
166
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.cpp
Normal file
166
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.cpp
Normal 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
|
||||
70
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.h
Normal file
70
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.h
Normal 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
|
||||
367
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp
Normal file
367
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp
Normal 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
|
||||
106
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.h
Normal file
106
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.h
Normal 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
|
||||
169
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp
Normal file
169
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp
Normal 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
|
||||
82
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.h
Normal file
82
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
266
Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp
Normal file
266
Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
125
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp
Normal file
125
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp
Normal 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
|
||||
39
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.h
Normal file
39
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.h
Normal 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
|
||||
944
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp
Normal file
944
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp
Normal 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
|
||||
68
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.h
Normal file
68
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.h
Normal 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
|
||||
Reference in New Issue
Block a user