1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "mtproto/special_config_request.h"
9 
10 #include "mtproto/details/mtproto_rsa_public_key.h"
11 #include "mtproto/mtproto_dc_options.h"
12 #include "mtproto/mtproto_auth_key.h"
13 #include "base/unixtime.h"
14 #include "base/openssl_help.h"
15 #include "base/call_delayed.h"
16 
17 #include <QtCore/QJsonDocument>
18 #include <QtCore/QJsonArray>
19 #include <QtCore/QJsonObject>
20 
21 namespace MTP::details {
22 namespace {
23 
24 constexpr auto kSendNextTimeout = crl::time(800);
25 
26 constexpr auto kPublicKey = "\
27 -----BEGIN RSA PUBLIC KEY-----\n\
28 MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\
29 fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n\
30 192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n\
31 9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n\
32 fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n\
33 Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\
34 -----END RSA PUBLIC KEY-----\
35 "_cs;
36 
37 const auto kRemoteProject = "peak-vista-421";
38 const auto kFireProject = "reserve-5a846";
39 const auto kConfigKey = "ipconfig";
40 const auto kConfigSubKey = "v3";
41 const auto kApiKey = "AIzaSyC2-kAkpDsroixRXw-sTw-Wfqo4NxjMwwM";
42 const auto kAppId = "1:560508485281:web:4ee13a6af4e84d49e67ae0";
43 
ApiDomain(const QString & service)44 QString ApiDomain(const QString &service) {
45 	return service + ".googleapis.com";
46 }
47 
GenerateInstanceId()48 QString GenerateInstanceId() {
49 	auto fid = bytes::array<17>();
50 	bytes::set_random(fid);
51 	fid[0] = (bytes::type(0xF0) & fid[0]) | bytes::type(0x07);
52 	return QString::fromLatin1(
53 		QByteArray::fromRawData(
54 			reinterpret_cast<const char*>(fid.data()),
55 			fid.size()
56 		).toBase64(QByteArray::Base64UrlEncoding).mid(0, 22));
57 }
58 
InstanceId()59 QString InstanceId() {
60 	static const auto result = GenerateInstanceId();
61 	return result;
62 }
63 
CheckPhoneByPrefixesRules(const QString & phone,const QString & rules)64 bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) {
65 	const auto check = QString(phone).replace(
66 		QRegularExpression("[^0-9]"),
67 		QString());
68 	auto result = false;
69 	for (const auto &prefix : rules.split(',')) {
70 		if (prefix.isEmpty()) {
71 			result = true;
72 		} else if (prefix[0] == '+' && check.startsWith(prefix.mid(1))) {
73 			result = true;
74 		} else if (prefix[0] == '-' && check.startsWith(prefix.mid(1))) {
75 			return false;
76 		}
77 	}
78 	return result;
79 }
80 
ConcatenateDnsTxtFields(const std::vector<DnsEntry> & response)81 QByteArray ConcatenateDnsTxtFields(const std::vector<DnsEntry> &response) {
82 	auto entries = QMultiMap<int, QString>();
83 	for (const auto &entry : response) {
84 		entries.insert(INT_MAX - entry.data.size(), entry.data);
85 	}
86 	return QStringList(entries.values()).join(QString()).toLatin1();
87 }
88 
ParseRemoteConfigResponse(const QByteArray & bytes)89 QByteArray ParseRemoteConfigResponse(const QByteArray &bytes) {
90 	auto error = QJsonParseError{ 0, QJsonParseError::NoError };
91 	const auto document = QJsonDocument::fromJson(bytes, &error);
92 	if (error.error != QJsonParseError::NoError) {
93 		LOG(("Config Error: Failed to parse fire response JSON, error: %1"
94 			).arg(error.errorString()));
95 		return {};
96 	} else if (!document.isObject()) {
97 		LOG(("Config Error: Not an object received in fire response JSON."));
98 		return {};
99 	}
100 	return document.object().value(
101 		"entries"
102 	).toObject().value(
103 		qsl("%1%2").arg(kConfigKey, kConfigSubKey)
104 	).toString().toLatin1();
105 }
106 
ParseFireStoreResponse(const QByteArray & bytes)107 QByteArray ParseFireStoreResponse(const QByteArray &bytes) {
108 	auto error = QJsonParseError{ 0, QJsonParseError::NoError };
109 	const auto document = QJsonDocument::fromJson(bytes, &error);
110 	if (error.error != QJsonParseError::NoError) {
111 		LOG(("Config Error: Failed to parse fire response JSON, error: %1"
112 			).arg(error.errorString()));
113 		return {};
114 	} else if (!document.isObject()) {
115 		LOG(("Config Error: Not an object received in fire response JSON."));
116 		return {};
117 	}
118 	return document.object().value(
119 		"fields"
120 	).toObject().value(
121 		"data"
122 	).toObject().value(
123 		"stringValue"
124 	).toString().toLatin1();
125 }
126 
ParseRealtimeResponse(const QByteArray & bytes)127 QByteArray ParseRealtimeResponse(const QByteArray &bytes) {
128 	if (bytes.size() < 2
129 		|| bytes[0] != '"'
130 		|| bytes[bytes.size() - 1] != '"') {
131 		return QByteArray();
132 	}
133 	return bytes.mid(1, bytes.size() - 2);
134 }
135 
ParseHttpDate(const QString & date)136 [[nodiscard]] QDateTime ParseHttpDate(const QString &date) {
137 	// Wed, 10 Jul 2019 14:33:38 GMT
138 	static const auto expression = QRegularExpression(
139 		R"(\w\w\w, (\d\d) (\w\w\w) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT)");
140 	const auto match = expression.match(date);
141 	if (!match.hasMatch()) {
142 		return QDateTime();
143 	}
144 
145 	const auto number = [&](int index) {
146 		return match.capturedView(index).toInt();
147 	};
148 	const auto day = number(1);
149 	const auto month = [&] {
150 		static const auto months = {
151 			"Jan",
152 			"Feb",
153 			"Mar",
154 			"Apr",
155 			"May",
156 			"Jun",
157 			"Jul",
158 			"Aug",
159 			"Sep",
160 			"Oct",
161 			"Nov",
162 			"Dec"
163 		};
164 		const auto captured = match.capturedView(2);
165 		for (auto i = begin(months); i != end(months); ++i) {
166 			if (captured == QString(*i)) {
167 				return 1 + int(i - begin(months));
168 			}
169 		}
170 		return 0;
171 	}();
172 	const auto year = number(3);
173 	const auto hour = number(4);
174 	const auto minute = number(5);
175 	const auto second = number(6);
176 	return QDateTime(
177 		QDate(year, month, day),
178 		QTime(hour, minute, second),
179 		Qt::UTC);
180 }
181 
182 } // namespace
183 
SpecialConfigRequest(Fn<void (DcId dcId,const std::string & ip,int port,bytes::const_span secret)> callback,Fn<void ()> timeDoneCallback,const QString & domainString,const QString & phone)184 SpecialConfigRequest::SpecialConfigRequest(
185 	Fn<void(
186 		DcId dcId,
187 		const std::string &ip,
188 		int port,
189 		bytes::const_span secret)> callback,
190 	Fn<void()> timeDoneCallback,
191 	const QString &domainString,
192 	const QString &phone)
193 : _callback(std::move(callback))
194 , _timeDoneCallback(std::move(timeDoneCallback))
195 , _domainString(domainString)
196 , _phone(phone) {
197 	Expects((_callback == nullptr) != (_timeDoneCallback == nullptr));
198 
199 	_manager.setProxy(QNetworkProxy::NoProxy);
200 
201 	auto domains = DnsDomains();
202 	const auto domainsCount = domains.size();
203 
204 	std::random_device rd;
205 	ranges::shuffle(domains, std::mt19937(rd()));
206 	const auto takeDomain = [&] {
207 		const auto result = domains.back();
208 		domains.pop_back();
209 		return result;
210 	};
211 	const auto shuffle = [&](int from, int till) {
212 		Expects(till > from);
213 
214 		ranges::shuffle(
215 			begin(_attempts) + from,
216 			begin(_attempts) + till,
217 			std::mt19937(rd()));
218 	};
219 
220 	_attempts = {};
221 	_attempts.push_back({ Type::Google, "dns.google.com" });
222 	_attempts.push_back({ Type::Google, takeDomain(), "dns" });
223 	_attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
224 	_attempts.push_back({ Type::RemoteConfig, "firebaseremoteconfig" });
225 	while (!domains.empty()) {
226 		_attempts.push_back({ Type::Google, takeDomain(), "dns" });
227 	}
228 	if (!_timeDoneCallback) {
229 		_attempts.push_back({ Type::Realtime, "firebaseio.com" });
230 		_attempts.push_back({ Type::FireStore, "firestore" });
231 		for (const auto &domain : DnsDomains()) {
232 			_attempts.push_back({ Type::FireStore, domain, "firestore" });
233 		}
234 	}
235 
236 	shuffle(0, 2);
237 	shuffle(2, 4);
238 	if (!_timeDoneCallback) {
239 		shuffle(
240 			_attempts.size() - (2 + domainsCount),
241 			_attempts.size() - domainsCount);
242 		shuffle(_attempts.size() - domainsCount, _attempts.size());
243 	}
244 	ranges::reverse(_attempts); // We go from last to first.
245 
246 	sendNextRequest();
247 }
248 
SpecialConfigRequest(Fn<void (DcId dcId,const std::string & ip,int port,bytes::const_span secret)> callback,const QString & domainString,const QString & phone)249 SpecialConfigRequest::SpecialConfigRequest(
250 	Fn<void(
251 		DcId dcId,
252 		const std::string &ip,
253 		int port,
254 		bytes::const_span secret)> callback,
255 	const QString &domainString,
256 	const QString &phone)
257 : SpecialConfigRequest(std::move(callback), nullptr, domainString, phone) {
258 }
259 
SpecialConfigRequest(Fn<void ()> timeDoneCallback,const QString & domainString)260 SpecialConfigRequest::SpecialConfigRequest(
261 	Fn<void()> timeDoneCallback,
262 	const QString &domainString)
263 : SpecialConfigRequest(
264 	nullptr,
265 	std::move(timeDoneCallback),
266 	domainString,
267 	QString()) {
268 }
269 
sendNextRequest()270 void SpecialConfigRequest::sendNextRequest() {
271 	Expects(!_attempts.empty());
272 
273 	const auto attempt = _attempts.back();
274 	_attempts.pop_back();
275 	if (!_attempts.empty()) {
276 		base::call_delayed(kSendNextTimeout, this, [=] {
277 			sendNextRequest();
278 		});
279 	}
280 	performRequest(attempt);
281 }
282 
performRequest(const Attempt & attempt)283 void SpecialConfigRequest::performRequest(const Attempt &attempt) {
284 	const auto type = attempt.type;
285 	auto url = QUrl();
286 	url.setScheme(qsl("https"));
287 	auto request = QNetworkRequest();
288 	auto payload = QByteArray();
289 	switch (type) {
290 	case Type::Mozilla: {
291 		url.setHost(attempt.data);
292 		url.setPath(qsl("/dns-query"));
293 		url.setQuery(qsl("name=%1&type=16&random_padding=%2").arg(
294 			_domainString,
295 			GenerateDnsRandomPadding()));
296 		request.setRawHeader("accept", "application/dns-json");
297 	} break;
298 	case Type::Google: {
299 		url.setHost(attempt.data);
300 		url.setPath(qsl("/resolve"));
301 		url.setQuery(qsl("name=%1&type=ANY&random_padding=%2").arg(
302 			_domainString,
303 			GenerateDnsRandomPadding()));
304 		if (!attempt.host.isEmpty()) {
305 			const auto host = attempt.host + ".google.com";
306 			request.setRawHeader("Host", host.toLatin1());
307 		}
308 	} break;
309 	case Type::RemoteConfig: {
310 		url.setHost(ApiDomain(attempt.data));
311 		url.setPath(qsl("/v1/projects/%1/namespaces/firebase:fetch"
312 		).arg(kRemoteProject));
313 		url.setQuery(qsl("key=%1").arg(kApiKey));
314 		payload = qsl("{\"app_id\":\"%1\",\"app_instance_id\":\"%2\"}").arg(
315 			kAppId,
316 			InstanceId()).toLatin1();
317 		request.setRawHeader("Content-Type", "application/json");
318 	} break;
319 	case Type::Realtime: {
320 		url.setHost(kFireProject + qsl(".%1").arg(attempt.data));
321 		url.setPath(qsl("/%1%2.json").arg(kConfigKey, kConfigSubKey));
322 	} break;
323 	case Type::FireStore: {
324 		url.setHost(attempt.host.isEmpty()
325 			? ApiDomain(attempt.data)
326 			: attempt.data);
327 		url.setPath(qsl("/v1/projects/%1/databases/(default)/documents/%2/%3"
328 		).arg(
329 			kFireProject,
330 			kConfigKey,
331 			kConfigSubKey));
332 		if (!attempt.host.isEmpty()) {
333 			const auto host = ApiDomain(attempt.host);
334 			request.setRawHeader("Host", host.toLatin1());
335 		}
336 	} break;
337 	default: Unexpected("Type in SpecialConfigRequest::performRequest.");
338 	}
339 	request.setUrl(url);
340 	request.setRawHeader("User-Agent", DnsUserAgent());
341 	const auto reply = _requests.emplace_back(payload.isEmpty()
342 		? _manager.get(request)
343 		: _manager.post(request, payload)
344 	).reply;
345 	connect(reply, &QNetworkReply::finished, this, [=] {
346 		requestFinished(type, reply);
347 	});
348 }
349 
handleHeaderUnixtime(not_null<QNetworkReply * > reply)350 void SpecialConfigRequest::handleHeaderUnixtime(
351 		not_null<QNetworkReply*> reply) {
352 	if (reply->error() != QNetworkReply::NoError) {
353 		return;
354 	}
355 	const auto date = QString::fromLatin1([&] {
356 		for (const auto &pair : reply->rawHeaderPairs()) {
357 			if (pair.first == "Date") {
358 				return pair.second;
359 			}
360 		}
361 		return QByteArray();
362 	}());
363 	if (date.isEmpty()) {
364 		LOG(("Config Error: No 'Date' header received."));
365 		return;
366 	}
367 	const auto parsed = ParseHttpDate(date);
368 	if (!parsed.isValid()) {
369 		LOG(("Config Error: Bad 'Date' header received: %1").arg(date));
370 		return;
371 	}
372 	base::unixtime::http_update(parsed.toSecsSinceEpoch());
373 	if (_timeDoneCallback) {
374 		_timeDoneCallback();
375 	}
376 }
377 
requestFinished(Type type,not_null<QNetworkReply * > reply)378 void SpecialConfigRequest::requestFinished(
379 		Type type,
380 		not_null<QNetworkReply*> reply) {
381 	handleHeaderUnixtime(reply);
382 	const auto result = finalizeRequest(reply);
383 	if (!_callback || result.isEmpty()) {
384 		return;
385 	}
386 
387 	switch (type) {
388 	case Type::Mozilla:
389 	case Type::Google: {
390 		constexpr auto kTypeRestriction = 16; // TXT
391 		handleResponse(ConcatenateDnsTxtFields(
392 			ParseDnsResponse(result, kTypeRestriction)));
393 	} break;
394 	case Type::RemoteConfig: {
395 		handleResponse(ParseRemoteConfigResponse(result));
396 	} break;
397 	case Type::Realtime: {
398 		handleResponse(ParseRealtimeResponse(result));
399 	} break;
400 	case Type::FireStore: {
401 		handleResponse(ParseFireStoreResponse(result));
402 	} break;
403 	default: Unexpected("Type in SpecialConfigRequest::requestFinished.");
404 	}
405 }
406 
finalizeRequest(not_null<QNetworkReply * > reply)407 QByteArray SpecialConfigRequest::finalizeRequest(
408 		not_null<QNetworkReply*> reply) {
409 	if (reply->error() != QNetworkReply::NoError) {
410 		DEBUG_LOG(("Config Error: Failed to get response, error: %2 (%3)"
411 			).arg(reply->errorString()
412 			).arg(reply->error()));
413 	}
414 	const auto result = reply->readAll();
415 	const auto from = ranges::remove(
416 		_requests,
417 		reply,
418 		[](const ServiceWebRequest &request) { return request.reply; });
419 	_requests.erase(from, end(_requests));
420 	return result;
421 }
422 
decryptSimpleConfig(const QByteArray & bytes)423 bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) {
424 	auto cleanBytes = bytes;
425 	auto removeFrom = std::remove_if(cleanBytes.begin(), cleanBytes.end(), [](char ch) {
426 		auto isGoodBase64 = (ch == '+') || (ch == '=') || (ch == '/')
427 			|| (ch >= 'a' && ch <= 'z')
428 			|| (ch >= 'A' && ch <= 'Z')
429 			|| (ch >= '0' && ch <= '9');
430 		return !isGoodBase64;
431 	});
432 	if (removeFrom != cleanBytes.end()) {
433 		cleanBytes.remove(removeFrom - cleanBytes.begin(), cleanBytes.end() - removeFrom);
434 	}
435 
436 	constexpr auto kGoodSizeBase64 = 344;
437 	if (cleanBytes.size() != kGoodSizeBase64) {
438 		LOG(("Config Error: Bad data size %1 required %2").arg(cleanBytes.size()).arg(kGoodSizeBase64));
439 		return false;
440 	}
441 	constexpr auto kGoodSizeData = 256;
442 	auto decodedBytes = QByteArray::fromBase64(cleanBytes, QByteArray::Base64Encoding);
443 	if (decodedBytes.size() != kGoodSizeData) {
444 		LOG(("Config Error: Bad data size %1 required %2").arg(decodedBytes.size()).arg(kGoodSizeData));
445 		return false;
446 	}
447 
448 	auto publicKey = details::RSAPublicKey(bytes::make_span(kPublicKey));
449 	auto decrypted = publicKey.decrypt(bytes::make_span(decodedBytes));
450 	auto decryptedBytes = gsl::make_span(decrypted);
451 
452 	auto aesEncryptedBytes = decryptedBytes.subspan(CTRState::KeySize);
453 	auto aesivec = bytes::make_vector(decryptedBytes.subspan(CTRState::KeySize - CTRState::IvecSize, CTRState::IvecSize));
454 	AES_KEY aeskey;
455 	AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(decryptedBytes.data()), CTRState::KeySize * CHAR_BIT, &aeskey);
456 	AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(aesEncryptedBytes.data()), reinterpret_cast<unsigned char*>(aesEncryptedBytes.data()), aesEncryptedBytes.size(), &aeskey, reinterpret_cast<unsigned char*>(aesivec.data()), AES_DECRYPT);
457 
458 	constexpr auto kDigestSize = 16;
459 	auto dataSize = aesEncryptedBytes.size() - kDigestSize;
460 	auto data = aesEncryptedBytes.subspan(0, dataSize);
461 	auto hash = openssl::Sha256(data);
462 	if (bytes::compare(gsl::make_span(hash).subspan(0, kDigestSize), aesEncryptedBytes.subspan(dataSize)) != 0) {
463 		LOG(("Config Error: Bad digest."));
464 		return false;
465 	}
466 
467 	mtpBuffer buffer;
468 	buffer.resize(data.size() / sizeof(mtpPrime));
469 	bytes::copy(bytes::make_span(buffer), data);
470 	auto from = &*buffer.cbegin();
471 	auto end = from + buffer.size();
472 	auto realLength = *from++;
473 	if (realLength <= 0 || realLength > dataSize || (realLength & 0x03)) {
474 		LOG(("Config Error: Bad length %1.").arg(realLength));
475 		return false;
476 	}
477 
478 	if (!_simpleConfig.read(from, end)) {
479 		LOG(("Config Error: Could not read configSimple."));
480 		return false;
481 	}
482 	if ((end - from) * sizeof(mtpPrime) != (dataSize - realLength)) {
483 		LOG(("Config Error: Bad read length %1, should be %2.").arg((end - from) * sizeof(mtpPrime)).arg(dataSize - realLength));
484 		return false;
485 	}
486 	return true;
487 }
488 
handleResponse(const QByteArray & bytes)489 void SpecialConfigRequest::handleResponse(const QByteArray &bytes) {
490 	if (!decryptSimpleConfig(bytes)) {
491 		return;
492 	}
493 	Assert(_simpleConfig.type() == mtpc_help_configSimple);
494 	const auto &config = _simpleConfig.c_help_configSimple();
495 	const auto now = base::unixtime::http_now();
496 	if (now > config.vexpires().v) {
497 		LOG(("Config Error: "
498 			"Bad date frame for simple config: %1-%2, our time is %3."
499 			).arg(config.vdate().v
500 			).arg(config.vexpires().v
501 			).arg(now));
502 		return;
503 	}
504 	if (config.vrules().v.empty()) {
505 		LOG(("Config Error: Empty simple config received."));
506 		return;
507 	}
508 	for (const auto &rule : config.vrules().v) {
509 		Assert(rule.type() == mtpc_accessPointRule);
510 		const auto &data = rule.c_accessPointRule();
511 		const auto phoneRules = qs(data.vphone_prefix_rules());
512 		if (!CheckPhoneByPrefixesRules(_phone, phoneRules)) {
513 			continue;
514 		}
515 
516 		const auto dcId = data.vdc_id().v;
517 		for (const auto &address : data.vips().v) {
518 			const auto parseIp = [](const MTPint &ipv4) {
519 				const auto ip = *reinterpret_cast<const uint32*>(&ipv4.v);
520 				return qsl("%1.%2.%3.%4"
521 				).arg((ip >> 24) & 0xFF
522 				).arg((ip >> 16) & 0xFF
523 				).arg((ip >> 8) & 0xFF
524 				).arg(ip & 0xFF).toStdString();
525 			};
526 			switch (address.type()) {
527 			case mtpc_ipPort: {
528 				const auto &fields = address.c_ipPort();
529 				const auto ip = parseIp(fields.vipv4());
530 				if (!ip.empty()) {
531 					_callback(dcId, ip, fields.vport().v, {});
532 				}
533 			} break;
534 			case mtpc_ipPortSecret: {
535 				const auto &fields = address.c_ipPortSecret();
536 				const auto ip = parseIp(fields.vipv4());
537 				if (!ip.empty()) {
538 					_callback(
539 						dcId,
540 						ip,
541 						fields.vport().v,
542 						bytes::make_span(fields.vsecret().v));
543 				}
544 			} break;
545 			default: Unexpected("Type in simpleConfig ips.");
546 			}
547 		}
548 	}
549 	_callback(0, std::string(), 0, {});
550 }
551 
552 } // namespace MTP::details
553