From 69b6b487382c12efc43d52f472cab5954ab850e2 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 8 Jul 2019 15:56:09 +0200
Subject: [PATCH] Add new socket type.

---
 Telegram/SourceFiles/base/openssl_help.h      |  19 +
 .../SourceFiles/mtproto/connection_tcp.cpp    |   6 +-
 .../mtproto/mtp_abstract_socket.cpp           |  11 +-
 .../SourceFiles/mtproto/mtp_abstract_socket.h |   2 +-
 .../SourceFiles/mtproto/mtp_tcp_socket.cpp    |  22 +-
 Telegram/SourceFiles/mtproto/mtp_tcp_socket.h |   5 +-
 .../SourceFiles/mtproto/mtp_tls_socket.cpp    | 549 ++++++++++++++++++
 Telegram/SourceFiles/mtproto/mtp_tls_socket.h |  60 ++
 Telegram/gyp/telegram_sources.txt             |   2 +
 9 files changed, 655 insertions(+), 21 deletions(-)
 create mode 100644 Telegram/SourceFiles/mtproto/mtp_tls_socket.cpp
 create mode 100644 Telegram/SourceFiles/mtproto/mtp_tls_socket.h

diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h
index 6f09d1bc7..d78734715 100644
--- a/Telegram/SourceFiles/base/openssl_help.h
+++ b/Telegram/SourceFiles/base/openssl_help.h
@@ -19,6 +19,7 @@ extern "C" {
 #include <openssl/modes.h>
 #include <openssl/crypto.h>
 #include <openssl/evp.h>
+#include <openssl/hmac.h>
 } // extern "C"
 
 #ifdef small
@@ -445,6 +446,24 @@ inline bytes::vector Pbkdf2Sha512(
 		EVP_sha512());
 }
 
+inline bytes::vector HmacSha256(
+		bytes::const_span key,
+		bytes::const_span data) {
+	auto result = bytes::vector(kSha256Size);
+	auto length = unsigned int(kSha256Size);
+
+	HMAC(
+		EVP_sha256(),
+		key.data(),
+		key.size(),
+		reinterpret_cast<const unsigned char*>(data.data()),
+		data.size(),
+		reinterpret_cast<unsigned char*>(result.data()),
+		&length);
+
+	return result;
+}
+
 } // namespace openssl
 
 namespace bytes {
diff --git a/Telegram/SourceFiles/mtproto/connection_tcp.cpp b/Telegram/SourceFiles/mtproto/connection_tcp.cpp
index fcf71ba3b..6f0f31091 100644
--- a/Telegram/SourceFiles/mtproto/connection_tcp.cpp
+++ b/Telegram/SourceFiles/mtproto/connection_tcp.cpp
@@ -224,7 +224,9 @@ bytes::const_span TcpConnection::Protocol::VersionD::readPacket(
 
 auto TcpConnection::Protocol::Create(bytes::vector &&secret)
 -> std::unique_ptr<Protocol> {
-	if (secret.size() == 17 && static_cast<uchar>(secret[0]) == 0xDD) {
+	if (secret.size() == 17
+		&& (static_cast<uchar>(secret[0]) == 0xDD
+			|| static_cast<uchar>(secret[0]) == 0xEE)) {
 		return std::make_unique<VersionD>(
 			bytes::make_vector(bytes::make_span(secret).subspan(1)));
 	} else if (secret.size() == 16) {
@@ -363,7 +365,7 @@ void TcpConnection::socketRead() {
 			TCP_LOG(("TCP Info: no bytes read, but bytes available was true..."));
 			break;
 		}
-	} while (_socket->isConnected() && _socket->bytesAvailable());
+	} while (_socket->isConnected() && _socket->hasBytesAvailable());
 }
 
 mtpBuffer TcpConnection::parsePacket(bytes::const_span bytes) {
diff --git a/Telegram/SourceFiles/mtproto/mtp_abstract_socket.cpp b/Telegram/SourceFiles/mtproto/mtp_abstract_socket.cpp
index c05e6c580..e83f8c289 100644
--- a/Telegram/SourceFiles/mtproto/mtp_abstract_socket.cpp
+++ b/Telegram/SourceFiles/mtproto/mtp_abstract_socket.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "mtproto/mtp_abstract_socket.h"
 
 #include "mtproto/mtp_tcp_socket.h"
+#include "mtproto/mtp_tls_socket.h"
 
 namespace MTP {
 namespace internal {
@@ -16,7 +17,15 @@ std::unique_ptr<AbstractSocket> AbstractSocket::Create(
 		not_null<QThread*> thread,
 		const bytes::vector &secret,
 		const ProxyData &proxy) {
-	return std::make_unique<TcpSocket>(thread, 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)) {
+		return std::make_unique<TlsSocket>(thread, secret, proxy);
+	} else {
+		return std::make_unique<TcpSocket>(thread, proxy);
+	}
 }
 
 } // namespace internal
diff --git a/Telegram/SourceFiles/mtproto/mtp_abstract_socket.h b/Telegram/SourceFiles/mtproto/mtp_abstract_socket.h
index f627601e0..7e1e69ebc 100644
--- a/Telegram/SourceFiles/mtproto/mtp_abstract_socket.h
+++ b/Telegram/SourceFiles/mtproto/mtp_abstract_socket.h
@@ -37,7 +37,7 @@ public:
 
 	virtual void connectToHost(const QString &address, int port) = 0;
 	[[nodiscard]] virtual bool isConnected() = 0;
-	[[nodiscard]] virtual int bytesAvailable() = 0;
+	[[nodiscard]] virtual bool hasBytesAvailable() = 0;
 	[[nodiscard]] virtual int64 read(char *buffer, int64 maxLength) = 0;
 	virtual int64 write(const char *buffer, int64 length) = 0;
 
diff --git a/Telegram/SourceFiles/mtproto/mtp_tcp_socket.cpp b/Telegram/SourceFiles/mtproto/mtp_tcp_socket.cpp
index 5150031f3..e6877b356 100644
--- a/Telegram/SourceFiles/mtproto/mtp_tcp_socket.cpp
+++ b/Telegram/SourceFiles/mtproto/mtp_tcp_socket.cpp
@@ -9,12 +9,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace MTP {
 namespace internal {
-namespace {
-
-using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError);
-const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error);
-
-} // namespace
 
 TcpSocket::TcpSocket(not_null<QThread*> thread, const ProxyData &proxy)
 : AbstractSocket(thread) {
@@ -38,14 +32,13 @@ TcpSocket::TcpSocket(not_null<QThread*> thread, const ProxyData &proxy)
 		&_socket,
 		&QTcpSocket::readyRead,
 		wrap([=] { _readyRead.fire({}); }));
+
+	using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError);
+	const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error);
 	connect(
 		&_socket,
 		QTcpSocket_error,
-		wrap([=](Error e) { logError(e); _error.fire({}); }));
-}
-
-TcpSocket::~TcpSocket() {
-	_socket.close();
+		wrap([=](Error e) { handleError(e); }));
 }
 
 void TcpSocket::connectToHost(const QString &address, int port) {
@@ -56,8 +49,8 @@ bool TcpSocket::isConnected() {
 	return (_socket.state() == QAbstractSocket::ConnectedState);
 }
 
-int TcpSocket::bytesAvailable() {
-	return _socket.bytesAvailable();
+bool TcpSocket::hasBytesAvailable() {
+	return _socket.bytesAvailable() > 0;
 }
 
 int64 TcpSocket::read(char *buffer, int64 maxLength) {
@@ -114,8 +107,9 @@ void TcpSocket::LogError(int errorCode, const QString &errorText) {
 		).arg(errorText));
 }
 
-void TcpSocket::logError(int errorCode) {
+void TcpSocket::handleError(int errorCode) {
 	LogError(errorCode, _socket.errorString());
+	_error.fire({});
 }
 
 } // namespace internal
diff --git a/Telegram/SourceFiles/mtproto/mtp_tcp_socket.h b/Telegram/SourceFiles/mtproto/mtp_tcp_socket.h
index b660d1b4f..6e7d97d83 100644
--- a/Telegram/SourceFiles/mtproto/mtp_tcp_socket.h
+++ b/Telegram/SourceFiles/mtproto/mtp_tcp_socket.h
@@ -15,11 +15,10 @@ namespace internal {
 class TcpSocket : public AbstractSocket {
 public:
 	TcpSocket(not_null<QThread*> thread, const ProxyData &proxy);
-	~TcpSocket();
 
 	void connectToHost(const QString &address, int port) override;
 	bool isConnected() override;
-	int bytesAvailable() override;
+	bool hasBytesAvailable() override;
 	int64 read(char *buffer, int64 maxLength) override;
 	int64 write(const char *buffer, int64 length) override;
 
@@ -28,7 +27,7 @@ public:
 	static void LogError(int errorCode, const QString &errorText);
 
 private:
-	void logError(int errorCode);
+	void handleError(int errorCode);
 
 	QTcpSocket _socket;
 
diff --git a/Telegram/SourceFiles/mtproto/mtp_tls_socket.cpp b/Telegram/SourceFiles/mtproto/mtp_tls_socket.cpp
new file mode 100644
index 000000000..4685e0442
--- /dev/null
+++ b/Telegram/SourceFiles/mtproto/mtp_tls_socket.cpp
@@ -0,0 +1,549 @@
+/*
+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/mtp_tls_socket.h"
+
+#include "mtproto/mtp_tcp_socket.h"
+#include "base/openssl_help.h"
+
+namespace MTP {
+namespace internal {
+namespace {
+
+constexpr auto kMaxGrease = 8;
+constexpr auto kClientHelloLength = 517;
+constexpr auto kHelloDigestLength = 32;
+constexpr auto kLengthSize = sizeof(uint16);
+const auto kServerHelloPart1 = qstr("\x16\x03\x03");
+const auto kServerHelloPart3 = qstr("\x14\x03\x03\x00\x01\x01\x17\x03\x03");
+constexpr auto kServerHelloDigestPosition = 11;
+const auto kServerHeader = qstr("\x17\x03\x03");
+constexpr auto kServerDataSkip = 5;
+
+[[nodiscard]] MTPTlsClientHello PrepareClientHelloRules() {
+	auto stack = std::vector<QVector<MTPTlsBlock>>();
+	const auto pushToBack = [&](MTPTlsBlock &&block) {
+		Expects(!stack.empty());
+
+		stack.back().push_back(std::move(block));
+	};
+	const auto S = [&](QLatin1String s) {
+		const auto data = QByteArray(s.data(), s.size());
+		pushToBack(MTP_tlsBlockString(MTP_bytes(data)));
+	};
+	const auto Z = [&](int length) {
+		pushToBack(MTP_tlsBlockZero(MTP_int(length)));
+	};
+	const auto G = [&](int seed) {
+		pushToBack(MTP_tlsBlockGrease(MTP_int(seed)));
+	};
+	const auto R = [&](int length) {
+		pushToBack(MTP_tlsBlockRandom(MTP_int(length)));
+	};
+	const auto D = [&] {
+		pushToBack(MTP_tlsBlockDomain());
+	};
+	const auto Open = [&] {
+		stack.emplace_back();
+	};
+	const auto Close = [&] {
+		Expects(stack.size() > 1);
+
+		const auto blocks = std::move(stack.back());
+		stack.pop_back();
+		pushToBack(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(blocks)));
+	};
+	const auto Finish = [&] {
+		Expects(stack.size() == 1);
+
+		return stack.back();
+	};
+
+	stack.emplace_back();
+
+	S(qstr("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03"));
+	Z(32);
+	S(qstr("\x20"));
+	R(32);
+	S(qstr("\x00\x22"));
+	G(0);
+	S(qstr(""
+		"\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9"
+		"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x0a"
+		"\x01\x00\x01\x91"));
+	G(2);
+	S(qstr("\x00\x00\x00\x00"));
+	Open();
+	Open();
+	S(qstr("\x00"));
+	Open();
+	D();
+	Close();
+	Close();
+	Close();
+	S(qstr("\x00\x17\x00\x00\xff\x01\x00\x01\x00\x00\x0a\x00\x0a\x00\x08"));
+	G(4);
+	S(qstr(""
+		"\x00\x1d\x00\x17\x00\x18\x00\x0b\x00\x02\x01\x00\x00\x23\x00\x00"
+		"\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70\x2f\x31"
+		"\x2e\x31\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x0d\x00\x14\x00"
+		"\x12\x04\x03\x08\x04\x04\x01\x05\x03\x08\x05\x05\x01\x08\x06\x06"
+		"\x01\x02\x01\x00\x12\x00\x00\x00\x33\x00\x2b\x00\x29"));
+	G(4);
+	S(qstr("\x00\x01\x00\x00\x1d\x00\x20"));
+	R(32);
+	S(qstr("\x00\x2d\x00\x02\x01\x01\x00\x2b\x00\x0b\x0a"));
+	G(6);
+	S(qstr("\x03\x04\x03\x03\x03\x02\x03\x01\x00\x1b\x00\x03\x02\x00\x02"));
+	G(3);
+	S(qstr("\x00\x01\x00\x00\x15"));
+
+	return MTP_tlsClientHello(MTP_vector<MTPTlsBlock>(Finish()));
+}
+
+[[nodiscard]] bytes::vector PrepareGreases() {
+	auto result = bytes::vector(kMaxGrease);
+	bytes::set_random(result);
+	for (auto &byte : result) {
+		byte = bytes::type((uchar(byte) & 0xF0) + 0x0A);
+	}
+	static_assert(kMaxGrease % 2 == 0);
+	for (auto i = 0; i != kMaxGrease; i += 2) {
+		if (result[i] == result[i + 1]) {
+			result[i + 1] = bytes::type(uchar(result[i + 1]) ^ 0x10);
+		}
+	}
+	return result;
+}
+
+struct ClientHello {
+	QByteArray data;
+	QByteArray digest;
+};
+
+class ClientHelloGenerator {
+public:
+	ClientHelloGenerator(
+		const MTPTlsClientHello &rules,
+		const QByteArray &domain,
+		const bytes::vector &key);
+	[[nodiscard]] ClientHello result();
+
+private:
+	[[nodiscard]] bytes::span grow(int size);
+	void writeBlocks(const QVector<MTPTlsBlock> &blocks);
+	void writeBlock(const MTPTlsBlock &data);
+	void writeBlock(const MTPDtlsBlockString &data);
+	void writeBlock(const MTPDtlsBlockZero &data);
+	void writeBlock(const MTPDtlsBlockGrease &data);
+	void writeBlock(const MTPDtlsBlockRandom &data);
+	void writeBlock(const MTPDtlsBlockDomain &data);
+	void writeBlock(const MTPDtlsBlockScope &data);
+	void writePadding();
+	void writeDigest();
+	void writeTimestamp();
+
+	const QByteArray &_domain;
+	const bytes::vector &_key;
+	bytes::vector _greases;
+	std::vector<int> _scopeStack;
+	QByteArray _result;
+	QByteArray _digest;
+	int _digestPosition = -1;
+	bool _error = false;
+
+};
+
+ClientHelloGenerator::ClientHelloGenerator(
+	const MTPTlsClientHello &rules,
+	const QByteArray &domain,
+	const bytes::vector &key)
+: _domain(domain)
+, _key(key)
+, _greases(PrepareGreases()) {
+	_result.reserve(kClientHelloLength);
+	writeBlocks(rules.match([&](const MTPDtlsClientHello &data) {
+		return data.vblocks().v;
+	}));
+	writePadding();
+	writeDigest();
+	writeTimestamp();
+}
+
+ClientHello ClientHelloGenerator::result() {
+	return {
+		_error ? QByteArray() : std::move(_result),
+		_error ? QByteArray() : std::move(_digest) };
+}
+
+bytes::span ClientHelloGenerator::grow(int size) {
+	if (_error
+		|| size <= 0
+		|| _result.size() + size > kClientHelloLength) {
+		_error = true;
+		return bytes::span();
+	}
+
+	const auto offset = _result.size();
+	_result.resize(offset + size);
+	return bytes::make_detached_span(_result).subspan(offset);
+}
+
+void ClientHelloGenerator::writeBlocks(const QVector<MTPTlsBlock> &blocks) {
+	for (const auto &block : blocks) {
+		writeBlock(block);
+	}
+}
+
+void ClientHelloGenerator::writeBlock(const MTPTlsBlock &data) {
+	data.match([&](const auto &data) {
+		writeBlock(data);
+	});
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockString &data) {
+	const auto &bytes = data.vdata().v;
+	const auto storage = grow(bytes.size());
+	if (storage.empty()) {
+		return;
+	}
+	bytes::copy(storage, bytes::make_span(bytes));
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockZero &data) {
+	const auto length = data.vlength().v;
+	const auto already = _result.size();
+	const auto storage = grow(length);
+	if (storage.empty()) {
+		return;
+	}
+	if (length == kHelloDigestLength && _digestPosition < 0) {
+		_digestPosition = already;
+	}
+	bytes::set_with_const(storage, bytes::type(0));
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockGrease &data) {
+	const auto seed = data.vseed().v;
+	if (seed < 0 || seed >= _greases.size()) {
+		_error = true;
+		return;
+	}
+	const auto storage = grow(2);
+	if (storage.empty()) {
+		return;
+	}
+	bytes::set_with_const(storage, _greases[seed]);
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockRandom &data) {
+	const auto length = data.vlength().v;
+	const auto storage = grow(length);
+	if (storage.empty()) {
+		return;
+	}
+	bytes::set_random(storage);
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockDomain &data) {
+	const auto storage = grow(_domain.size());
+	if (storage.empty()) {
+		return;
+	}
+	bytes::copy(storage, bytes::make_span(_domain));
+}
+
+void ClientHelloGenerator::writeBlock(const MTPDtlsBlockScope &data) {
+	const auto already = _result.size();
+	const auto storage = grow(kLengthSize);
+	if (storage.empty()) {
+		return;
+	}
+	writeBlocks(data.ventries().v);
+	const auto length = qToBigEndian(uint16(_result.size() - already));
+	bytes::copy(storage, bytes::object_as_span(&length));
+}
+
+void ClientHelloGenerator::writePadding() {
+	const auto padding = kClientHelloLength - kLengthSize - _result.size();
+	writeBlock(MTP_tlsBlockScope(
+		MTP_vector<MTPTlsBlock>(1, MTP_tlsBlockZero(MTP_int(padding)))));
+}
+
+void ClientHelloGenerator::writeDigest() {
+	if (_digestPosition < 0) {
+		_error = true;
+		return;
+	}
+	bytes::copy(
+		bytes::make_detached_span(_result).subspan(_digestPosition),
+		openssl::HmacSha256(_key, bytes::make_span(_result)));
+}
+
+void ClientHelloGenerator::writeTimestamp() {
+	if (_digestPosition < 0) {
+		_error = true;
+		return;
+	}
+	const auto storage = bytes::make_detached_span(_result).subspan(
+		_digestPosition + kHelloDigestLength - sizeof(int32),
+		sizeof(int32));
+	auto already = int32();
+	bytes::copy(bytes::object_as_span(&already), storage);
+	already ^= qToLittleEndian(int32(unixtime()));
+	bytes::copy(storage, bytes::object_as_span(&already));
+
+	_digest = QByteArray(kHelloDigestLength, Qt::Uninitialized);
+	bytes::copy(
+		bytes::make_detached_span(_digest),
+		bytes::make_detached_span(_result).subspan(
+			_digestPosition,
+			kHelloDigestLength));
+}
+
+[[nodiscard]] ClientHello PrepareClientHello(
+		const MTPTlsClientHello &rules,
+		const QByteArray &domain,
+		const bytes::vector &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;
+	}
+	return !bytes::compare(
+		data.subspan(0, check.size()),
+		bytes::make_span(check.data(), check.size()));
+}
+
+[[nodiscard]] int ReadPartLength(bytes::const_span data, int offset) {
+	const auto storage = data.subspan(offset, kLengthSize);
+	return qFromBigEndian(
+		*reinterpret_cast<const uint16*>(storage.data()));
+}
+
+} // namespace
+
+TlsSocket::TlsSocket(
+	not_null<QThread*> thread,
+	const bytes::vector &secret,
+	const ProxyData &proxy)
+: AbstractSocket(thread)
+, _key(ExtractKey(secret, proxy)) {
+	_socket.moveToThread(thread);
+	_socket.setProxy(ToNetworkProxy(proxy));
+	const auto wrap = [&](auto handler) {
+		return [=](auto &&...args) {
+			InvokeQueued(this, [=] { handler(args...); });
+		};
+	};
+	using Error = QAbstractSocket::SocketError;
+	connect(
+		&_socket,
+		&QTcpSocket::connected,
+		wrap([=] { plainConnected(); }));
+	connect(
+		&_socket,
+		&QTcpSocket::disconnected,
+		wrap([=] { plainDisconnected(); }));
+	connect(
+		&_socket,
+		&QTcpSocket::readyRead,
+		wrap([=] { plainReadyRead(); }));
+
+	using ErrorSignal = void(QTcpSocket::*)(QAbstractSocket::SocketError);
+	const auto QTcpSocket_error = ErrorSignal(&QAbstractSocket::error);
+	connect(
+		&_socket,
+		QTcpSocket_error,
+		wrap([=](Error e) { handleError(e); }));
+}
+
+void TlsSocket::plainConnected() {
+	if (_state != State::Connecting) {
+		return;
+	}
+
+	static const auto kClientHelloRules = PrepareClientHelloRules();
+	const auto hello = PrepareClientHello(
+		kClientHelloRules,
+		"google.com",
+		_key);
+	if (hello.data.isEmpty()) {
+		LOG(("TLS Error: Could not generate Client Hello!"));
+		_state = State::Error;
+		_error.fire({});
+	} else {
+		_state = State::WaitingHello;
+		_incoming = hello.digest;
+		_socket.write(hello.data);
+	}
+}
+
+void TlsSocket::plainDisconnected() {
+	_state = State::NotConnected;
+	_incoming = QByteArray();
+	_serverHelloLength = 0;
+	_disconnected.fire({});
+}
+
+void TlsSocket::plainReadyRead() {
+	switch (_state) {
+	case State::WaitingHello: return readHello();
+	case State::Ready:
+	case State::Working: return readData();
+	}
+}
+
+bool TlsSocket::requiredHelloPartReady() const {
+	return _incoming.size() >= kHelloDigestLength + _serverHelloLength;
+}
+
+void TlsSocket::readHello() {
+	const auto parts1Size = kServerHelloPart1.size() + kLengthSize;
+	if (!_serverHelloLength) {
+		_serverHelloLength = parts1Size;
+	}
+	while (!requiredHelloPartReady()) {
+		if (!_socket.bytesAvailable()) {
+			return;
+		}
+		_incoming.append(_socket.readAll());
+	}
+	checkHelloParts12(parts1Size);
+}
+
+void TlsSocket::checkHelloParts12(int parts1Size) {
+	const auto data = bytes::make_span(_incoming).subspan(
+		kHelloDigestLength,
+		parts1Size);
+	const auto part2Size = ReadPartLength(data, parts1Size - kLengthSize);
+	const auto parts123Size = parts1Size
+		+ part2Size
+		+ kServerHelloPart3.size()
+		+ kLengthSize;
+	if (_serverHelloLength == parts1Size) {
+		const auto part1Offset = parts1Size
+			- kLengthSize
+			- kServerHelloPart1.size();
+		if (!CheckPart(data.subspan(part1Offset), kServerHelloPart1)) {
+			LOG(("TLS Error: Bad Server Hello part1."));
+			handleError();
+			return;
+		}
+		_serverHelloLength = parts123Size;
+		if (!requiredHelloPartReady()) {
+			readHello();
+			return;
+		}
+	}
+	checkHelloParts34(parts123Size);
+}
+
+void TlsSocket::checkHelloParts34(int parts123Size) {
+	const auto data = bytes::make_span(_incoming).subspan(
+		kHelloDigestLength,
+		parts123Size);
+	const auto part4Size = ReadPartLength(data, parts123Size - kLengthSize);
+	const auto full = parts123Size + part4Size;
+	if (_serverHelloLength == parts123Size) {
+		const auto part3Offset = parts123Size
+			- kLengthSize
+			- kServerHelloPart3.size();
+		if (!CheckPart(data.subspan(part3Offset), kServerHelloPart3)) {
+			LOG(("TLS Error: Bad Server Hello part."));
+			handleError();
+			return;
+		}
+		_serverHelloLength = full;
+		if (!requiredHelloPartReady()) {
+			readHello();
+			return;
+		}
+	}
+	checkHelloDigest();
+}
+
+void TlsSocket::checkHelloDigest() {
+	const auto incoming = bytes::make_detached_span(_incoming);
+	const auto fulldata = incoming.subspan(
+		0,
+		kHelloDigestLength + _serverHelloLength);
+	const auto digest = fulldata.subspan(
+		kHelloDigestLength + kServerHelloDigestPosition,
+		kHelloDigestLength);
+	const auto digestCopy = bytes::make_vector(digest);
+	bytes::set_with_const(digest, bytes::type(0));
+	const auto check = openssl::HmacSha256(_key, fulldata);
+	if (bytes::compare(digestCopy, check) != 0) {
+		LOG(("TLS Error: Bad Server Hello digest."));
+		handleError();
+		return;
+	}
+	if (incoming.size() > fulldata.size()) {
+		bytes::move(incoming, incoming.subspan(fulldata.size()));
+		_incoming.chop(fulldata.size());
+		InvokeQueued(this, [=] { readData(); });
+	} else {
+		_incoming.clear();
+	}
+	_state = State::Ready;
+	_connected.fire({});
+}
+
+void TlsSocket::readData() {
+}
+
+void TlsSocket::connectToHost(const QString &address, int port) {
+	Expects(_state == State::NotConnected);
+
+	_state = State::Connecting;
+	_socket.connectToHost(address, port);
+}
+
+bool TlsSocket::isConnected() {
+	return (_socket.state() == QAbstractSocket::ConnectedState);
+}
+
+bool TlsSocket::hasBytesAvailable() {
+	return _socket.bytesAvailable();
+}
+
+int64 TlsSocket::read(char *buffer, int64 maxLength) {
+	return _socket.read(buffer, maxLength);
+}
+
+int64 TlsSocket::write(const char *buffer, int64 length) {
+	return _socket.write(buffer, length);
+}
+
+int32 TlsSocket::debugState() {
+	return _socket.state();
+}
+
+void TlsSocket::handleError(int errorCode) {
+	if (errorCode) {
+		TcpSocket::LogError(errorCode, _socket.errorString());
+	}
+	_state = State::Error;
+	_error.fire({});
+}
+
+} // namespace internal
+} // namespace MTP
diff --git a/Telegram/SourceFiles/mtproto/mtp_tls_socket.h b/Telegram/SourceFiles/mtproto/mtp_tls_socket.h
new file mode 100644
index 000000000..0f42301c8
--- /dev/null
+++ b/Telegram/SourceFiles/mtproto/mtp_tls_socket.h
@@ -0,0 +1,60 @@
+/*
+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/mtp_abstract_socket.h"
+
+namespace MTP {
+namespace internal {
+
+class TlsSocket : public AbstractSocket {
+public:
+	TlsSocket(
+		not_null<QThread*> thread,
+		const bytes::vector &secret,
+		const ProxyData &proxy);
+
+	void connectToHost(const QString &address, int port) override;
+	bool isConnected() override;
+	bool hasBytesAvailable() override;
+	int64 read(char *buffer, int64 maxLength) override;
+	int64 write(const char *buffer, int64 length) override;
+
+	int32 debugState() override;
+
+private:
+	enum class State {
+		NotConnected,
+		Connecting,
+		WaitingHello,
+		Ready,
+		Working,
+		Error,
+	};
+
+	void plainConnected();
+	void plainDisconnected();
+	void plainReadyRead();
+	void handleError(int errorCode = 0);
+	[[nodiscard]] bool requiredHelloPartReady() const;
+	void readHello();
+	void checkHelloParts12(int parts1Size);
+	void checkHelloParts34(int parts123Size);
+	void checkHelloDigest();
+	void readData();
+
+	QTcpSocket _socket;
+	bytes::vector _key;
+	State _state = State::NotConnected;
+	QByteArray _incoming;
+	int16 _serverHelloLength = 0;
+
+};
+
+} // namespace internal
+} // namespace MTP
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index 3e2b56007..556f314da 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -535,6 +535,8 @@
 <(src_loc)/mtproto/mtp_abstract_socket.h
 <(src_loc)/mtproto/mtp_tcp_socket.cpp
 <(src_loc)/mtproto/mtp_tcp_socket.h
+<(src_loc)/mtproto/mtp_tls_socket.cpp
+<(src_loc)/mtproto/mtp_tls_socket.h
 <(src_loc)/mtproto/mtp_instance.cpp
 <(src_loc)/mtproto/mtp_instance.h
 <(src_loc)/mtproto/rsa_public_key.cpp