mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 23:00:58 +00:00
Extract mtproto key generation code.
This commit is contained in:
parent
70dbd9e5b4
commit
08bfe6f1c1
Telegram
SourceFiles
apiwrap.cpp
boxes
calls
core
application.cppapplication.hcore_cloud_password.cppcrash_report_window.cppcrash_report_window.hlocal_url_handlers.cppsandbox.cppsandbox.hutils.cpputils.h
facades.cppfacades.hmain
mtproto
connection.cppconnection.hconnection_abstract.cppconnection_abstract.hdc_options.cppdc_options.h
details
mtproto_dc_key_checker.cppmtproto_dc_key_checker.hmtproto_dc_key_creator.cppmtproto_dc_key_creator.h
mtp_instance.cppmtproto_dh_utils.cppmtproto_dh_utils.hmtproto_proxy_data.cppmtproto_proxy_data.hrsa_public_key.cpprsa_public_key.hsession.hsettings
storage
window
gyp
@ -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 };
|
||||
|
@ -38,6 +38,8 @@ namespace {
|
||||
|
||||
constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
|
||||
|
||||
using ProxyData = MTP::ProxyData;
|
||||
|
||||
class Base64UrlInput : public Ui::MaskedInputField {
|
||||
public:
|
||||
Base64UrlInput(
|
||||
|
@ -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();
|
||||
|
@ -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(),
|
||||
|
@ -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"
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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));
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
@ -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
|
572
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal file
572
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal 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
|
102
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal file
102
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal 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
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
173
Telegram/SourceFiles/mtproto/mtproto_dh_utils.cpp
Normal file
173
Telegram/SourceFiles/mtproto/mtproto_dh_utils.cpp
Normal 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
|
35
Telegram/SourceFiles/mtproto/mtproto_dh_utils.h
Normal file
35
Telegram/SourceFiles/mtproto/mtproto_dh_utils.h
Normal 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
|
238
Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp
Normal file
238
Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp
Normal 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
|
58
Telegram/SourceFiles/mtproto/mtproto_proxy_data.h
Normal file
58
Telegram/SourceFiles/mtproto/mtproto_proxy_data.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)) {
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user