Support base64 encoding of the secret.

This commit is contained in:
John Preston 2019-07-08 18:02:45 +02:00
parent 2f0331b2e0
commit 8135f4b427
13 changed files with 269 additions and 117 deletions

View File

@ -145,6 +145,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
"lng_sure_enable" = "Enable";
"lng_proxy_invalid" = "The proxy link is invalid.";
"lng_proxy_unsupported" = "Your Telegram Desktop version doesn't support this proxy type or the proxy link is invalid. Please update Telegram Desktop to the latest version.";
"lng_edit_deleted" = "This message was deleted";
"lng_edit_too_long" = "Your message text is too long";

View File

@ -31,6 +31,57 @@ namespace {
constexpr auto kSaveSettingsDelayedTimeout = crl::time(1000);
class Base64UrlInput : public Ui::MaskedInputField {
public:
Base64UrlInput(
QWidget *parent,
const style::InputField &st,
rpl::producer<QString> placeholder,
const QString &val);
protected:
void correctValue(
const QString &was,
int wasCursor,
QString &now,
int &nowCursor) override;
};
Base64UrlInput::Base64UrlInput(
QWidget *parent,
const style::InputField &st,
rpl::producer<QString> placeholder,
const QString &val)
: MaskedInputField(parent, st, std::move(placeholder), val) {
if (!QRegularExpression("^[a-zA-Z0-9_\\-]+$").match(val).hasMatch()) {
setText(QString());
}
}
void Base64UrlInput::correctValue(
const QString &was,
int wasCursor,
QString &now,
int &nowCursor) {
QString newText;
newText.reserve(now.size());
auto newPos = nowCursor;
for (auto i = 0, l = now.size(); i < l; ++i) {
const auto ch = now[i];
if ((ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z')
|| (ch == '-')
|| (ch == '_')) {
newText.append(ch);
} else if (i < nowCursor) {
--newPos;
}
}
setCorrectedText(now, nowCursor, newText, newPos);
}
class ProxyRow : public Ui::RippleButton {
public:
using View = ProxiesBoxController::ItemView;
@ -150,7 +201,7 @@ private:
QPointer<Ui::PortInput> _port;
QPointer<Ui::InputField> _user;
QPointer<Ui::PasswordInput> _password;
QPointer<Ui::HexInput> _secret;
QPointer<Base64UrlInput> _secret;
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _credentials;
QPointer<Ui::SlideWrap<Ui::VerticalLayout>> _mtprotoCredentials;
@ -852,12 +903,11 @@ void ProxyBox::setupMtprotoCredentials(const ProxyData &data) {
addLabel(mtproto, tr::lng_proxy_credentials(tr::now));
auto secretWrap = object_ptr<Ui::RpWidget>(mtproto);
_secret = Ui::CreateChild<Ui::HexInput>(
_secret = Ui::CreateChild<Base64UrlInput>(
secretWrap.data(),
st::connectionUserInputField,
tr::lng_connection_proxy_secret_ph(),
(data.type == Type::Mtproto) ? data.password : QString());
_secret->setMaxLength(ProxyData::MaxMtprotoPasswordLength());
_secret->move(0, 0);
_secret->heightValue(
) | rpl::start_with_next([=, wrap = secretWrap.data()](int height) {
@ -975,6 +1025,11 @@ void ProxiesBoxController::ShowApplyConfirmation(
strong->closeBox();
}
}), LayerOption::KeepOther);
} else {
Ui::show(Box<InformBox>(
(proxy.status() == ProxyData::Status::Unsupported
? tr::lng_proxy_unsupported(tr::now)
: tr::lng_proxy_invalid(tr::now))));
}
}

View File

@ -62,30 +62,127 @@ namespace {
std::atomic<int> GlobalAtomicRequestId = 0;
QReadWriteLock unixtimeLock;
volatile int32 unixtimeDelta = 0;
volatile bool unixtimeWasSet = false;
volatile uint64 _msgIdStart, _msgIdLocal = 0, _msgIdMsStart;
QReadWriteLock unixtimeLock;
volatile int32 unixtimeDelta = 0;
volatile bool unixtimeWasSet = false;
volatile uint64 _msgIdStart, _msgIdLocal = 0, _msgIdMsStart;
void _initMsgIdConstants() {
void _initMsgIdConstants() {
#ifdef Q_OS_WIN
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
_msgIdMsStart = li.QuadPart;
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
_msgIdMsStart = li.QuadPart;
#elif defined Q_OS_MAC
_msgIdMsStart = mach_absolute_time();
_msgIdMsStart = mach_absolute_time();
#else
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
_msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec);
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
_msgIdMsStart = 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec);
#endif
uint32 msgIdRand;
memset_rand(&msgIdRand, sizeof(uint32));
_msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand);
}
uint32 msgIdRand;
memset_rand(&msgIdRand, sizeof(uint32));
_msgIdStart = (((uint64)((uint32)unixtime()) << 32) | (uint64)msgIdRand);
}
[[nodiscard]] bool IsHexMtprotoPassword(const QString &password) {
const auto bad = [](QChar ch) {
const auto code = ch.unicode();
return (code < 'a' || code > 'f')
&& (code < 'A' || code > 'F')
&& (code < '0' || code > '9');
};
return (password.size() % 2 == 0)
&& (std::find_if(password.begin(), password.end(), bad)
== 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]] bool IsBase64UrlMtprotoPassword(const QString &password) {
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 != '-');
};
return (password.size() % 4 != 1)
&& (std::find_if(password.begin(), password.end(), bad)
== password.end());
}
[[nodiscard]] ProxyData::Status Base64UrlMtprotoPasswordStatus(
const QString &password) {
const auto size = (password.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
TimeId LocalUnixtime() {
return (TimeId)time(nullptr);
}
@ -233,16 +330,16 @@ namespace {
}
bool ProxyData::valid() const {
if (type == Type::None || host.isEmpty() || !port) {
return false;
} else if (type == Type::Mtproto && !ValidMtprotoPassword(password)) {
return false;
}
return true;
return status() == Status::Valid;
}
int ProxyData::MaxMtprotoPasswordLength() {
return 34;
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 {
@ -259,30 +356,13 @@ bool ProxyData::tryCustomResolve() const {
bytes::vector ProxyData::secretFromMtprotoPassword() const {
Expects(type == Type::Mtproto);
Expects(password.size() % 2 == 0);
const auto length = 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');
}
return -1;
};
auto result = bytes::vector(length);
for (auto i = 0; i != length; ++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<gsl::byte>(high * 16 + low);
if (IsHexMtprotoPassword(password)) {
return SecretFromHexMtprotoPassword(password);
} else if (IsBase64UrlMtprotoPassword(password)) {
return SecretFromBase64UrlMtprotoPassword(password);
}
return result;
return {};
}
ProxyData::operator bool() const {
@ -304,16 +384,17 @@ bool ProxyData::operator!=(const ProxyData &other) const {
return !(*this == other);
}
bool ProxyData::ValidMtprotoPassword(const QString &secret) {
using Expression = QRegularExpression;
if (secret.size() == 32) {
static const auto check = Expression("^[a-fA-F0-9]{32}$");
return check.match(secret).hasMatch();
} else if (secret.size() == 34) {
static const auto check = Expression("^(dd|ee)[a-fA-F0-9]{32}$");
return check.match(secret).hasMatch();
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 false;
return Status::Invalid;
}
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {

View File

@ -270,6 +270,11 @@ struct ProxyData {
Http,
Mtproto,
};
enum class Status {
Valid,
Unsupported,
Invalid,
};
Type type = Type::None;
QString host;
@ -279,16 +284,18 @@ struct ProxyData {
std::vector<QString> resolvedIPs;
crl::time resolvedExpireAt = 0;
bool valid() const;
bool supportsCalls() const;
bool tryCustomResolve() const;
bytes::vector secretFromMtprotoPassword() const;
explicit operator bool() const;
bool operator==(const ProxyData &other) const;
bool operator!=(const ProxyData &other) const;
[[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;
static bool ValidMtprotoPassword(const QString &secret);
static int MaxMtprotoPasswordLength();
[[nodiscard]] static bool ValidMtprotoPassword(const QString &password);
[[nodiscard]] static Status MtprotoPasswordStatus(
const QString &password);
};

View File

@ -30,7 +30,7 @@ constexpr auto kConnectionStartPrefixSize = 64;
class TcpConnection::Protocol {
public:
static std::unique_ptr<Protocol> Create(bytes::vector &&secret);
static std::unique_ptr<Protocol> Create(bytes::const_span secret);
virtual uint32 id() const = 0;
virtual bool supportsArbitraryLength() const = 0;
@ -223,15 +223,14 @@ bytes::const_span TcpConnection::Protocol::VersionD::readPacket(
return bytes.subspan(sizeLength, size - sizeLength);
}
auto TcpConnection::Protocol::Create(bytes::vector &&secret)
auto TcpConnection::Protocol::Create(bytes::const_span secret)
-> std::unique_ptr<Protocol> {
if (secret.size() == 17
&& (static_cast<uchar>(secret[0]) == 0xDD
|| static_cast<uchar>(secret[0]) == 0xEE)) {
if ((secret.size() >= 21 && secret[0] == bytes::type(0xEE))
|| (secret.size() == 17 && secret[0] == bytes::type(0xDD))) {
return std::make_unique<VersionD>(
bytes::make_vector(bytes::make_span(secret).subspan(1)));
bytes::make_vector(secret.subspan(1, 16)));
} else if (secret.size() == 16) {
return std::make_unique<Version1>(std::move(secret));
return std::make_unique<Version1>(bytes::make_vector(secret));
} else if (secret.empty()) {
return std::make_unique<Version0>();
}
@ -522,10 +521,13 @@ void TcpConnection::connectToServer(
Expects(_protocol == nullptr);
Expects(_protocolDcId == 0);
const auto secret = (_proxy.type == ProxyData::Type::Mtproto)
? _proxy.secretFromMtprotoPassword()
: protocolSecret;
if (_proxy.type == ProxyData::Type::Mtproto) {
_address = _proxy.host;
_port = _proxy.port;
_protocol = Protocol::Create(_proxy.secretFromMtprotoPassword());
_protocol = Protocol::Create(secret);
DEBUG_LOG(("TCP Info: "
"dc:%1 - Connecting to proxy '%2'"
@ -534,14 +536,17 @@ void TcpConnection::connectToServer(
} else {
_address = address;
_port = port;
_protocol = Protocol::Create(base::duplicate(protocolSecret));
_protocol = Protocol::Create(secret);
DEBUG_LOG(("TCP Info: "
"dc:%1 - Connecting to '%2'"
).arg(protocolDcId
).arg(_address + ':' + QString::number(_port)));
}
_socket = AbstractSocket::Create(thread(), protocolSecret, _proxy);
_socket = AbstractSocket::Create(
thread(),
secret,
ToNetworkProxy(_proxy));
_protocolDcId = protocolDcId;
_socket->connected(

View File

@ -257,7 +257,7 @@ bool DcOptions::ApplyOneOption(
return false;
}
}
i->second.push_back(Endpoint(dcId, flags, ip, port, secret));
i->second.emplace_back(dcId, flags, ip, port, secret);
} else {
data.emplace(dcId, std::vector<Endpoint>(
1,

View File

@ -16,12 +16,8 @@ namespace internal {
std::unique_ptr<AbstractSocket> AbstractSocket::Create(
not_null<QThread*> thread,
const bytes::vector &secret,
const ProxyData &proxy) {
const auto proxySecret = (proxy.type == ProxyData::Type::Mtproto)
? proxy.secretFromMtprotoPassword()
: bytes::vector();
const auto &usingSecret = proxySecret.empty() ? secret : proxySecret;
if (!usingSecret.empty() && usingSecret[0] == bytes::type(0xEE)) {
const QNetworkProxy &proxy) {
if (secret.size() >= 21 && secret[0] == bytes::type(0xEE)) {
return std::make_unique<TlsSocket>(thread, secret, proxy);
} else {
return std::make_unique<TcpSocket>(thread, proxy);

View File

@ -17,7 +17,7 @@ public:
static std::unique_ptr<AbstractSocket> Create(
not_null<QThread*> thread,
const bytes::vector &secret,
const ProxyData &proxy);
const QNetworkProxy &proxy);
explicit AbstractSocket(not_null<QThread*> thread) {
moveToThread(thread);

View File

@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace MTP {
namespace internal {
TcpSocket::TcpSocket(not_null<QThread*> thread, const ProxyData &proxy)
TcpSocket::TcpSocket(not_null<QThread*> thread, const QNetworkProxy &proxy)
: AbstractSocket(thread) {
_socket.moveToThread(thread);
_socket.setProxy(ToNetworkProxy(proxy));
_socket.setProxy(proxy);
const auto wrap = [&](auto handler) {
return [=](auto &&...args) {
InvokeQueued(this, [=] { handler(args...); });

View File

@ -14,7 +14,7 @@ namespace internal {
class TcpSocket final : public AbstractSocket {
public:
TcpSocket(not_null<QThread*> thread, const ProxyData &proxy);
TcpSocket(not_null<QThread*> thread, const QNetworkProxy &proxy);
void connectToHost(const QString &address, int port) override;
bool isConnected() override;

View File

@ -131,8 +131,8 @@ class ClientHelloGenerator {
public:
ClientHelloGenerator(
const MTPTlsClientHello &rules,
const QByteArray &domain,
const bytes::vector &key);
bytes::const_span domain,
bytes::const_span key);
[[nodiscard]] ClientHello result();
private:
@ -149,8 +149,8 @@ private:
void writeDigest();
void writeTimestamp();
const QByteArray &_domain;
const bytes::vector &_key;
bytes::const_span _domain;
bytes::const_span _key;
bytes::vector _greases;
std::vector<int> _scopeStack;
QByteArray _result;
@ -162,8 +162,8 @@ private:
ClientHelloGenerator::ClientHelloGenerator(
const MTPTlsClientHello &rules,
const QByteArray &domain,
const bytes::vector &key)
bytes::const_span domain,
bytes::const_span key)
: _domain(domain)
, _key(key)
, _greases(PrepareGreases()) {
@ -256,7 +256,7 @@ void ClientHelloGenerator::writeBlock(const MTPDtlsBlockDomain &data) {
if (storage.empty()) {
return;
}
bytes::copy(storage, bytes::make_span(_domain));
bytes::copy(storage, _domain);
}
void ClientHelloGenerator::writeBlock(const MTPDtlsBlockScope &data) {
@ -309,22 +309,11 @@ void ClientHelloGenerator::writeTimestamp() {
[[nodiscard]] ClientHello PrepareClientHello(
const MTPTlsClientHello &rules,
const QByteArray &domain,
const bytes::vector &key) {
bytes::const_span domain,
bytes::const_span key) {
return ClientHelloGenerator(rules, domain, key).result();
}
[[nodiscard]] bytes::vector ExtractKey(
const bytes::vector &secret,
const ProxyData &proxy) {
const auto proxySecret = proxy.secretFromMtprotoPassword();
const auto &useSecret = proxySecret.empty() ? secret : proxySecret;
if (useSecret.size() != 17 || useSecret[0] != bytes::type(0xEE)) {
return {};
}
return bytes::make_vector(bytes::make_span(useSecret).subspan(1));
}
[[nodiscard]] bool CheckPart(bytes::const_span data, QLatin1String check) {
if (data.size() < check.size()) {
return false;
@ -345,11 +334,13 @@ void ClientHelloGenerator::writeTimestamp() {
TlsSocket::TlsSocket(
not_null<QThread*> thread,
const bytes::vector &secret,
const ProxyData &proxy)
const QNetworkProxy &proxy)
: AbstractSocket(thread)
, _key(ExtractKey(secret, proxy)) {
, _secret(secret) {
Expects(_secret.size() >= 21 && _secret[0] == bytes::type(0xEE));
_socket.moveToThread(thread);
_socket.setProxy(ToNetworkProxy(proxy));
_socket.setProxy(proxy);
const auto wrap = [&](auto handler) {
return [=](auto &&...args) {
InvokeQueued(this, [=] { handler(args...); });
@ -377,6 +368,14 @@ TlsSocket::TlsSocket(
wrap([=](Error e) { handleError(e); }));
}
bytes::const_span TlsSocket::domainFromSecret() const {
return bytes::make_span(_secret).subspan(17);
}
bytes::const_span TlsSocket::keyFromSecret() const {
return bytes::make_span(_secret).subspan(1, 16);
}
void TlsSocket::plainConnected() {
if (_state != State::Connecting) {
return;
@ -385,8 +384,8 @@ void TlsSocket::plainConnected() {
static const auto kClientHelloRules = PrepareClientHelloRules();
const auto hello = PrepareClientHello(
kClientHelloRules,
"www.google.com",
_key);
domainFromSecret(),
keyFromSecret());
if (hello.data.isEmpty()) {
LOG(("TLS Error: Could not generate Client Hello!"));
_state = State::Error;
@ -492,7 +491,7 @@ void TlsSocket::checkHelloDigest() {
kHelloDigestLength);
const auto digestCopy = bytes::make_vector(digest);
bytes::set_with_const(digest, bytes::type(0));
const auto check = openssl::HmacSha256(_key, fulldata);
const auto check = openssl::HmacSha256(keyFromSecret(), fulldata);
if (bytes::compare(digestCopy, check) != 0) {
LOG(("TLS Error: Bad Server Hello digest."));
handleError();

View File

@ -17,7 +17,7 @@ public:
TlsSocket(
not_null<QThread*> thread,
const bytes::vector &secret,
const ProxyData &proxy);
const QNetworkProxy &proxy);
void connectToHost(const QString &address, int port) override;
bool isConnected() override;
@ -36,6 +36,9 @@ private:
Error,
};
[[nodiscard]] bytes::const_span domainFromSecret() const;
[[nodiscard]] bytes::const_span keyFromSecret() const;
void plainConnected();
void plainDisconnected();
void plainReadyRead();
@ -49,8 +52,8 @@ private:
[[nodiscard]] bool checkNextPacket();
void shiftIncomingBy(int amount);
const bytes::vector _secret;
QTcpSocket _socket;
bytes::vector _key;
State _state = State::NotConnected;
QByteArray _incoming;
int _incomingGoodDataOffset = 0;

View File

@ -1332,9 +1332,13 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
const auto unchecked = static_cast<ProxyData::Settings>(settings);
switch (unchecked) {
case ProxyData::Settings::Enabled:
Global::SetProxySettings(Global::SelectedProxy()
? ProxyData::Settings::Enabled
: ProxyData::Settings::System);
break;
case ProxyData::Settings::Disabled:
case ProxyData::Settings::System:
case ProxyData::Settings::Enabled:
Global::SetProxySettings(unchecked);
break;
default: