Extract mtproto key generation code.

This commit is contained in:
John Preston 2019-11-13 17:12:04 +03:00
parent 70dbd9e5b4
commit 08bfe6f1c1
40 changed files with 1667 additions and 1297 deletions

View file

@ -280,11 +280,11 @@ void ApiWrap::refreshProxyPromotion() {
return;
}
const auto key = [&]() -> std::pair<QString, uint32> {
if (Global::ProxySettings() != ProxyData::Settings::Enabled) {
if (Global::ProxySettings() != MTP::ProxyData::Settings::Enabled) {
return {};
}
const auto &proxy = Global::SelectedProxy();
if (proxy.type != ProxyData::Type::Mtproto) {
if (proxy.type != MTP::ProxyData::Type::Mtproto) {
return {};
}
return { proxy.host, proxy.port };

View file

@ -38,6 +38,8 @@ namespace {
constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
using ProxyData = MTP::ProxyData;
class Base64UrlInput : public Ui::MaskedInputField {
public:
Base64UrlInput(

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "base/object_ptr.h"
#include "mtproto/connection_abstract.h"
#include "mtproto/mtproto_proxy_data.h"
namespace Ui {
class BoxContent;
@ -25,6 +26,7 @@ class Radioenum;
class ProxiesBoxController : public base::Subscriber {
public:
using ProxyData = MTP::ProxyData;
using Type = ProxyData::Type;
ProxiesBoxController();

View file

@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/rate_call_box.h"
#include "calls/calls_instance.h"
#include "base/openssl_help.h"
#include "mtproto/connection.h"
#include "mtproto/mtproto_dh_utils.h"
#include "media/audio/media_audio_track.h"
#include "base/platform/base_platform_info.h"
#include "calls/calls_panel.h"
@ -623,10 +623,10 @@ void Call::createAndStartController(const MTPDphoneCall &call) {
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), (_type == Type::Outgoing));
_controller->SetCallbacks(callbacks);
if (Global::UseProxyForCalls()
&& (Global::ProxySettings() == ProxyData::Settings::Enabled)) {
&& (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)) {
const auto &proxy = Global::SelectedProxy();
if (proxy.supportsCalls()) {
Assert(proxy.type == ProxyData::Type::Socks5);
Assert(proxy.type == MTP::ProxyData::Type::Socks5);
_controller->SetProxy(
tgvoip::PROXY_SOCKS5,
proxy.host.toStdString(),

View file

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "calls/calls_instance.h"
#include "mtproto/connection.h"
#include "mtproto/mtproto_dh_utils.h"
#include "core/application.h"
#include "main/main_session.h"
#include "apiwrap.h"

View file

@ -370,12 +370,12 @@ void Application::saveSettingsDelayed(crl::time delay) {
}
void Application::setCurrentProxy(
const ProxyData &proxy,
ProxyData::Settings settings) {
const MTP::ProxyData &proxy,
MTP::ProxyData::Settings settings) {
const auto current = [&] {
return (Global::ProxySettings() == ProxyData::Settings::Enabled)
return (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)
? Global::SelectedProxy()
: ProxyData();
: MTP::ProxyData();
};
const auto was = current();
Global::SetSelectedProxy(proxy);
@ -391,12 +391,12 @@ auto Application::proxyChanges() const -> rpl::producer<ProxyChange> {
}
void Application::badMtprotoConfigurationError() {
if (Global::ProxySettings() == ProxyData::Settings::Enabled
if (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled
&& !_badProxyDisableBox) {
const auto disableCallback = [=] {
setCurrentProxy(
Global::SelectedProxy(),
ProxyData::Settings::System);
MTP::ProxyData::Settings::System);
};
_badProxyDisableBox = Ui::show(Box<InformBox>(
Lang::Hard::ProxyConfigError(),

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/core_settings.h"
#include "mtproto/auth_key.h"
#include "mtproto/mtproto_proxy_data.h"
#include "base/observer.h"
#include "base/timer.h"
@ -131,12 +132,12 @@ public:
return _dcOptions.get();
}
struct ProxyChange {
ProxyData was;
ProxyData now;
MTP::ProxyData was;
MTP::ProxyData now;
};
void setCurrentProxy(
const ProxyData &proxy,
ProxyData::Settings settings);
const MTP::ProxyData &proxy,
MTP::ProxyData::Settings settings);
[[nodiscard]] rpl::producer<ProxyChange> proxyChanges() const;
void badMtprotoConfigurationError();

View file

@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/core_cloud_password.h"
#include "base/openssl_help.h"
#include "mtproto/connection.h"
#include "mtproto/mtproto_dh_utils.h"
namespace Core {
namespace {

View file

@ -814,10 +814,10 @@ void LastCrashedWindow::onNetworkSettingsSaved(
QString password) {
Expects(host.isEmpty() || port != 0);
auto proxy = ProxyData();
auto proxy = MTP::ProxyData();
proxy.type = host.isEmpty()
? ProxyData::Type::None
: ProxyData::Type::Http;
? MTP::ProxyData::Type::None
: MTP::ProxyData::Type::Http;
proxy.host = host;
proxy.port = port;
proxy.user = username;
@ -843,7 +843,7 @@ void LastCrashedWindow::proxyUpdated() {
activate();
}
rpl::producer<ProxyData> LastCrashedWindow::proxyChanges() const {
rpl::producer<MTP::ProxyData> LastCrashedWindow::proxyChanges() const {
return _proxyChanges.events();
}

View file

@ -16,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkAccessManager>
namespace MTP {
struct ProxyData;
} // namespace MTP
namespace Core {
class Launcher;
} // namespace Core
@ -96,7 +100,7 @@ public:
const QByteArray &crashdump,
Fn<void()> launch);
rpl::producer<ProxyData> proxyChanges() const;
rpl::producer<MTP::ProxyData> proxyChanges() const;
rpl::lifetime &lifetime() {
return _lifetime;
@ -199,7 +203,7 @@ private:
void setDownloadProgress(qint64 ready, qint64 total);
Fn<void()> _launch;
rpl::event_stream<ProxyData> _proxyChanges;
rpl::event_stream<MTP::ProxyData> _proxyChanges;
rpl::lifetime _lifetime;
};

View file

@ -163,7 +163,9 @@ bool ApplySocksProxy(
auto params = url_parse_params(
match->captured(1),
qthelp::UrlParamNameTransform::ToLower);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Socks5, params);
ProxiesBoxController::ShowApplyConfirmation(
MTP::ProxyData::Type::Socks5,
params);
return true;
}
@ -174,7 +176,9 @@ bool ApplyMtprotoProxy(
auto params = url_parse_params(
match->captured(1),
qthelp::UrlParamNameTransform::ToLower);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params);
ProxiesBoxController::ShowApplyConfirmation(
MTP::ProxyData::Type::Mtproto,
params);
return true;
}

View file

@ -329,7 +329,7 @@ void Sandbox::singleInstanceChecked() {
_lastCrashDump,
[=] { launchApplication(); });
window->proxyChanges(
) | rpl::start_with_next([=](ProxyData &&proxy) {
) | rpl::start_with_next([=](MTP::ProxyData &&proxy) {
_sandboxProxy = std::move(proxy);
refreshGlobalProxy();
}, window->lifetime());
@ -443,15 +443,15 @@ void Sandbox::refreshGlobalProxy() {
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
const auto proxy = !Global::started()
? _sandboxProxy
: (Global::ProxySettings() == ProxyData::Settings::Enabled)
: (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled)
? Global::SelectedProxy()
: ProxyData();
if (proxy.type == ProxyData::Type::Socks5
|| proxy.type == ProxyData::Type::Http) {
: MTP::ProxyData();
if (proxy.type == MTP::ProxyData::Type::Socks5
|| proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(
ToNetworkProxy(ToDirectIpProxy(proxy)));
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
} else if (!Global::started()
|| Global::ProxySettings() == ProxyData::Settings::System) {
|| Global::ProxySettings() == MTP::ProxyData::Settings::System) {
QNetworkProxyFactory::setUseSystemConfiguration(true);
} else {
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
@ -555,7 +555,7 @@ rpl::producer<> Sandbox::widgetUpdateRequests() const {
return _widgetUpdateRequests.events();
}
ProxyData Sandbox::sandboxProxy() const {
MTP::ProxyData Sandbox::sandboxProxy() const {
return _sandboxProxy;
}

View file

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "mtproto/mtproto_proxy_data.h"
#include <QtWidgets/QApplication>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
@ -50,7 +52,7 @@ public:
rpl::producer<> widgetUpdateRequests() const;
ProxyData sandboxProxy() const;
MTP::ProxyData sandboxProxy() const;
static Sandbox &Instance() {
Expects(QCoreApplication::instance() != nullptr);
@ -119,7 +121,7 @@ private:
std::unique_ptr<UpdateChecker> _updateChecker;
QByteArray _lastCrashDump;
ProxyData _sandboxProxy;
MTP::ProxyData _sandboxProxy;
rpl::event_stream<> _widgetUpdateRequests;

View file

@ -62,128 +62,6 @@ namespace {
std::atomic<int> GlobalAtomicRequestId = 0;
[[nodiscard]] bool IsHexMtprotoPassword(const QString &password) {
const auto size = password.size();
if (size < 32 || size % 2 == 1) {
return false;
}
const auto bad = [](QChar ch) {
const auto code = ch.unicode();
return (code < 'a' || code > 'f')
&& (code < 'A' || code > 'F')
&& (code < '0' || code > '9');
};
const auto i = std::find_if(password.begin(), password.end(), bad);
return (i == password.end());
}
[[nodiscard]] ProxyData::Status HexMtprotoPasswordStatus(
const QString &password) {
const auto size = password.size() / 2;
const auto valid = (size == 16)
|| (size == 17 && (password[0] == 'd') && (password[1] == 'd'))
|| (size >= 21 && (password[0] == 'e') && (password[1] == 'e'));
if (valid) {
return ProxyData::Status::Valid;
} else if (size < 16) {
return ProxyData::Status::Invalid;
}
return ProxyData::Status::Unsupported;
}
[[nodiscard]] bytes::vector SecretFromHexMtprotoPassword(
const QString &password) {
Expects(password.size() % 2 == 0);
const auto size = password.size() / 2;
const auto fromHex = [](QChar ch) -> int {
const auto code = int(ch.unicode());
if (code >= '0' && code <= '9') {
return (code - '0');
} else if (code >= 'A' && code <= 'F') {
return 10 + (code - 'A');
} else if (ch >= 'a' && ch <= 'f') {
return 10 + (code - 'a');
}
Unexpected("Code in ProxyData fromHex.");
};
auto result = bytes::vector(size);
for (auto i = 0; i != size; ++i) {
const auto high = fromHex(password[2 * i]);
const auto low = fromHex(password[2 * i + 1]);
if (high < 0 || low < 0) {
return {};
}
result[i] = static_cast<bytes::type>(high * 16 + low);
}
return result;
}
[[nodiscard]] QStringRef Base64UrlInner(const QString &password) {
Expects(password.size() > 2);
// Skip one or two '=' at the end of the string.
return password.midRef(0, [&] {
auto result = password.size();
for (auto i = 0; i != 2; ++i) {
const auto prev = result - 1;
if (password[prev] != '=') {
break;
}
result = prev;
}
return result;
}());
}
[[nodiscard]] bool IsBase64UrlMtprotoPassword(const QString &password) {
const auto size = password.size();
if (size < 22 || size % 4 == 1) {
return false;
}
const auto bad = [](QChar ch) {
const auto code = ch.unicode();
return (code < 'a' || code > 'z')
&& (code < 'A' || code > 'Z')
&& (code < '0' || code > '9')
&& (code != '_')
&& (code != '-');
};
const auto inner = Base64UrlInner(password);
const auto begin = inner.data();
const auto end = begin + inner.size();
return (std::find_if(begin, end, bad) == end);
}
[[nodiscard]] ProxyData::Status Base64UrlMtprotoPasswordStatus(
const QString &password) {
const auto inner = Base64UrlInner(password);
const auto size = (inner.size() * 3) / 4;
const auto valid = (size == 16)
|| (size == 17
&& (password[0] == '3')
&& ((password[1] >= 'Q' && password[1] <= 'Z')
|| (password[1] >= 'a' && password[1] <= 'f')))
|| (size >= 21
&& (password[0] == '7')
&& (password[1] >= 'g')
&& (password[1] <= 'v'));
if (size < 16) {
return ProxyData::Status::Invalid;
} else if (valid) {
return ProxyData::Status::Valid;
}
return ProxyData::Status::Unsupported;
}
[[nodiscard]] bytes::vector SecretFromBase64UrlMtprotoPassword(
const QString &password) {
const auto result = QByteArray::fromBase64(
password.toLatin1(),
QByteArray::Base64UrlEncoding);
return bytes::make_vector(bytes::make_span(result));
}
} // namespace
// Precise timing functions / rand init
@ -248,105 +126,6 @@ namespace {
}
}
bool ProxyData::valid() const {
return status() == Status::Valid;
}
ProxyData::Status ProxyData::status() const {
if (type == Type::None || host.isEmpty() || !port) {
return Status::Invalid;
} else if (type == Type::Mtproto) {
return MtprotoPasswordStatus(password);
}
return Status::Valid;
}
bool ProxyData::supportsCalls() const {
return (type == Type::Socks5);
}
bool ProxyData::tryCustomResolve() const {
return (type == Type::Socks5 || type == Type::Mtproto)
&& !qthelp::is_ipv6(host)
&& !QRegularExpression(
qsl("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
).match(host).hasMatch();
}
bytes::vector ProxyData::secretFromMtprotoPassword() const {
Expects(type == Type::Mtproto);
if (IsHexMtprotoPassword(password)) {
return SecretFromHexMtprotoPassword(password);
} else if (IsBase64UrlMtprotoPassword(password)) {
return SecretFromBase64UrlMtprotoPassword(password);
}
return {};
}
ProxyData::operator bool() const {
return valid();
}
bool ProxyData::operator==(const ProxyData &other) const {
if (!valid()) {
return !other.valid();
}
return (type == other.type)
&& (host == other.host)
&& (port == other.port)
&& (user == other.user)
&& (password == other.password);
}
bool ProxyData::operator!=(const ProxyData &other) const {
return !(*this == other);
}
bool ProxyData::ValidMtprotoPassword(const QString &password) {
return MtprotoPasswordStatus(password) == Status::Valid;
}
ProxyData::Status ProxyData::MtprotoPasswordStatus(const QString &password) {
if (IsHexMtprotoPassword(password)) {
return HexMtprotoPasswordStatus(password);
} else if (IsBase64UrlMtprotoPassword(password)) {
return Base64UrlMtprotoPasswordStatus(password);
}
return Status::Invalid;
}
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {
if (!proxy.tryCustomResolve()
|| ipIndex < 0
|| ipIndex >= proxy.resolvedIPs.size()) {
return proxy;
}
return {
proxy.type,
proxy.resolvedIPs[ipIndex],
proxy.port,
proxy.user,
proxy.password
};
}
QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
if (proxy.type == ProxyData::Type::None) {
return QNetworkProxy::DefaultProxy;
} else if (proxy.type == ProxyData::Type::Mtproto) {
return QNetworkProxy::NoProxy;
}
return QNetworkProxy(
(proxy.type == ProxyData::Type::Socks5
? QNetworkProxy::Socks5Proxy
: QNetworkProxy::HttpProxy),
proxy.host,
proxy.port,
proxy.user,
proxy.password);
}
namespace ThirdParty {
void start() {

View file

@ -218,50 +218,6 @@ enum DBIWorkMode {
dbiwmWindowOnly = 2,
};
struct ProxyData {
enum class Settings {
System,
Enabled,
Disabled,
};
enum class Type {
None,
Socks5,
Http,
Mtproto,
};
enum class Status {
Valid,
Unsupported,
Invalid,
};
Type type = Type::None;
QString host;
uint32 port = 0;
QString user, password;
std::vector<QString> resolvedIPs;
crl::time resolvedExpireAt = 0;
[[nodiscard]] bool valid() const;
[[nodiscard]] Status status() const;
[[nodiscard]] bool supportsCalls() const;
[[nodiscard]] bool tryCustomResolve() const;
[[nodiscard]] bytes::vector secretFromMtprotoPassword() const;
[[nodiscard]] explicit operator bool() const;
[[nodiscard]] bool operator==(const ProxyData &other) const;
[[nodiscard]] bool operator!=(const ProxyData &other) const;
[[nodiscard]] static bool ValidMtprotoPassword(const QString &password);
[[nodiscard]] static Status MtprotoPasswordStatus(
const QString &password);
};
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex = 0);
QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
static const int MatrixRowShift = 40000;
inline int rowscount(int fullCount, int countPerRow) {

View file

@ -373,9 +373,9 @@ struct Data {
bool NotificationsDemoIsShown = false;
bool TryIPv6 = !Platform::IsWindows();
std::vector<ProxyData> ProxiesList;
ProxyData SelectedProxy;
ProxyData::Settings ProxySettings = ProxyData::Settings::System;
std::vector<MTP::ProxyData> ProxiesList;
MTP::ProxyData SelectedProxy;
MTP::ProxyData::Settings ProxySettings = MTP::ProxyData::Settings::System;
bool UseProxyForCalls = false;
base::Observable<void> ConnectionTypeChanged;
@ -501,9 +501,9 @@ DefineVar(Global, Notify::ScreenCorner, NotificationsCorner);
DefineVar(Global, bool, NotificationsDemoIsShown);
DefineVar(Global, bool, TryIPv6);
DefineVar(Global, std::vector<ProxyData>, ProxiesList);
DefineVar(Global, ProxyData, SelectedProxy);
DefineVar(Global, ProxyData::Settings, ProxySettings);
DefineVar(Global, std::vector<MTP::ProxyData>, ProxiesList);
DefineVar(Global, MTP::ProxyData, SelectedProxy);
DefineVar(Global, MTP::ProxyData::Settings, ProxySettings);
DefineVar(Global, bool, UseProxyForCalls);
DefineRefVar(Global, base::Observable<void>, ConnectionTypeChanged);

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/observer.h"
#include "base/call_delayed.h"
#include "ui/effects/animation_value.h"
#include "mtproto/mtproto_proxy_data.h"
class History;
@ -224,9 +225,9 @@ DeclareVar(Notify::ScreenCorner, NotificationsCorner);
DeclareVar(bool, NotificationsDemoIsShown);
DeclareVar(bool, TryIPv6);
DeclareVar(std::vector<ProxyData>, ProxiesList);
DeclareVar(ProxyData, SelectedProxy);
DeclareVar(ProxyData::Settings, ProxySettings);
DeclareVar(std::vector<MTP::ProxyData>, ProxiesList);
DeclareVar(MTP::ProxyData, SelectedProxy);
DeclareVar(MTP::ProxyData::Settings, ProxySettings);
DeclareVar(bool, UseProxyForCalls);
DeclareRefVar(base::Observable<void>, ConnectionTypeChanged);

View file

@ -35,8 +35,8 @@ void Account::watchProxyChanges() {
Core::App().proxyChanges(
) | rpl::start_with_next([=](const ProxyChange &change) {
const auto key = [&](const ProxyData &proxy) {
return (proxy.type == ProxyData::Type::Mtproto)
const auto key = [&](const MTP::ProxyData &proxy) {
return (proxy.type == MTP::ProxyData::Type::Mtproto)
? std::make_pair(proxy.host, proxy.port)
: std::make_pair(QString(), uint32(0));
};

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/connection.h"
#include "mtproto/details/mtproto_dc_key_creator.h"
#include "mtproto/session.h"
#include "mtproto/rsa_public_key.h"
#include "mtproto/rpc_sender.h"
@ -39,7 +40,6 @@ namespace {
constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL);
constexpr auto kIntSize = static_cast<int>(sizeof(mtpPrime));
constexpr auto kMaxModExpSize = 256;
constexpr auto kWaitForBetterTimeout = crl::time(2000);
constexpr auto kMinConnectedTimeout = crl::time(1000);
constexpr auto kMaxConnectedTimeout = crl::time(8000);
@ -57,6 +57,8 @@ constexpr auto kRequestConfigTimeout = crl::time(8000);
// Don't try to handle messages larger than this size.
constexpr auto kMaxMessageLength = 16 * 1024 * 1024;
using namespace details;
QString LogIdsVector(const QVector<MTPlong> &ids) {
if (!ids.size()) return "[]";
auto idsStr = QString("[%1").arg(ids.cbegin()->v);
@ -66,162 +68,6 @@ QString LogIdsVector(const QVector<MTPlong> &ids) {
return idsStr + "]";
}
bool IsGoodModExpFirst(
const openssl::BigNum &modexp,
const openssl::BigNum &prime) {
const auto diff = openssl::BigNum::Sub(prime, modexp);
if (modexp.failed() || prime.failed() || diff.failed()) {
return false;
}
constexpr auto kMinDiffBitsCount = 2048 - 64;
if (diff.isNegative()
|| diff.bitsSize() < kMinDiffBitsCount
|| modexp.bitsSize() < kMinDiffBitsCount
|| modexp.bytesSize() > kMaxModExpSize) {
return false;
}
return true;
}
bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) {
constexpr auto kGoodPrimeBitsCount = 2048;
if (prime.failed()
|| prime.isNegative()
|| prime.bitsSize() != kGoodPrimeBitsCount) {
LOG(("MTP Error: Bad prime bits count %1, expected %2."
).arg(prime.bitsSize()
).arg(kGoodPrimeBitsCount));
return false;
}
const auto context = openssl::Context();
if (!prime.isPrime(context)) {
LOG(("MTP Error: Bad prime."));
return false;
}
switch (g) {
case 2: {
const auto mod8 = prime.countModWord(8);
if (mod8 != 7) {
LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8));
return false;
}
} break;
case 3: {
const auto mod3 = prime.countModWord(3);
if (mod3 != 2) {
LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3));
return false;
}
} break;
case 4: break;
case 5: {
const auto mod5 = prime.countModWord(5);
if (mod5 != 1 && mod5 != 4) {
LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5));
return false;
}
} break;
case 6: {
const auto mod24 = prime.countModWord(24);
if (mod24 != 19 && mod24 != 23) {
LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24));
return false;
}
} break;
case 7: {
const auto mod7 = prime.countModWord(7);
if (mod7 != 3 && mod7 != 5 && mod7 != 6) {
LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7));
return false;
}
} break;
default: {
LOG(("BigNum PT Error: bad g value: %1").arg(g));
return false;
} break;
}
if (!openssl::BigNum(prime).subWord(1).divWord(2).isPrime(context)) {
LOG(("MTP Error: Bad (prime - 1) / 2."));
return false;
}
return true;
}
bool IsPrimeAndGood(bytes::const_span primeBytes, int g) {
static constexpr unsigned char GoodPrime[] = {
0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73,
0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F,
0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB,
0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37,
0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB,
0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64,
0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88,
0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4,
0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41,
0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54,
0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D,
0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4,
0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05,
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B };
if (!bytes::compare(bytes::make_span(GoodPrime), primeBytes)) {
if (g == 3 || g == 4 || g == 5 || g == 7) {
return true;
}
}
return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g);
}
bytes::vector CreateAuthKey(
bytes::const_span firstBytes,
bytes::const_span randomBytes,
bytes::const_span primeBytes) {
using openssl::BigNum;
const auto first = BigNum(firstBytes);
const auto prime = BigNum(primeBytes);
if (!IsGoodModExpFirst(first, prime)) {
LOG(("AuthKey Error: Bad first prime in CreateAuthKey()."));
return {};
}
return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes();
}
ModExpFirst CreateModExp(
int g,
bytes::const_span primeBytes,
bytes::const_span randomSeed) {
Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize);
using namespace openssl;
BigNum prime(primeBytes);
auto result = ModExpFirst();
result.randomPower.resize(ModExpFirst::kRandomPowerSize);
while (true) {
bytes::set_random(result.randomPower);
for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) {
result.randomPower[i] ^= randomSeed[i];
}
const auto modexp = BigNum::ModExp(
BigNum(g),
BigNum(result.randomPower),
prime);
if (IsGoodModExpFirst(modexp, prime)) {
result.modexp = modexp.getBytes();
return result;
}
}
}
void wrapInvokeAfter(SecureRequest &to, const SecureRequest &from, const RequestMap &haveSent, int32 skipBeforeRequest = 0) {
const auto afterId = *(mtpMsgId*)(from->after->data() + 4);
const auto i = afterId ? haveSent.constFind(afterId) : haveSent.cend();
@ -245,48 +91,6 @@ void wrapInvokeAfter(SecureRequest &to, const SecureRequest &from, const Request
}
}
bool parsePQ(const QByteArray &pqStr, QByteArray &pStr, QByteArray &qStr) {
if (pqStr.length() > 8) return false; // more than 64 bit pq
uint64 pq = 0, p, q;
const uchar *pqChars = (const uchar*)pqStr.constData();
for (uint32 i = 0, l = pqStr.length(); i < l; ++i) {
pq <<= 8;
pq |= (uint64)pqChars[i];
}
uint64 pqSqrt = (uint64)sqrtl((long double)pq), ySqr, y;
while (pqSqrt * pqSqrt > pq) --pqSqrt;
while (pqSqrt * pqSqrt < pq) ++pqSqrt;
for (ySqr = pqSqrt * pqSqrt - pq; ; ++pqSqrt, ySqr = pqSqrt * pqSqrt - pq) {
y = (uint64)sqrtl((long double)ySqr);
while (y * y > ySqr) --y;
while (y * y < ySqr) ++y;
if (!ySqr || y + pqSqrt >= pq) return false;
if (y * y == ySqr) {
p = pqSqrt + y;
q = (pqSqrt > y) ? (pqSqrt - y) : (y - pqSqrt);
break;
}
}
if (p > q) std::swap(p, q);
pStr.resize(4);
uchar *pChars = (uchar*)pStr.data();
for (uint32 i = 0; i < 4; ++i) {
*(pChars + 3 - i) = (uchar)(p & 0xFF);
p >>= 8;
}
qStr.resize(4);
uchar *qChars = (uchar*)qStr.data();
for (uint32 i = 0; i < 4; ++i) {
*(qChars + 3 - i) = (uchar)(q & 0xFF);
q >>= 8;
}
return true;
}
} // namespace
Connection::Connection(not_null<Instance*> instance) : _instance(instance) {
@ -411,6 +215,7 @@ void ConnectionPrivate::destroyAllConnections() {
_waitForReceivedTimer.cancel();
_waitForConnectedTimer.cancel();
_testConnections.clear();
_keyCreator = nullptr;
_connection = nullptr;
}
@ -1392,8 +1197,6 @@ void ConnectionPrivate::doDisconnect() {
}
}
clearAuthKeyData();
setState(DisconnectedState);
restarted = false;
}
@ -2568,7 +2371,7 @@ void ConnectionPrivate::removeTestConnection(
end(_testConnections));
}
void ConnectionPrivate::updateAuthKey() {
void ConnectionPrivate::updateAuthKey() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || !_connection) return;
@ -2597,7 +2400,7 @@ void ConnectionPrivate::updateAuthKey() {
DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), will be creating auth_key"));
lockKey();
auto &key = sessionData->getKey();
const auto &key = sessionData->getKey();
if (key) {
if (keyId != key->keyId()) clearMessages();
keyId = key->keyId();
@ -2609,19 +2412,49 @@ void ConnectionPrivate::updateAuthKey() {
_instance->checkIfKeyWasDestroyed(_shiftedDcId);
return;
}
_authKeyData = std::make_unique<ConnectionPrivate::AuthKeyCreateData>();
_authKeyStrings = std::make_unique<ConnectionPrivate::AuthKeyCreateStrings>();
const auto nonce = _authKeyData->nonce = rand_value<MTPint128>();
connect(_connection, &AbstractConnection::receivedData, [=] {
pqAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_pq..."));
lockFinished.unlock();
sendNotSecureRequest(MTPReq_pq_multi(nonce));
createDcKey();
}
void ConnectionPrivate::createDcKey() {
using Result = DcKeyCreator::Result;
using Error = DcKeyCreator::Error;
auto delegate = DcKeyCreator::Delegate();
delegate.done = [=](base::expected<Result, Error> result) {
_keyCreator = nullptr;
if (result) {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
sessionData->setSalt(result->serverSalt);
auto authKey = std::move(result->key);
DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(authKey->keyId()).arg(result->serverSalt));
sessionData->owner()->notifyKeyCreated(std::move(authKey)); // slot will call authKeyCreated()
sessionData->clear(_instance);
unlockKey();
} else if (result.error() == Error::UnknownPublicKey) {
if (_dcType == DcType::Cdn) {
LOG(("Warning: CDN public RSA key not found"));
requestCDNConfig();
} else {
LOG(("AuthKey Error: could not choose public RSA key"));
restart();
}
} else {
restart();
}
};
_keyCreator = std::make_unique<DcKeyCreator>(
BareDcId(_shiftedDcId),
getProtocolDcId(),
_connection.get(),
_instance->dcOptions(),
std::move(delegate));
}
void ConnectionPrivate::clearMessages() {
@ -2630,410 +2463,8 @@ void ConnectionPrivate::clearMessages() {
}
}
void ConnectionPrivate::pqAnswered() {
disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
MTPReq_pq::ResponseType res_pq;
if (!readNotSecureResponse(res_pq)) {
return restart();
}
auto &res_pq_data = res_pq.c_resPQ();
if (res_pq_data.vnonce() != _authKeyData->nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in res_pq)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&res_pq_data.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
return restart();
}
auto rsaKey = internal::RSAPublicKey();
if (!_instance->dcOptions()->getDcRSAKey(BareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints().v, &rsaKey)) {
if (_dcType == DcType::Cdn) {
LOG(("Warning: CDN public RSA key not found"));
requestCDNConfig();
return;
}
LOG(("AuthKey Error: could not choose public RSA key"));
return restart();
}
Assert(rsaKey.isValid());
_authKeyData->server_nonce = res_pq_data.vserver_nonce();
_authKeyData->new_nonce = rand_value<MTPint256>();
auto &pq = res_pq_data.vpq().v;
auto p = QByteArray();
auto q = QByteArray();
if (!internal::parsePQ(pq, p, q)) {
LOG(("AuthKey Error: could not factor pq!"));
DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str()));
return restart();
}
auto p_q_inner = MTP_p_q_inner_data_dc(
res_pq_data.vpq(),
MTP_bytes(std::move(p)),
MTP_bytes(std::move(q)),
_authKeyData->nonce,
_authKeyData->server_nonce,
_authKeyData->new_nonce,
MTP_int(getProtocolDcId()));
auto dhEncString = encryptPQInnerRSA(p_q_inner, rsaKey);
if (dhEncString.empty()) {
return restart();
}
connect(_connection, &AbstractConnection::receivedData, [=] {
dhParamsAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
sendNotSecureRequest(MTPReq_DH_params(
_authKeyData->nonce,
_authKeyData->server_nonce,
p_q_inner.c_p_q_inner_data_dc().vp(),
p_q_inner.c_p_q_inner_data_dc().vq(),
MTP_long(rsaKey.getFingerPrint()),
MTP_bytes(dhEncString)));
}
bytes::vector ConnectionPrivate::encryptPQInnerRSA(
const MTPP_Q_inner_data &data,
const internal::RSAPublicKey &key) {
auto p_q_inner_size = tl::count_length(data);
auto encSize = (p_q_inner_size >> 2) + 6;
if (encSize >= 65) {
auto tmp = mtpBuffer();
tmp.reserve(encSize);
data.write(tmp);
LOG(("AuthKey Error: too large data for RSA encrypt, size %1").arg(encSize * sizeof(mtpPrime)));
DEBUG_LOG(("AuthKey Error: bad data for RSA encrypt %1").arg(Logs::mb(&tmp[0], tmp.size() * 4).str()));
return {}; // can't be 255-byte string
}
auto encBuffer = mtpBuffer();
encBuffer.reserve(65); // 260 bytes
encBuffer.resize(6);
encBuffer[0] = 0;
data.write(encBuffer);
hashSha1(&encBuffer[6], p_q_inner_size, &encBuffer[1]);
if (encSize < 65) {
encBuffer.resize(65);
memset_rand(&encBuffer[encSize], (65 - encSize) * sizeof(mtpPrime));
}
auto bytes = bytes::make_span(encBuffer);
auto bytesToEncrypt = bytes.subspan(3, 256);
return key.encrypt(bytesToEncrypt);
}
void ConnectionPrivate::dhParamsAnswered() {
disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
MTPReq_DH_params::ResponseType res_DH_params;
if (!readNotSecureResponse(res_DH_params)) {
return restart();
}
switch (res_DH_params.type()) {
case mtpc_server_DH_params_ok: {
const auto &encDH(res_DH_params.c_server_DH_params_ok());
if (encDH.vnonce() != _authKeyData->nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_ok)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
return restart();
}
if (encDH.vserver_nonce() != _authKeyData->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(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
return restart();
}
auto &encDHStr = encDH.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 restart();
}
uint32 nlen = tl::count_length(_authKeyData->new_nonce), slen = tl::count_length(_authKeyData->server_nonce);
uchar tmp_aes[1024], sha1ns[20], sha1sn[20], sha1nn[20];
memcpy(tmp_aes, &_authKeyData->new_nonce, nlen);
memcpy(tmp_aes + nlen, &_authKeyData->server_nonce, slen);
memcpy(tmp_aes + nlen + slen, &_authKeyData->new_nonce, nlen);
memcpy(tmp_aes + nlen + slen + nlen, &_authKeyData->new_nonce, nlen);
hashSha1(tmp_aes, nlen + slen, sha1ns);
hashSha1(tmp_aes + nlen, nlen + slen, sha1sn);
hashSha1(tmp_aes + nlen + slen, nlen + nlen, sha1nn);
mtpBuffer decBuffer;
decBuffer.resize(encDHBufLen);
memcpy(_authKeyData->aesKey, sha1ns, 20);
memcpy(_authKeyData->aesKey + 20, sha1sn, 12);
memcpy(_authKeyData->aesIV, sha1sn + 12, 8);
memcpy(_authKeyData->aesIV + 8, sha1nn, 20);
memcpy(_authKeyData->aesIV + 28, &_authKeyData->new_nonce, 4);
aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, _authKeyData->aesKey, _authKeyData->aesIV);
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 restart();
}
const auto &dh_inner_data(dh_inner.c_server_DH_inner_data());
if (dh_inner_data.vnonce() != _authKeyData->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()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
return restart();
}
if (dh_inner_data.vserver_nonce() != _authKeyData->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()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
return restart();
}
uchar sha1Buffer[20];
if (memcmp(&decBuffer[0], hashSha1(&decBuffer[5], (to - from) * sizeof(mtpPrime), sha1Buffer), 20)) {
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(&_authKeyData->server_nonce, 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 16).str()).arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
return restart();
}
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 restart();
}
_authKeyStrings->dh_prime = bytes::make_vector(
dh_inner_data.vdh_prime().v);
_authKeyData->g = dh_inner_data.vg().v;
_authKeyStrings->g_a = bytes::make_vector(dh_inner_data.vg_a().v);
_authKeyData->retry_id = MTP_long(0);
_authKeyData->retries = 0;
} return dhClientParamsSend();
case mtpc_server_DH_params_fail: {
const auto &encDH(res_DH_params.c_server_DH_params_fail());
if (encDH.vnonce() != _authKeyData->nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_fail)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
return restart();
}
if (encDH.vserver_nonce() != _authKeyData->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(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
return restart();
}
uchar sha1Buffer[20];
if (encDH.vnew_nonce_hash() != *(MTPint128*)(hashSha1(&_authKeyData->new_nonce, 32, sha1Buffer) + 1)) {
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(&encDH.vnew_nonce_hash(), 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 32).str()));
return restart();
}
LOG(("AuthKey Error: server_DH_params_fail received!"));
} return restart();
}
LOG(("AuthKey Error: unknown server_DH_params received, typeId = %1").arg(res_DH_params.type()));
return restart();
}
void ConnectionPrivate::dhClientParamsSend() {
if (++_authKeyData->retries > 5) {
LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(_authKeyData->retries - 1));
return restart();
}
// gen rand 'b'
auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
bytes::set_random(randomSeed);
auto g_b_data = CreateModExp(_authKeyData->g, _authKeyStrings->dh_prime, randomSeed);
if (g_b_data.modexp.empty()) {
LOG(("AuthKey Error: could not generate good g_b."));
return restart();
}
auto computedAuthKey = CreateAuthKey(_authKeyStrings->g_a, g_b_data.randomPower, _authKeyStrings->dh_prime);
if (computedAuthKey.empty()) {
LOG(("AuthKey Error: could not generate auth_key."));
return restart();
}
AuthKey::FillData(_authKeyStrings->auth_key, computedAuthKey);
// count auth_key hashes - parts of sha1(auth_key)
auto auth_key_sha = hashSha1(_authKeyStrings->auth_key.data(), _authKeyStrings->auth_key.size());
memcpy(&_authKeyData->auth_key_aux_hash, auth_key_sha.data(), 8);
memcpy(&_authKeyData->auth_key_hash, auth_key_sha.data() + 12, 8);
auto client_dh_inner = MTP_client_DH_inner_data(_authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->retry_id, MTP_bytes(g_b_data.modexp));
auto sdhEncString = encryptClientDHInner(client_dh_inner);
connect(_connection, &AbstractConnection::receivedData, [=] {
dhClientParamsAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
sendNotSecureRequest(MTPSet_client_DH_params(
_authKeyData->nonce,
_authKeyData->server_nonce,
MTP_string(std::move(sdhEncString))));
}
std::string ConnectionPrivate::encryptClientDHInner(const MTPClient_DH_Inner_Data &data) {
auto client_dh_inner_size = tl::count_length(data);
auto encSize = (client_dh_inner_size >> 2) + 5;
auto encFullSize = encSize;
if (encSize & 0x03) {
encFullSize += 4 - (encSize & 0x03);
}
auto encBuffer = mtpBuffer();
encBuffer.reserve(encFullSize);
encBuffer.resize(5);
data.write(encBuffer);
hashSha1(&encBuffer[5], client_dh_inner_size, &encBuffer[0]);
if (encSize < encFullSize) {
encBuffer.resize(encFullSize);
memset_rand(&encBuffer[encSize], (encFullSize - encSize) * sizeof(mtpPrime));
}
auto sdhEncString = std::string(encFullSize * 4, ' ');
aesIgeEncryptRaw(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), _authKeyData->aesKey, _authKeyData->aesIV);
return sdhEncString;
}
void ConnectionPrivate::dhClientParamsAnswered() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
MTPSet_client_DH_params::ResponseType res_client_DH_params;
if (!readNotSecureResponse(res_client_DH_params)) {
lockFinished.unlock();
return restart();
}
switch (res_client_DH_params.type()) {
case mtpc_dh_gen_ok: {
const auto &resDH(res_client_DH_params.c_dh_gen_ok());
if (resDH.vnonce() != _authKeyData->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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
lockFinished.unlock();
return restart();
}
if (resDH.vserver_nonce() != _authKeyData->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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
lockFinished.unlock();
return restart();
}
_authKeyData->new_nonce_buf[32] = 1;
uchar sha1Buffer[20];
if (resDH.vnew_nonce_hash1() != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) {
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(&resDH.vnew_nonce_hash1(), 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str()));
lockFinished.unlock();
return restart();
}
uint64 salt1 = _authKeyData->new_nonce.l.l, salt2 = _authKeyData->server_nonce.l, serverSalt = salt1 ^ salt2;
sessionData->setSalt(serverSalt);
auto authKey = std::make_shared<AuthKey>(AuthKey::Type::Generated, BareDcId(_shiftedDcId), _authKeyStrings->auth_key);
DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(authKey->keyId()).arg(serverSalt));
sessionData->owner()->notifyKeyCreated(std::move(authKey)); // slot will call authKeyCreated()
sessionData->clear(_instance);
unlockKey();
} return;
case mtpc_dh_gen_retry: {
const auto &resDH(res_client_DH_params.c_dh_gen_retry());
if (resDH.vnonce() != _authKeyData->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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
lockFinished.unlock();
return restart();
}
if (resDH.vserver_nonce() != _authKeyData->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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
lockFinished.unlock();
return restart();
}
_authKeyData->new_nonce_buf[32] = 2;
uchar sha1Buffer[20];
if (resDH.vnew_nonce_hash2() != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) {
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(&resDH.vnew_nonce_hash2(), 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str()));
lockFinished.unlock();
return restart();
}
_authKeyData->retry_id = _authKeyData->auth_key_aux_hash;
} return dhClientParamsSend();
case mtpc_dh_gen_fail: {
const auto &resDH(res_client_DH_params.c_dh_gen_fail());
if (resDH.vnonce() != _authKeyData->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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str()));
lockFinished.unlock();
return restart();
}
if (resDH.vserver_nonce() != _authKeyData->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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str()));
lockFinished.unlock();
return restart();
}
_authKeyData->new_nonce_buf[32] = 3;
uchar sha1Buffer[20];
if (resDH.vnew_nonce_hash3() != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) {
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(&resDH.vnew_nonce_hash3(), 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str()));
lockFinished.unlock();
return restart();
}
LOG(("AuthKey Error: dh_gen_fail received!"));
}
lockFinished.unlock();
return restart();
}
LOG(("AuthKey Error: unknown set_client_DH_params_answer received, typeId = %1").arg(res_client_DH_params.type()));
lockFinished.unlock();
return restart();
}
void ConnectionPrivate::authKeyCreated() {
clearAuthKeyData();
_keyCreator = nullptr;
connect(_connection, &AbstractConnection::receivedData, [=] {
handleReceived();
@ -3052,33 +2483,6 @@ void ConnectionPrivate::authKeyCreated() {
emit needToSendAsync();
}
void ConnectionPrivate::clearAuthKeyData() {
auto zeroMemory = [](bytes::span bytes) {
#ifdef Q_OS_WIN2
SecureZeroMemory(bytes.data(), bytes.size());
#else // Q_OS_WIN
auto end = reinterpret_cast<char*>(bytes.data()) + bytes.size();
for (volatile auto p = reinterpret_cast<volatile char*>(bytes.data()); p != end; ++p) {
*p = 0;
}
#endif // Q_OS_WIN
};
if (_authKeyData) {
zeroMemory(gsl::make_span(reinterpret_cast<gsl::byte*>(_authKeyData.get()), sizeof(AuthKeyCreateData)));
_authKeyData.reset();
}
if (_authKeyStrings) {
if (!_authKeyStrings->dh_prime.empty()) {
zeroMemory(_authKeyStrings->dh_prime);
}
if (!_authKeyStrings->g_a.empty()) {
zeroMemory(_authKeyStrings->g_a);
}
zeroMemory(_authKeyStrings->auth_key);
_authKeyStrings.reset();
}
}
void ConnectionPrivate::onError(
not_null<AbstractConnection*> connection,
qint32 errorCode) {
@ -3104,7 +2508,7 @@ void ConnectionPrivate::handleError(int errorCode) {
_waitForConnectedTimer.cancel();
if (errorCode == -404) {
if (_dcType == DcType::Cdn) {
if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) {
LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId));
clearMessages();
keyId = kRecreateKeyId;
@ -3124,44 +2528,6 @@ void ConnectionPrivate::handleError(int errorCode) {
void ConnectionPrivate::onReadyData() {
}
template <typename Request>
void ConnectionPrivate::sendNotSecureRequest(const Request &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));
onSentSome(bytesSize);
}
template <typename Response>
bool ConnectionPrivate::readNotSecureResponse(Response &response) {
onReceivedSome();
if (_connection->received().empty()) {
LOG(("AuthKey Error: "
"trying to read response from empty received list"));
return false;
}
const auto buffer = std::move(_connection->received().front());
_connection->received().pop_front();
const auto answer = _connection->parseNotSecureResponse(buffer);
if (answer.empty()) {
return false;
}
auto from = answer.data();
return response.read(from, from + answer.size());
}
bool ConnectionPrivate::sendSecureRequest(
SecureRequest &&request,
bool needAnyResponse,
@ -3299,8 +2665,10 @@ void ConnectionPrivate::unlockKey() {
}
ConnectionPrivate::~ConnectionPrivate() {
clearAuthKeyData();
Assert(_finished && _connection == nullptr && _testConnections.empty());
Expects(_finished);
Expects(!_connection);
Expects(_testConnections.empty());
Expects(!_keyCreator);
}
void ConnectionPrivate::stop() {
@ -3316,29 +2684,4 @@ void ConnectionPrivate::stop() {
}
} // namespace internal
bool IsPrimeAndGood(bytes::const_span primeBytes, int g) {
return internal::IsPrimeAndGood(primeBytes, g);
}
bool IsGoodModExpFirst(
const openssl::BigNum &modexp,
const openssl::BigNum &prime) {
return internal::IsGoodModExpFirst(modexp, prime);
}
ModExpFirst CreateModExp(
int g,
bytes::const_span primeBytes,
bytes::const_span randomSeed) {
return internal::CreateModExp(g, primeBytes, randomSeed);
}
bytes::vector CreateAuthKey(
bytes::const_span firstBytes,
bytes::const_span randomBytes,
bytes::const_span primeBytes) {
return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes);
}
} // namespace MTP

View file

@ -15,31 +15,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
namespace MTP {
namespace details {
class DcKeyCreator;
} // namespace details
// How much time to wait for some more requests, when sending msg acks.
constexpr auto kAckSendWaiting = crl::time(10000);
class Instance;
[[nodiscard]] bool IsPrimeAndGood(bytes::const_span primeBytes, int g);
struct ModExpFirst {
static constexpr auto kRandomPowerSize = 256;
bytes::vector modexp;
bytes::vector randomPower;
};
[[nodiscard]] bool IsGoodModExpFirst(
const openssl::BigNum &modexp,
const openssl::BigNum &prime);
[[nodiscard]] ModExpFirst CreateModExp(
int g,
bytes::const_span primeBytes,
bytes::const_span randomSeed);
[[nodiscard]] bytes::vector CreateAuthKey(
bytes::const_span firstBytes,
bytes::const_span randomBytes,
bytes::const_span primeBytes);
namespace internal {
class AbstractConnection;
@ -139,11 +123,6 @@ public slots:
void onReadyData();
// Auth key creation packet receive slots
void pqAnswered();
void dhParamsAnswered();
void dhClientParamsAnswered();
// General packet receive slot, connected to conn->receivedData signal
void handleReceived();
@ -214,8 +193,6 @@ private:
bool setState(int32 state, int32 ifState = Connection::UpdateAlways);
bytes::vector encryptPQInnerRSA(const MTPP_Q_inner_data &data, const internal::RSAPublicKey &key);
std::string encryptClientDHInner(const MTPClient_DH_Inner_Data &data);
void appendTestConnection(
DcOptions::Variants::Protocol protocol,
const QString &ip,
@ -231,11 +208,11 @@ private:
void resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
void resendMany(QVector<quint64> msgIds, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
template <typename Request>
void sendNotSecureRequest(const Request &request);
template <typename Response>
[[nodiscard]] bool readNotSecureResponse(Response &response);
void createDcKey();
void resetSession();
void lockKey();
void unlockKey();
void authKeyCreated();
not_null<Instance*> _instance;
DcType _dcType = DcType::Regular;
@ -244,7 +221,6 @@ private:
int32 _state = DisconnectedState;
bool _needSessionReset = false;
void resetSession();
ShiftedDcId _shiftedDcId = 0;
not_null<Connection*> _owner;
@ -283,40 +259,8 @@ private:
std::unique_ptr<ConnectionOptions> _connectionOptions;
bool myKeyLock = false;
void lockKey();
void unlockKey();
// Auth key creation fields and methods
struct AuthKeyCreateData {
AuthKeyCreateData()
: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf))
, auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf + 33)) {
}
MTPint128 nonce, server_nonce;
uchar new_nonce_buf[41] = { 0 }; // 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash
MTPint256 &new_nonce;
MTPlong &auth_key_aux_hash;
uint32 retries = 0;
MTPlong retry_id;
int32 g = 0;
uchar aesKey[32] = { 0 };
uchar aesIV[32] = { 0 };
MTPlong auth_key_hash;
};
struct AuthKeyCreateStrings {
bytes::vector dh_prime;
bytes::vector g_a;
AuthKey::Data auth_key = { { gsl::byte{} } };
};
std::unique_ptr<AuthKeyCreateData> _authKeyData;
std::unique_ptr<AuthKeyCreateStrings> _authKeyStrings;
void dhClientParamsSend();
void authKeyCreated();
void clearAuthKeyData();
std::unique_ptr<details::DcKeyCreator> _keyCreator;
};

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/connection_resolving.h"
#include "mtproto/session.h"
#include "base/unixtime.h"
#include "base/openssl_help.h"
namespace MTP {
namespace internal {
@ -187,5 +188,11 @@ ConnectionPointer AbstractConnection::Create(
return result;
}
uint32 AbstractConnection::extendedNotSecurePadding() const {
return requiresExtendedPadding()
? uint32(openssl::RandomValue<uchar>() & 0x3F)
: 0;
}
} // namespace internal
} // namespace MTP

View file

@ -8,8 +8,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "mtproto/dc_options.h"
#include "mtproto/mtproto_proxy_data.h"
#include "base/bytes.h"
#include <QtCore/QObject>
#include <QtCore/QThread>
namespace MTP {
class Instance;
@ -60,17 +64,17 @@ public:
virtual ~AbstractConnection() = default;
// virtual constructor
static ConnectionPointer Create(
[[nodiscard]] static ConnectionPointer Create(
not_null<Instance*> instance,
DcOptions::Variants::Protocol protocol,
QThread *thread,
const bytes::vector &secret,
const ProxyData &proxy);
virtual ConnectionPointer clone(const ProxyData &proxy) = 0;
[[nodiscard]] virtual ConnectionPointer clone(const ProxyData &proxy) = 0;
virtual crl::time pingTime() const = 0;
virtual crl::time fullConnectTimeout() const = 0;
[[nodiscard]] virtual crl::time pingTime() const = 0;
[[nodiscard]] virtual crl::time fullConnectTimeout() const = 0;
virtual void sendData(mtpBuffer &&buffer) = 0;
virtual void disconnectFromServer() = 0;
virtual void connectToServer(
@ -80,41 +84,41 @@ public:
int16 protocolDcId) = 0;
virtual void timedOut() {
}
virtual bool isConnected() const = 0;
virtual bool usingHttpWait() {
[[nodiscard]] virtual bool isConnected() const = 0;
[[nodiscard]] virtual bool usingHttpWait() {
return false;
}
virtual bool needHttpWait() {
[[nodiscard]] virtual bool needHttpWait() {
return false;
}
virtual bool requiresExtendedPadding() const {
[[nodiscard]] virtual bool requiresExtendedPadding() const {
return false;
}
virtual int32 debugState() const = 0;
[[nodiscard]] virtual int32 debugState() const = 0;
virtual QString transport() const = 0;
virtual QString tag() const = 0;
[[nodiscard]] virtual QString transport() const = 0;
[[nodiscard]] virtual QString tag() const = 0;
void setSentEncrypted() {
_sentEncrypted = true;
}
using BuffersQueue = std::deque<mtpBuffer>;
BuffersQueue &received() {
[[nodiscard]] BuffersQueue &received() {
return _receivedQueue;
}
template <typename Request>
mtpBuffer prepareNotSecurePacket(
[[nodiscard]] mtpBuffer prepareNotSecurePacket(
const Request &request,
mtpMsgId newId) const;
mtpBuffer prepareSecurePacket(
[[nodiscard]] mtpBuffer prepareSecurePacket(
uint64 keyId,
MTPint128 msgKey,
uint32 size) const;
gsl::span<const mtpPrime> parseNotSecureResponse(
[[nodiscard]] gsl::span<const mtpPrime> parseNotSecureResponse(
const mtpBuffer &buffer) const;
// Used to emit error(...) with no real code from the server.
@ -139,8 +143,12 @@ protected:
// first we always send fake MTPReq_pq to see if connection works at all
// we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one
mtpBuffer preparePQFake(const MTPint128 &nonce) const;
std::optional<MTPResPQ> readPQFakeReply(const mtpBuffer &buffer) const;
[[nodiscard]] mtpBuffer preparePQFake(const MTPint128 &nonce) const;
[[nodiscard]] std::optional<MTPResPQ> readPQFakeReply(
const mtpBuffer &buffer) const;
private:
[[nodiscard]] uint32 extendedNotSecurePadding() const;
};
@ -149,9 +157,7 @@ mtpBuffer AbstractConnection::prepareNotSecurePacket(
const Request &request,
mtpMsgId newId) const {
const auto intsSize = tl::count_length(request) >> 2;
const auto intsPadding = requiresExtendedPadding()
? uint32(rand_value<uchar>() & 0x3F)
: 0;
const auto intsPadding = extendedNotSecurePadding();
auto result = mtpBuffer();
constexpr auto kTcpPrefixInts = 2;
@ -176,10 +182,10 @@ mtpBuffer AbstractConnection::prepareNotSecurePacket(
*messageLength = (result.size() - kPrefixInts + intsPadding) << 2;
if (intsPadding > 0) {
result.resize(result.size() + intsPadding);
memset_rand(
result.data() + result.size() - intsPadding,
intsPadding * sizeof(mtpPrime));
const auto skipPrimes = result.size();
result.resize(skipPrimes + intsPadding);
const auto skipBytes = skipPrimes * sizeof(mtpPrime);
bytes::set_random(bytes::make_span(result).subspan(skipBytes));
}
return result;

View file

@ -105,8 +105,8 @@ void DcOptions::readBuiltInPublicKeys() {
for (const auto key : PublicRSAKeys) {
const auto keyBytes = bytes::make_span(key, strlen(key));
auto parsed = internal::RSAPublicKey(keyBytes);
if (parsed.isValid()) {
_publicKeys.emplace(parsed.getFingerPrint(), std::move(parsed));
if (parsed.valid()) {
_publicKeys.emplace(parsed.fingerprint(), std::move(parsed));
} else {
LOG(("MTP Error: could not read this public RSA key:"));
LOG((key));
@ -505,8 +505,8 @@ void DcOptions::constructFromSerialized(const QByteArray &serialized) {
}
auto key = internal::RSAPublicKey(n, e);
if (key.isValid()) {
_cdnPublicKeys[dcId].emplace(key.getFingerPrint(), std::move(key));
if (key.valid()) {
_cdnPublicKeys[dcId].emplace(key.fingerprint(), std::move(key));
} else {
LOG(("MTP Error: Could not read valid CDN public key."));
}
@ -554,9 +554,9 @@ void DcOptions::setCDNConfig(const MTPDcdnConfig &config) {
const auto &keyData = publicKey.c_cdnPublicKey();
const auto keyBytes = bytes::make_span(keyData.vpublic_key().v);
auto key = internal::RSAPublicKey(keyBytes);
if (key.isValid()) {
if (key.valid()) {
_cdnPublicKeys[keyData.vdc_id().v].emplace(
key.getFingerPrint(),
key.fingerprint(),
std::move(key));
} else {
LOG(("MTP Error: could not read this public RSA key:"));
@ -570,20 +570,22 @@ bool DcOptions::hasCDNKeysForDc(DcId dcId) const {
return _cdnPublicKeys.find(dcId) != _cdnPublicKeys.cend();
}
bool DcOptions::getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const {
auto findKey = [&fingerprints, &result](const std::map<uint64, internal::RSAPublicKey> &keys) {
for_const (auto &fingerprint, fingerprints) {
auto it = keys.find(static_cast<uint64>(fingerprint.v));
internal::RSAPublicKey DcOptions::getDcRSAKey(
DcId dcId,
const QVector<MTPlong> &fingerprints) const {
const auto findKey = [&](
const std::map<uint64, internal::RSAPublicKey> &keys) {
for (const auto &fingerprint : fingerprints) {
const auto it = keys.find(static_cast<uint64>(fingerprint.v));
if (it != keys.cend()) {
*result = it->second;
return true;
return it->second;
}
}
return false;
return internal::RSAPublicKey();
};
{
ReadLocker lock(this);
auto it = _cdnPublicKeys.find(dcId);
const auto it = _cdnPublicKeys.find(dcId);
if (it != _cdnPublicKeys.cend()) {
return findKey(it->second);
}

View file

@ -10,9 +10,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/observer.h"
#include "base/bytes.h"
#include "mtproto/rsa_public_key.h"
#include <QtCore/QReadWriteLock>
#include <string>
#include <vector>
#include <map>
#include <set>
namespace MTP {
@ -90,8 +93,10 @@ public:
DcType dcType(ShiftedDcId shiftedDcId) const;
void setCDNConfig(const MTPDcdnConfig &config);
bool hasCDNKeysForDc(DcId dcId) const;
bool getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const;
[[nodiscard]] bool hasCDNKeysForDc(DcId dcId) const;
[[nodiscard]] internal::RSAPublicKey getDcRSAKey(
DcId dcId,
const QVector<MTPlong> &fingerprints) const;
// Debug feature for now.
bool loadFromFile(const QString &path);

View file

@ -0,0 +1,31 @@
/*
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_checker.h"
#include "mtproto/mtp_instance.h"
#include <QtCore/QPointer>
namespace MTP::details {
DcKeyChecker::DcKeyChecker(
not_null<Instance*> instance,
DcId dcId,
const AuthKeyPtr &key,
FnMut<void()> destroyMe)
: _instance(instance)
, _dcId(dcId)
, _key(key)
, _destroyMe(std::move(destroyMe)) {
crl::on_main(instance, [=] {
auto destroy = std::move(_destroyMe);
destroy();
});
}
} // namespace MTP::details

View file

@ -0,0 +1,35 @@
/*
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/auth_key.h"
namespace MTP {
class Instance;
} // namespace MTP
namespace MTP::details {
class DcKeyChecker final {
public:
DcKeyChecker(
not_null<Instance*> instance,
DcId dcId,
const AuthKeyPtr &key,
FnMut<void()> destroyMe);
private:
not_null<Instance*> _instance;
DcId _dcId = 0;
AuthKeyPtr _key;
FnMut<void()> _destroyMe;
};
} // namespace MTP::details

View file

@ -0,0 +1,572 @@
/*
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/connection_abstract.h"
#include "mtproto/mtproto_dh_utils.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "scheme.h"
#include "logs.h"
namespace MTP::details {
namespace {
struct ParsedPQ {
QByteArray p;
QByteArray q;
};
[[nodiscard]] ParsedPQ ParsePQ(const QByteArray &pqStr) {
if (pqStr.length() > 8) {
// More than 64 bit pq.
return ParsedPQ();
}
uint64 pq = 0, p, q;
const uchar *pqChars = (const uchar*)pqStr.constData();
for (uint32 i = 0, l = pqStr.length(); i < l; ++i) {
pq <<= 8;
pq |= (uint64)pqChars[i];
}
uint64 pqSqrt = (uint64)sqrtl((long double)pq), ySqr, y;
while (pqSqrt * pqSqrt > pq) --pqSqrt;
while (pqSqrt * pqSqrt < pq) ++pqSqrt;
for (ySqr = pqSqrt * pqSqrt - pq; ; ++pqSqrt, ySqr = pqSqrt * pqSqrt - pq) {
y = (uint64)sqrtl((long double)ySqr);
while (y * y > ySqr) --y;
while (y * y < ySqr) ++y;
if (!ySqr || y + pqSqrt >= pq) {
return ParsedPQ();
}
if (y * y == ySqr) {
p = pqSqrt + y;
q = (pqSqrt > y) ? (pqSqrt - y) : (y - pqSqrt);
break;
}
}
if (p > q) std::swap(p, q);
auto pStr = QByteArray(4, Qt::Uninitialized);
uchar *pChars = (uchar*)pStr.data();
for (uint32 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 (uint32 i = 0; i < 4; ++i) {
*(qChars + 3 - i) = (uchar)(q & 0xFF);
q >>= 8;
}
return { pStr, qStr };
}
[[nodiscard]] bytes::vector EncryptPQInnerRSA(
const MTPP_Q_inner_data &data,
const RSAPublicKey &key) {
constexpr auto kSkipPrimes = 6;
constexpr auto kMaxPrimes = 65; // 260 bytes
const auto p_q_inner_size = tl::count_length(data);
const auto sizeInPrimes = (p_q_inner_size >> 2) + kSkipPrimes;
if (sizeInPrimes >= kMaxPrimes) {
auto tmp = mtpBuffer();
tmp.reserve(sizeInPrimes);
data.write(tmp);
LOG(("AuthKey Error: too large data for RSA encrypt, size %1").arg(sizeInPrimes * sizeof(mtpPrime)));
DEBUG_LOG(("AuthKey Error: bad data for RSA encrypt %1").arg(Logs::mb(&tmp[0], tmp.size() * 4).str()));
return {}; // can't be 255-byte string
}
auto encBuffer = mtpBuffer();
encBuffer.reserve(kMaxPrimes);
encBuffer.resize(kSkipPrimes);
data.write(encBuffer);
encBuffer.resize(kMaxPrimes);
const auto bytes = bytes::make_span(encBuffer);
const auto hashSrc = bytes.subspan(
kSkipPrimes * sizeof(mtpPrime),
p_q_inner_size);
bytes::copy(bytes.subspan(sizeof(mtpPrime)), openssl::Sha1(hashSrc));
bytes::set_random(bytes.subspan(sizeInPrimes * sizeof(mtpPrime)));
const auto bytesToEncrypt = bytes.subspan(3, 256);
return key.encrypt(bytesToEncrypt);
}
[[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::DcKeyCreator(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions,
Delegate delegate)
: _connection(connection)
, _dcOptions(dcOptions)
, _dcId(dcId)
, _protocolDcId(protocolDcId)
, _delegate(std::move(delegate)) {
Expects(_delegate.done != nullptr);
_data.nonce = openssl::RandomValue<MTPint128>();
pqSend();
}
DcKeyCreator::~DcKeyCreator() {
const auto clearBytes = [](bytes::span bytes) {
OPENSSL_cleanse(bytes.data(), bytes.size());
};
OPENSSL_cleanse(&_data, sizeof(_data));
clearBytes(_dhPrime);
clearBytes(_g_a);
clearBytes(_authKey);
}
void DcKeyCreator::pqSend() {
QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
pqAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_pq..."));
sendNotSecureRequest(MTPReq_pq_multi(_data.nonce));
}
void DcKeyCreator::pqAnswered() {
QObject::disconnect(
_connection,
&AbstractConnection::receivedData,
nullptr,
nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
MTPReq_pq::ResponseType res_pq;
if (!readNotSecureResponse(res_pq)) {
return failed();
}
auto &res_pq_data = res_pq.c_resPQ();
if (res_pq_data.vnonce() != _data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in res_pq)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&res_pq_data.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
const auto rsaKey = _dcOptions->getDcRSAKey(
_dcId,
res_pq.c_resPQ().vserver_public_key_fingerprints().v);
if (!rsaKey.valid()) {
return failed(Error::UnknownPublicKey);
}
_data.server_nonce = res_pq_data.vserver_nonce();
_data.new_nonce = openssl::RandomValue<MTPint256>();
const auto &pq = res_pq_data.vpq().v;
const auto parsed = ParsePQ(res_pq_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();
}
auto p_q_inner = MTP_p_q_inner_data_dc(
res_pq_data.vpq(),
MTP_bytes(parsed.p),
MTP_bytes(parsed.q),
_data.nonce,
_data.server_nonce,
_data.new_nonce,
MTP_int(_protocolDcId));
const auto dhEncString = EncryptPQInnerRSA(p_q_inner, rsaKey);
if (dhEncString.empty()) {
return failed();
}
QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
dhParamsAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
sendNotSecureRequest(MTPReq_DH_params(
_data.nonce,
_data.server_nonce,
p_q_inner.c_p_q_inner_data_dc().vp(),
p_q_inner.c_p_q_inner_data_dc().vq(),
MTP_long(rsaKey.fingerprint()),
MTP_bytes(dhEncString)));
}
void DcKeyCreator::dhParamsAnswered() {
QObject::disconnect(
_connection,
&AbstractConnection::receivedData,
nullptr,
nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
MTPReq_DH_params::ResponseType res_DH_params;
if (!readNotSecureResponse(res_DH_params)) {
return failed();
}
switch (res_DH_params.type()) {
case mtpc_server_DH_params_ok: {
const auto &encDH(res_DH_params.c_server_DH_params_ok());
if (encDH.vnonce() != _data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_ok)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (encDH.vserver_nonce() != _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(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
return failed();
}
auto &encDHStr = encDH.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(_data.new_nonce);
const auto slen = sizeof(_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(&_data.new_nonce));
bytes::copy(tmp_aes.subspan(nlen), bytes::object_as_span(&_data.server_nonce));
bytes::copy(tmp_aes.subspan(nlen + slen), bytes::object_as_span(&_data.new_nonce));
bytes::copy(tmp_aes.subspan(nlen + slen + nlen), bytes::object_as_span(&_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(_data.aesKey);
const auto aesIV = bytes::make_span(_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(&_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() != _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()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (dh_inner_data.vserver_nonce() != _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()).arg(Logs::mb(&_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(&_data.server_nonce, 16).str()).arg(Logs::mb(&_data.new_nonce, 16).str()).arg(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();
}
_dhPrime = bytes::make_vector(
dh_inner_data.vdh_prime().v);
_data.g = dh_inner_data.vg().v;
_g_a = bytes::make_vector(dh_inner_data.vg_a().v);
_data.retry_id = MTP_long(0);
_data.retries = 0;
} return dhClientParamsSend();
case mtpc_server_DH_params_fail: {
const auto &encDH(res_DH_params.c_server_DH_params_fail());
if (encDH.vnonce() != _data.nonce) {
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_fail)!"));
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (encDH.vserver_nonce() != _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(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
return failed();
}
if (encDH.vnew_nonce_hash() != NonceDigest(bytes::object_as_span(&_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(&encDH.vnew_nonce_hash(), 16).str()).arg(Logs::mb(&_data.new_nonce, 32).str()));
return failed();
}
LOG(("AuthKey Error: server_DH_params_fail received!"));
} return failed();
}
LOG(("AuthKey Error: unknown server_DH_params received, typeId = %1").arg(res_DH_params.type()));
return failed();
}
void DcKeyCreator::dhClientParamsSend() {
if (++_data.retries > 5) {
LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(_data.retries - 1));
return failed();
}
// gen rand 'b'
auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
bytes::set_random(randomSeed);
auto g_b_data = CreateModExp(_data.g, _dhPrime, randomSeed);
if (g_b_data.modexp.empty()) {
LOG(("AuthKey Error: could not generate good g_b."));
return failed();
}
auto computedAuthKey = CreateAuthKey(_g_a, g_b_data.randomPower, _dhPrime);
if (computedAuthKey.empty()) {
LOG(("AuthKey Error: could not generate auth_key."));
return failed();
}
AuthKey::FillData(_authKey, computedAuthKey);
auto auth_key_sha = openssl::Sha1(_authKey);
memcpy(&_data.auth_key_aux_hash, auth_key_sha.data(), 8);
memcpy(&_data.auth_key_hash, auth_key_sha.data() + 12, 8);
const auto client_dh_inner = MTP_client_DH_inner_data(
_data.nonce,
_data.server_nonce,
_data.retry_id,
MTP_bytes(g_b_data.modexp));
auto sdhEncString = EncryptClientDHInner(
client_dh_inner,
_data.aesKey.data(),
_data.aesIV.data());
QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
dhClientParamsAnswered();
});
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
sendNotSecureRequest(MTPSet_client_DH_params(
_data.nonce,
_data.server_nonce,
MTP_string(std::move(sdhEncString))));
}
void DcKeyCreator::dhClientParamsAnswered() {
QObject::disconnect(
_connection,
&AbstractConnection::receivedData,
nullptr,
nullptr);
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
MTPSet_client_DH_params::ResponseType res_client_DH_params;
if (!readNotSecureResponse(res_client_DH_params)) {
return failed();
}
switch (res_client_DH_params.type()) {
case mtpc_dh_gen_ok: {
const auto &resDH(res_client_DH_params.c_dh_gen_ok());
if (resDH.vnonce() != _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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (resDH.vserver_nonce() != _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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
return failed();
}
_data.new_nonce_buf[32] = bytes::type(1);
if (resDH.vnew_nonce_hash1() != NonceDigest(_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(&resDH.vnew_nonce_hash1(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
return failed();
}
uint64 salt1 = _data.new_nonce.l.l, salt2 = _data.server_nonce.l;
done(salt1 ^ salt2);
} return;
case mtpc_dh_gen_retry: {
const auto &resDH(res_client_DH_params.c_dh_gen_retry());
if (resDH.vnonce() != _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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (resDH.vserver_nonce() != _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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
return failed();
}
_data.new_nonce_buf[32] = bytes::type(2);
uchar sha1Buffer[20];
if (resDH.vnew_nonce_hash2() != NonceDigest(_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(&resDH.vnew_nonce_hash2(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
return failed();
}
_data.retry_id = _data.auth_key_aux_hash;
} return dhClientParamsSend();
case mtpc_dh_gen_fail: {
const auto &resDH(res_client_DH_params.c_dh_gen_fail());
if (resDH.vnonce() != _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(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
return failed();
}
if (resDH.vserver_nonce() != _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(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
return failed();
}
_data.new_nonce_buf[32] = bytes::type(3);
uchar sha1Buffer[20];
if (resDH.vnew_nonce_hash3() != NonceDigest(_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(&resDH.vnew_nonce_hash3(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
return failed();
}
LOG(("AuthKey Error: dh_gen_fail received!"));
} return failed();
}
LOG(("AuthKey Error: unknown set_client_DH_params_answer received, typeId = %1").arg(res_client_DH_params.type()));
return failed();
}
template <typename Request>
void DcKeyCreator::sendNotSecureRequest(const Request &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 Response>
bool DcKeyCreator::readNotSecureResponse(Response &response) {
if (_delegate.receivedSome) {
_delegate.receivedSome();
}
if (_connection->received().empty()) {
LOG(("AuthKey Error: "
"trying to read response from empty received list"));
return false;
}
const auto buffer = std::move(_connection->received().front());
_connection->received().pop_front();
const auto answer = _connection->parseNotSecureResponse(buffer);
if (answer.empty()) {
return false;
}
auto from = answer.data();
return response.read(from, from + answer.size());
}
void DcKeyCreator::failed(Error error) {
auto onstack = std::move(_delegate.done);
onstack(tl::unexpected(error));
}
void DcKeyCreator::done(uint64 serverSalt) {
auto result = Result();
result.key = std::make_shared<AuthKey>(
AuthKey::Type::Generated,
_dcId,
_authKey);
result.serverSalt = serverSalt;
auto onstack = std::move(_delegate.done);
onstack(std::move(result));
}
} // namespace MTP::details

View file

@ -0,0 +1,102 @@
/*
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/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 {
using namespace ::MTP::internal;
class DcKeyCreator final {
public:
enum class Error {
UnknownPublicKey,
Other,
};
struct Result {
AuthKeyPtr key;
uint64 serverSalt = 0;
};
struct Delegate {
FnMut<void(base::expected<Result, Error>)> done;
Fn<void(uint64)> sentSome;
Fn<void()> receivedSome;
};
DcKeyCreator(
DcId dcId,
int16 protocolDcId,
not_null<AbstractConnection*> connection,
not_null<DcOptions*> dcOptions,
Delegate delegate);
~DcKeyCreator();
private:
// Auth key creation fields and methods
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;
uint32 retries = 0;
MTPlong retry_id;
int32 g = 0;
bytes::array<32> aesKey;
bytes::array<32> aesIV;
MTPlong auth_key_hash;
};
template <typename Request>
void sendNotSecureRequest(const Request &request);
template <typename Response>
[[nodiscard]] bool readNotSecureResponse(Response &response);
void pqSend();
void pqAnswered();
void dhParamsAnswered();
void dhClientParamsSend();
void dhClientParamsAnswered();
void failed(Error error = Error::Other);
void done(uint64 serverSalt);
const not_null<AbstractConnection*> _connection;
const not_null<DcOptions*> _dcOptions;
const DcId _dcId;
const int16 _protocolDcId = 0;
Delegate _delegate;
Data _data;
bytes::vector _dhPrime;
bytes::vector _g_a;
AuthKey::Data _authKey = { { gsl::byte{} } };
FnMut<void(base::expected<Result, Error>)> _done;
};
} // namespace MTP::details

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/mtp_instance.h"
#include "mtproto/details/mtproto_dc_key_checker.h"
#include "mtproto/session.h"
#include "mtproto/dc_options.h"
#include "mtproto/dcenter.h"
@ -74,7 +75,6 @@ public:
void cancel(mtpRequestId requestId);
[[nodiscard]] int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms
void killSession(ShiftedDcId shiftedDcId);
void killSession(std::unique_ptr<internal::Session> session);
void stopSession(ShiftedDcId shiftedDcId);
void reInitConnection(DcId dcId);
void logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail);
@ -133,6 +133,7 @@ public:
void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
void performKeyDestroy(ShiftedDcId shiftedDcId);
void completedKeyDestroy(ShiftedDcId shiftedDcId);
void checkMainDcKey();
void clearKilledSessions();
void prepareToDestroy();
@ -227,6 +228,8 @@ private:
base::Timer _checkDelayedTimer;
std::unique_ptr<details::DcKeyChecker> _mainDcKeyChecker;
// Debug flag to find out how we end up crashing.
bool MustNotCreateSessions = false;
@ -1102,13 +1105,13 @@ void Instance::Private::globalCallback(const mtpPrime *from, const mtpPrime *end
[[maybe_unused]] bool result = (*_globalHandler.onDone)(0, from, end);
}
void Instance::Private::onStateChange(int32 dcWithShift, int32 state) {
void Instance::Private::onStateChange(ShiftedDcId dcWithShift, int32 state) {
if (_stateChangedHandler) {
_stateChangedHandler(dcWithShift, state);
}
}
void Instance::Private::onSessionReset(int32 dcWithShift) {
void Instance::Private::onSessionReset(ShiftedDcId dcWithShift) {
if (_sessionResetHandler) {
_sessionResetHandler(dcWithShift);
}
@ -1505,6 +1508,26 @@ void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) {
}
}
void Instance::Private::checkMainDcKey() {
if (_mainDcKeyChecker) {
return;
}
const auto id = mainDcId();
const auto key = [&] {
QReadLocker lock(&_keysForWriteLock);
const auto i = _keysForWrite.find(id);
return (i != end(_keysForWrite)) ? i->second : AuthKeyPtr();
}();
if (!key) {
return;
}
_mainDcKeyChecker = std::make_unique<details::DcKeyChecker>(
_instance,
id,
key,
[=] { _mainDcKeyChecker = nullptr; });
}
void Instance::Private::setUpdatesHandler(RPCDoneHandlerPtr onDone) {
_globalHandler.onDone = onDone;
}
@ -1751,6 +1774,10 @@ void Instance::checkIfKeyWasDestroyed(ShiftedDcId shiftedDcId) {
LOG(("MTP Info: checkIfKeyWasDestroyed on destroying key %1, "
"assuming it is destroyed.").arg(shiftedDcId));
_private->completedKeyDestroy(shiftedDcId);
} else if (BareDcId(shiftedDcId) == mainDcId()) {
LOG(("MTP Info: checkIfKeyWasDestroyed for main dc %1, "
"checking.").arg(shiftedDcId));
_private->checkMainDcKey();
}
});
}

View file

@ -0,0 +1,173 @@
/*
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/mtproto_dh_utils.h"
namespace MTP {
namespace {
constexpr auto kMaxModExpSize = 256;
bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) {
constexpr auto kGoodPrimeBitsCount = 2048;
if (prime.failed()
|| prime.isNegative()
|| prime.bitsSize() != kGoodPrimeBitsCount) {
LOG(("MTP Error: Bad prime bits count %1, expected %2."
).arg(prime.bitsSize()
).arg(kGoodPrimeBitsCount));
return false;
}
const auto context = openssl::Context();
if (!prime.isPrime(context)) {
LOG(("MTP Error: Bad prime."));
return false;
}
switch (g) {
case 2: {
const auto mod8 = prime.countModWord(8);
if (mod8 != 7) {
LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8));
return false;
}
} break;
case 3: {
const auto mod3 = prime.countModWord(3);
if (mod3 != 2) {
LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3));
return false;
}
} break;
case 4: break;
case 5: {
const auto mod5 = prime.countModWord(5);
if (mod5 != 1 && mod5 != 4) {
LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5));
return false;
}
} break;
case 6: {
const auto mod24 = prime.countModWord(24);
if (mod24 != 19 && mod24 != 23) {
LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24));
return false;
}
} break;
case 7: {
const auto mod7 = prime.countModWord(7);
if (mod7 != 3 && mod7 != 5 && mod7 != 6) {
LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7));
return false;
}
} break;
default: {
LOG(("BigNum PT Error: bad g value: %1").arg(g));
return false;
} break;
}
if (!openssl::BigNum(prime).subWord(1).divWord(2).isPrime(context)) {
LOG(("MTP Error: Bad (prime - 1) / 2."));
return false;
}
return true;
}
} // namespace
bool IsGoodModExpFirst(
const openssl::BigNum &modexp,
const openssl::BigNum &prime) {
const auto diff = openssl::BigNum::Sub(prime, modexp);
if (modexp.failed() || prime.failed() || diff.failed()) {
return false;
}
constexpr auto kMinDiffBitsCount = 2048 - 64;
if (diff.isNegative()
|| diff.bitsSize() < kMinDiffBitsCount
|| modexp.bitsSize() < kMinDiffBitsCount
|| modexp.bytesSize() > kMaxModExpSize) {
return false;
}
return true;
}
bool IsPrimeAndGood(bytes::const_span primeBytes, int g) {
static constexpr unsigned char GoodPrime[] = {
0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73,
0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F,
0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB,
0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37,
0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB,
0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64,
0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88,
0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4,
0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41,
0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54,
0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D,
0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4,
0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05,
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B };
if (!bytes::compare(bytes::make_span(GoodPrime), primeBytes)) {
if (g == 3 || g == 4 || g == 5 || g == 7) {
return true;
}
}
return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g);
}
ModExpFirst CreateModExp(
int g,
bytes::const_span primeBytes,
bytes::const_span randomSeed) {
Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize);
using namespace openssl;
BigNum prime(primeBytes);
auto result = ModExpFirst();
result.randomPower.resize(ModExpFirst::kRandomPowerSize);
while (true) {
bytes::set_random(result.randomPower);
for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) {
result.randomPower[i] ^= randomSeed[i];
}
const auto modexp = BigNum::ModExp(
BigNum(g),
BigNum(result.randomPower),
prime);
if (IsGoodModExpFirst(modexp, prime)) {
result.modexp = modexp.getBytes();
return result;
}
}
}
bytes::vector CreateAuthKey(
bytes::const_span firstBytes,
bytes::const_span randomBytes,
bytes::const_span primeBytes) {
using openssl::BigNum;
const auto first = BigNum(firstBytes);
const auto prime = BigNum(primeBytes);
if (!IsGoodModExpFirst(first, prime)) {
LOG(("AuthKey Error: Bad first prime in CreateAuthKey()."));
return {};
}
return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes();
}
} // namespace MTP

View file

@ -0,0 +1,35 @@
/*
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/openssl_help.h"
namespace MTP {
struct ModExpFirst {
static constexpr auto kRandomPowerSize = 256;
bytes::vector modexp;
bytes::vector randomPower;
};
[[nodiscard]] bool IsPrimeAndGood(bytes::const_span primeBytes, int g);
[[nodiscard]] bool IsGoodModExpFirst(
const openssl::BigNum &modexp,
const openssl::BigNum &prime);
[[nodiscard]] ModExpFirst CreateModExp(
int g,
bytes::const_span primeBytes,
bytes::const_span randomSeed);
[[nodiscard]] bytes::vector CreateAuthKey(
bytes::const_span firstBytes,
bytes::const_span randomBytes,
bytes::const_span primeBytes);
} // namespace MTP

View file

@ -0,0 +1,238 @@
/*
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/mtproto_proxy_data.h"
#include "base/qthelp_url.h"
namespace MTP {
namespace {
[[nodiscard]] bool IsHexMtprotoPassword(const QString &password) {
const auto size = password.size();
if (size < 32 || size % 2 == 1) {
return false;
}
const auto bad = [](QChar ch) {
const auto code = ch.unicode();
return (code < 'a' || code > 'f')
&& (code < 'A' || code > 'F')
&& (code < '0' || code > '9');
};
const auto i = std::find_if(password.begin(), password.end(), bad);
return (i == password.end());
}
[[nodiscard]] ProxyData::Status HexMtprotoPasswordStatus(
const QString &password) {
const auto size = password.size() / 2;
const auto valid = (size == 16)
|| (size == 17 && (password[0] == 'd') && (password[1] == 'd'))
|| (size >= 21 && (password[0] == 'e') && (password[1] == 'e'));
if (valid) {
return ProxyData::Status::Valid;
} else if (size < 16) {
return ProxyData::Status::Invalid;
}
return ProxyData::Status::Unsupported;
}
[[nodiscard]] bytes::vector SecretFromHexMtprotoPassword(
const QString &password) {
Expects(password.size() % 2 == 0);
const auto size = password.size() / 2;
const auto fromHex = [](QChar ch) -> int {
const auto code = int(ch.unicode());
if (code >= '0' && code <= '9') {
return (code - '0');
} else if (code >= 'A' && code <= 'F') {
return 10 + (code - 'A');
} else if (ch >= 'a' && ch <= 'f') {
return 10 + (code - 'a');
}
Unexpected("Code in ProxyData fromHex.");
};
auto result = bytes::vector(size);
for (auto i = 0; i != size; ++i) {
const auto high = fromHex(password[2 * i]);
const auto low = fromHex(password[2 * i + 1]);
if (high < 0 || low < 0) {
return {};
}
result[i] = static_cast<bytes::type>(high * 16 + low);
}
return result;
}
[[nodiscard]] QStringRef Base64UrlInner(const QString &password) {
Expects(password.size() > 2);
// Skip one or two '=' at the end of the string.
return password.midRef(0, [&] {
auto result = password.size();
for (auto i = 0; i != 2; ++i) {
const auto prev = result - 1;
if (password[prev] != '=') {
break;
}
result = prev;
}
return result;
}());
}
[[nodiscard]] bool IsBase64UrlMtprotoPassword(const QString &password) {
const auto size = password.size();
if (size < 22 || size % 4 == 1) {
return false;
}
const auto bad = [](QChar ch) {
const auto code = ch.unicode();
return (code < 'a' || code > 'z')
&& (code < 'A' || code > 'Z')
&& (code < '0' || code > '9')
&& (code != '_')
&& (code != '-');
};
const auto inner = Base64UrlInner(password);
const auto begin = inner.data();
const auto end = begin + inner.size();
return (std::find_if(begin, end, bad) == end);
}
[[nodiscard]] ProxyData::Status Base64UrlMtprotoPasswordStatus(
const QString &password) {
const auto inner = Base64UrlInner(password);
const auto size = (inner.size() * 3) / 4;
const auto valid = (size == 16)
|| (size == 17
&& (password[0] == '3')
&& ((password[1] >= 'Q' && password[1] <= 'Z')
|| (password[1] >= 'a' && password[1] <= 'f')))
|| (size >= 21
&& (password[0] == '7')
&& (password[1] >= 'g')
&& (password[1] <= 'v'));
if (size < 16) {
return ProxyData::Status::Invalid;
} else if (valid) {
return ProxyData::Status::Valid;
}
return ProxyData::Status::Unsupported;
}
[[nodiscard]] bytes::vector SecretFromBase64UrlMtprotoPassword(
const QString &password) {
const auto result = QByteArray::fromBase64(
password.toLatin1(),
QByteArray::Base64UrlEncoding);
return bytes::make_vector(bytes::make_span(result));
}
} // namespace
bool ProxyData::valid() const {
return status() == Status::Valid;
}
ProxyData::Status ProxyData::status() const {
if (type == Type::None || host.isEmpty() || !port) {
return Status::Invalid;
} else if (type == Type::Mtproto) {
return MtprotoPasswordStatus(password);
}
return Status::Valid;
}
bool ProxyData::supportsCalls() const {
return (type == Type::Socks5);
}
bool ProxyData::tryCustomResolve() const {
return (type == Type::Socks5 || type == Type::Mtproto)
&& !qthelp::is_ipv6(host)
&& !QRegularExpression(
QStringLiteral("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
).match(host).hasMatch();
}
bytes::vector ProxyData::secretFromMtprotoPassword() const {
Expects(type == Type::Mtproto);
if (IsHexMtprotoPassword(password)) {
return SecretFromHexMtprotoPassword(password);
} else if (IsBase64UrlMtprotoPassword(password)) {
return SecretFromBase64UrlMtprotoPassword(password);
}
return {};
}
ProxyData::operator bool() const {
return valid();
}
bool ProxyData::operator==(const ProxyData &other) const {
if (!valid()) {
return !other.valid();
}
return (type == other.type)
&& (host == other.host)
&& (port == other.port)
&& (user == other.user)
&& (password == other.password);
}
bool ProxyData::operator!=(const ProxyData &other) const {
return !(*this == other);
}
bool ProxyData::ValidMtprotoPassword(const QString &password) {
return MtprotoPasswordStatus(password) == Status::Valid;
}
ProxyData::Status ProxyData::MtprotoPasswordStatus(const QString &password) {
if (IsHexMtprotoPassword(password)) {
return HexMtprotoPasswordStatus(password);
} else if (IsBase64UrlMtprotoPassword(password)) {
return Base64UrlMtprotoPasswordStatus(password);
}
return Status::Invalid;
}
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {
if (!proxy.tryCustomResolve()
|| ipIndex < 0
|| ipIndex >= proxy.resolvedIPs.size()) {
return proxy;
}
return {
proxy.type,
proxy.resolvedIPs[ipIndex],
proxy.port,
proxy.user,
proxy.password
};
}
QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
if (proxy.type == ProxyData::Type::None) {
return QNetworkProxy::DefaultProxy;
} else if (proxy.type == ProxyData::Type::Mtproto) {
return QNetworkProxy::NoProxy;
}
return QNetworkProxy(
(proxy.type == ProxyData::Type::Socks5
? QNetworkProxy::Socks5Proxy
: QNetworkProxy::HttpProxy),
proxy.host,
proxy.port,
proxy.user,
proxy.password);
}
} // namespace MTP

View file

@ -0,0 +1,58 @@
/*
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
namespace MTP {
struct ProxyData {
enum class Settings {
System,
Enabled,
Disabled,
};
enum class Type {
None,
Socks5,
Http,
Mtproto,
};
enum class Status {
Valid,
Unsupported,
Invalid,
};
Type type = Type::None;
QString host;
uint32 port = 0;
QString user, password;
std::vector<QString> resolvedIPs;
crl::time resolvedExpireAt = 0;
[[nodiscard]] bool valid() const;
[[nodiscard]] Status status() const;
[[nodiscard]] bool supportsCalls() const;
[[nodiscard]] bool tryCustomResolve() const;
[[nodiscard]] bytes::vector secretFromMtprotoPassword() const;
[[nodiscard]] explicit operator bool() const;
[[nodiscard]] bool operator==(const ProxyData &other) const;
[[nodiscard]] bool operator!=(const ProxyData &other) const;
[[nodiscard]] static bool ValidMtprotoPassword(const QString &password);
[[nodiscard]] static Status MtprotoPasswordStatus(
const QString &password);
};
[[nodiscard]] ProxyData ToDirectIpProxy(
const ProxyData &proxy,
int ipIndex = 0);
[[nodiscard]] QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
} // namespace MTP

View file

@ -92,133 +92,159 @@ RSA *CreateRaw(bytes::const_span key) {
class RSAPublicKey::Private {
public:
Private(bytes::const_span key)
: _rsa(CreateRaw(key)) {
if (_rsa) {
computeFingerprint();
}
}
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();
}
}
}
bytes::vector getN() const {
Expects(isValid());
explicit Private(bytes::const_span key);
Private(bytes::const_span nBytes, bytes::const_span eBytes);
~Private();
const BIGNUM *n;
RSA_get0_key(_rsa, &n, nullptr, nullptr);
return toBytes(n);
}
bytes::vector getE() const {
Expects(isValid());
const BIGNUM *e;
RSA_get0_key(_rsa, nullptr, &e, nullptr);
return toBytes(e);
}
uint64 getFingerPrint() const {
return _fingerprint;
}
bool isValid() const {
return _rsa != nullptr;
}
bytes::vector encrypt(bytes::const_span data) const {
Expects(isValid());
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) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).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 decrypt(bytes::const_span data) const {
Expects(isValid());
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) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).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 encryptOAEPpadding(bytes::const_span data) const {
Expects(isValid());
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) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, "
"key fp: %1, result: %2, error: %3"
).arg(getFingerPrint()
).arg(encryptedSize
).arg(ERR_error_string(ERR_get_error(), 0)
));
return {};
}
return result;
}
~Private() {
RSA_free(_rsa);
}
[[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() {
Expects(isValid());
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);
uchar sha1Buffer[20];
_fingerprint = *(uint64*)(hashSha1(&string[0], string.size() * sizeof(mtpPrime), sha1Buffer) + 3);
}
static bytes::vector 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;
}
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) {
ERR_load_crypto_strings();
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) {
ERR_load_crypto_strings();
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) {
ERR_load_crypto_strings();
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);
uchar sha1Buffer[20];
_fingerprint = *(uint64*)(hashSha1(&string[0], string.size() * sizeof(mtpPrime), sha1Buffer) + 3);
}
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)) {
}
@ -229,35 +255,40 @@ RSAPublicKey::RSAPublicKey(
: _private(std::make_shared<Private>(nBytes, eBytes)) {
}
bool RSAPublicKey::isValid() const {
return _private && _private->isValid();
bool RSAPublicKey::empty() const {
return !_private;
}
uint64 RSAPublicKey::getFingerPrint() const {
Expects(isValid());
return _private->getFingerPrint();
bool RSAPublicKey::valid() const {
return !empty() && _private->valid();
}
uint64 RSAPublicKey::fingerprint() const {
Expects(valid());
return _private->fingerprint();
}
bytes::vector RSAPublicKey::getN() const {
Expects(isValid());
Expects(valid());
return _private->getN();
}
bytes::vector RSAPublicKey::getE() const {
Expects(isValid());
Expects(valid());
return _private->getE();
}
bytes::vector RSAPublicKey::encrypt(bytes::const_span data) const {
Expects(isValid());
Expects(valid());
return _private->encrypt(data);
}
bytes::vector RSAPublicKey::decrypt(bytes::const_span data) const {
Expects(isValid());
Expects(valid());
return _private->decrypt(data);
}

View file

@ -26,19 +26,20 @@ public:
// or in "-----BEGIN PUBLIC KEY----- ..." format
explicit RSAPublicKey(bytes::const_span key);
bool isValid() const;
uint64 getFingerPrint() const;
bytes::vector getN() const;
bytes::vector getE() const;
[[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
bytes::vector encrypt(bytes::const_span data) const;
[[nodiscard]] bytes::vector encrypt(bytes::const_span data) const;
// data has exactly 256 chars to be decrypted
bytes::vector decrypt(bytes::const_span data) const;
[[nodiscard]] bytes::vector decrypt(bytes::const_span data) const;
// data has lequal than 215 chars to be decrypted
bytes::vector encryptOAEPpadding(bytes::const_span data) const;
[[nodiscard]] bytes::vector encryptOAEPpadding(bytes::const_span data) const;
private:
class Private;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "mtproto/rpc_sender.h"
#include "mtproto/mtproto_proxy_data.h"
#include <QtCore/QTimer>

View file

@ -48,7 +48,7 @@ void SetupConnectionType(not_null<Ui::VerticalLayout*> container) {
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
const auto connectionType = [] {
const auto transport = MTP::dctransport();
if (Global::ProxySettings() != ProxyData::Settings::Enabled) {
if (Global::ProxySettings() != MTP::ProxyData::Settings::Enabled) {
return transport.isEmpty()
? tr::lng_connection_auto_connecting(tr::now)
: tr::lng_connection_auto(tr::now, lt_transport, transport);

View file

@ -1264,7 +1264,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
stream >> v;
if (!_checkStreamStatus(stream)) return false;
ProxyData proxy;
MTP::ProxyData proxy;
switch (v) {
case dbictHttpProxy:
case dbictTcpProxy: {
@ -1274,14 +1274,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
proxy.port = uint32(port);
proxy.type = (v == dbictTcpProxy)
? ProxyData::Type::Socks5
: ProxyData::Type::Http;
? MTP::ProxyData::Type::Socks5
: MTP::ProxyData::Type::Http;
} break;
};
Global::SetSelectedProxy(proxy ? proxy : ProxyData());
Global::SetSelectedProxy(proxy ? proxy : MTP::ProxyData());
Global::SetProxySettings(proxy
? ProxyData::Settings::Enabled
: ProxyData::Settings::System);
? MTP::ProxyData::Settings::Enabled
: MTP::ProxyData::Settings::System);
if (proxy) {
Global::SetProxiesList({ 1, proxy });
} else {
@ -1299,20 +1299,20 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
const auto readProxy = [&] {
qint32 proxyType, port;
ProxyData proxy;
MTP::ProxyData proxy;
stream >> proxyType >> proxy.host >> port >> proxy.user >> proxy.password;
proxy.port = port;
proxy.type = (proxyType == dbictTcpProxy)
? ProxyData::Type::Socks5
? MTP::ProxyData::Type::Socks5
: (proxyType == dbictHttpProxy)
? ProxyData::Type::Http
: (proxyType == kProxyTypeShift + int(ProxyData::Type::Socks5))
? ProxyData::Type::Socks5
: (proxyType == kProxyTypeShift + int(ProxyData::Type::Http))
? ProxyData::Type::Http
: (proxyType == kProxyTypeShift + int(ProxyData::Type::Mtproto))
? ProxyData::Type::Mtproto
: ProxyData::Type::None;
? MTP::ProxyData::Type::Http
: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Socks5))
? MTP::ProxyData::Type::Socks5
: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Http))
? MTP::ProxyData::Type::Http
: (proxyType == kProxyTypeShift + int(MTP::ProxyData::Type::Mtproto))
? MTP::ProxyData::Type::Mtproto
: MTP::ProxyData::Type::None;
return proxy;
};
if (connectionType == dbictProxiesListOld
@ -1327,7 +1327,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
index -= (index > 0 ? count : -count);
}
auto list = std::vector<ProxyData>();
auto list = std::vector<MTP::ProxyData>();
for (auto i = 0; i < count; ++i) {
const auto proxy = readProxy();
if (proxy) {
@ -1345,29 +1345,29 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
if (connectionType == dbictProxiesListOld) {
settings = static_cast<qint32>(
(index > 0 && index <= list.size()
? ProxyData::Settings::Enabled
: ProxyData::Settings::System));
? MTP::ProxyData::Settings::Enabled
: MTP::ProxyData::Settings::System));
index = std::abs(index);
}
if (index > 0 && index <= list.size()) {
Global::SetSelectedProxy(list[index - 1]);
} else {
Global::SetSelectedProxy(ProxyData());
Global::SetSelectedProxy(MTP::ProxyData());
}
const auto unchecked = static_cast<ProxyData::Settings>(settings);
const auto unchecked = static_cast<MTP::ProxyData::Settings>(settings);
switch (unchecked) {
case ProxyData::Settings::Enabled:
case MTP::ProxyData::Settings::Enabled:
Global::SetProxySettings(Global::SelectedProxy()
? ProxyData::Settings::Enabled
: ProxyData::Settings::System);
? MTP::ProxyData::Settings::Enabled
: MTP::ProxyData::Settings::System);
break;
case ProxyData::Settings::Disabled:
case ProxyData::Settings::System:
case MTP::ProxyData::Settings::Disabled:
case MTP::ProxyData::Settings::System:
Global::SetProxySettings(unchecked);
break;
default:
Global::SetProxySettings(ProxyData::Settings::System);
Global::SetProxySettings(MTP::ProxyData::Settings::System);
break;
}
Global::SetUseProxyForCalls(calls == 1);
@ -1381,14 +1381,14 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
Global::SetSelectedProxy(proxy);
if (connectionType == dbictTcpProxy
|| connectionType == dbictHttpProxy) {
Global::SetProxySettings(ProxyData::Settings::Enabled);
Global::SetProxySettings(MTP::ProxyData::Settings::Enabled);
} else {
Global::SetProxySettings(ProxyData::Settings::System);
Global::SetProxySettings(MTP::ProxyData::Settings::System);
}
} else {
Global::SetProxiesList({});
Global::SetSelectedProxy(ProxyData());
Global::SetProxySettings(ProxyData::Settings::System);
Global::SetSelectedProxy(MTP::ProxyData());
Global::SetProxySettings(MTP::ProxyData::Settings::System);
}
}
Core::App().refreshGlobalProxy();
@ -2646,7 +2646,7 @@ void writeSettings() {
auto &proxies = Global::RefProxiesList();
const auto &proxy = Global::SelectedProxy();
auto proxyIt = ranges::find(proxies, proxy);
if (proxy.type != ProxyData::Type::None
if (proxy.type != MTP::ProxyData::Type::None
&& proxyIt == end(proxies)) {
proxies.push_back(proxy);
proxyIt = end(proxies) - 1;

View file

@ -271,7 +271,7 @@ void ConnectionState::refreshState() {
const auto under = _widget && _widget->isOver();
const auto mtp = MTP::dcstate();
const auto throughProxy
= (Global::ProxySettings() == ProxyData::Settings::Enabled);
= (Global::ProxySettings() == MTP::ProxyData::Settings::Enabled);
if (mtp == MTP::ConnectingState
|| mtp == MTP::DisconnectedState
|| (mtp < 0 && mtp > -600)) {

View file

@ -34,6 +34,14 @@
'<(src_loc)',
],
'sources': [
'<(src_loc)/mtproto/details/mtproto_dc_key_checker.cpp',
'<(src_loc)/mtproto/details/mtproto_dc_key_checker.h',
'<(src_loc)/mtproto/details/mtproto_dc_key_creator.cpp',
'<(src_loc)/mtproto/details/mtproto_dc_key_creator.h',
'<(src_loc)/mtproto/mtproto_dh_utils.cpp',
'<(src_loc)/mtproto/mtproto_dh_utils.h',
'<(src_loc)/mtproto/mtproto_proxy_data.cpp',
'<(src_loc)/mtproto/mtproto_proxy_data.h',
'<(src_loc)/mtproto/mtp_abstract_socket.cpp',
'<(src_loc)/mtproto/mtp_abstract_socket.h',
'<(src_loc)/mtproto/mtp_tcp_socket.cpp',