283 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 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 "storage/storage_domain.h"
 | |
| 
 | |
| #include "storage/details/storage_file_utilities.h"
 | |
| #include "storage/serialize_common.h"
 | |
| #include "mtproto/mtproto_config.h"
 | |
| #include "main/main_domain.h"
 | |
| #include "main/main_account.h"
 | |
| #include "base/random.h"
 | |
| 
 | |
| namespace Storage {
 | |
| namespace {
 | |
| 
 | |
| using namespace details;
 | |
| 
 | |
| [[nodiscard]] QString BaseGlobalPath() {
 | |
| 	return cWorkingDir() + qsl("tdata/");
 | |
| }
 | |
| 
 | |
| [[nodiscard]] QString ComputeKeyName(const QString &dataName) {
 | |
| 	// We dropped old test authorizations when migrated to multi auth.
 | |
| 	//return "key_" + dataName + (cTestMode() ? "[test]" : "");
 | |
| 	return "key_" + dataName;
 | |
| }
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| Domain::Domain(not_null<Main::Domain*> owner, const QString &dataName)
 | |
| : _owner(owner)
 | |
| , _dataName(dataName) {
 | |
| }
 | |
| 
 | |
| Domain::~Domain() = default;
 | |
| 
 | |
| StartResult Domain::start(const QByteArray &passcode) {
 | |
| 	const auto modern = startModern(passcode);
 | |
| 	if (modern == StartModernResult::Success) {
 | |
| 		if (_oldVersion < AppVersion) {
 | |
| 			writeAccounts();
 | |
| 		}
 | |
| 		return StartResult::Success;
 | |
| 	} else if (modern == StartModernResult::IncorrectPasscode) {
 | |
| 		return StartResult::IncorrectPasscode;
 | |
| 	} else if (modern == StartModernResult::Failed) {
 | |
| 		startFromScratch();
 | |
| 		return StartResult::Success;
 | |
| 	}
 | |
| 	auto legacy = std::make_unique<Main::Account>(_owner, _dataName, 0);
 | |
| 	const auto result = legacy->legacyStart(passcode);
 | |
| 	if (result == StartResult::Success) {
 | |
| 		_oldVersion = legacy->local().oldMapVersion();
 | |
| 		startWithSingleAccount(passcode, std::move(legacy));
 | |
| 	}
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void Domain::startAdded(
 | |
| 		not_null<Main::Account*> account,
 | |
| 		std::unique_ptr<MTP::Config> config) {
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	account->prepareToStartAdded(_localKey);
 | |
| 	account->start(std::move(config));
 | |
| }
 | |
| 
 | |
| void Domain::startWithSingleAccount(
 | |
| 		const QByteArray &passcode,
 | |
| 		std::unique_ptr<Main::Account> account) {
 | |
| 	Expects(account != nullptr);
 | |
| 
 | |
| 	if (auto localKey = account->local().peekLegacyLocalKey()) {
 | |
| 		_localKey = std::move(localKey);
 | |
| 		encryptLocalKey(passcode);
 | |
| 		account->start(nullptr);
 | |
| 	} else {
 | |
| 		generateLocalKey();
 | |
| 		account->start(account->prepareToStart(_localKey));
 | |
| 	}
 | |
| 	_owner->accountAddedInStorage(Main::Domain::AccountWithIndex{
 | |
| 		.account = std::move(account)
 | |
| 	});
 | |
| 	writeAccounts();
 | |
| }
 | |
| 
 | |
| void Domain::generateLocalKey() {
 | |
| 	Expects(_localKey == nullptr);
 | |
| 	Expects(_passcodeKeySalt.isEmpty());
 | |
| 	Expects(_passcodeKeyEncrypted.isEmpty());
 | |
| 
 | |
| 	auto pass = QByteArray(MTP::AuthKey::kSize, Qt::Uninitialized);
 | |
| 	auto salt = QByteArray(LocalEncryptSaltSize, Qt::Uninitialized);
 | |
| 	base::RandomFill(pass.data(), pass.size());
 | |
| 	base::RandomFill(salt.data(), salt.size());
 | |
| 	_localKey = CreateLocalKey(pass, salt);
 | |
| 
 | |
| 	encryptLocalKey(QByteArray());
 | |
| }
 | |
| 
 | |
| void Domain::encryptLocalKey(const QByteArray &passcode) {
 | |
| 	_passcodeKeySalt.resize(LocalEncryptSaltSize);
 | |
| 	base::RandomFill(_passcodeKeySalt.data(), _passcodeKeySalt.size());
 | |
| 	_passcodeKey = CreateLocalKey(passcode, _passcodeKeySalt);
 | |
| 
 | |
| 	EncryptedDescriptor passKeyData(MTP::AuthKey::kSize);
 | |
| 	_localKey->write(passKeyData.stream);
 | |
| 	_passcodeKeyEncrypted = PrepareEncrypted(passKeyData, _passcodeKey);
 | |
| 	_hasLocalPasscode = !passcode.isEmpty();
 | |
| }
 | |
| 
 | |
| Domain::StartModernResult Domain::startModern(
 | |
| 		const QByteArray &passcode) {
 | |
| 	const auto name = ComputeKeyName(_dataName);
 | |
| 
 | |
| 	FileReadDescriptor keyData;
 | |
| 	if (!ReadFile(keyData, name, BaseGlobalPath())) {
 | |
| 		return StartModernResult::Empty;
 | |
| 	}
 | |
| 	LOG(("App Info: reading accounts info..."));
 | |
| 
 | |
| 	QByteArray salt, keyEncrypted, infoEncrypted;
 | |
| 	keyData.stream >> salt >> keyEncrypted >> infoEncrypted;
 | |
| 	if (!CheckStreamStatus(keyData.stream)) {
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 
 | |
| 	if (salt.size() != LocalEncryptSaltSize) {
 | |
| 		LOG(("App Error: bad salt in info file, size: %1").arg(salt.size()));
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 	_passcodeKey = CreateLocalKey(passcode, salt);
 | |
| 
 | |
| 	EncryptedDescriptor keyInnerData, info;
 | |
| 	if (!DecryptLocal(keyInnerData, keyEncrypted, _passcodeKey)) {
 | |
| 		LOG(("App Info: could not decrypt pass-protected key from info file, "
 | |
| 			"maybe bad password..."));
 | |
| 		return StartModernResult::IncorrectPasscode;
 | |
| 	}
 | |
| 	auto key = Serialize::read<MTP::AuthKey::Data>(keyInnerData.stream);
 | |
| 	if (keyInnerData.stream.status() != QDataStream::Ok
 | |
| 		|| !keyInnerData.stream.atEnd()) {
 | |
| 		LOG(("App Error: could not read pass-protected key from info file"));
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 	_localKey = std::make_shared<MTP::AuthKey>(key);
 | |
| 
 | |
| 	_passcodeKeyEncrypted = keyEncrypted;
 | |
| 	_passcodeKeySalt = salt;
 | |
| 	_hasLocalPasscode = !passcode.isEmpty();
 | |
| 
 | |
| 	if (!DecryptLocal(info, infoEncrypted, _localKey)) {
 | |
| 		LOG(("App Error: could not decrypt info."));
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 	LOG(("App Info: reading encrypted info..."));
 | |
| 	auto count = qint32();
 | |
| 	info.stream >> count;
 | |
| 	if (count <= 0 || count > Main::Domain::kMaxAccounts) {
 | |
| 		LOG(("App Error: bad accounts count: %1").arg(count));
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 
 | |
| 	_oldVersion = keyData.version;
 | |
| 
 | |
| 	auto tried = base::flat_set<int>();
 | |
| 	auto sessions = base::flat_set<uint64>();
 | |
| 	auto active = 0;
 | |
| 	for (auto i = 0; i != count; ++i) {
 | |
| 		auto index = qint32();
 | |
| 		info.stream >> index;
 | |
| 		if (index >= 0
 | |
| 			&& index < Main::Domain::kMaxAccounts
 | |
| 			&& tried.emplace(index).second) {
 | |
| 			auto account = std::make_unique<Main::Account>(
 | |
| 				_owner,
 | |
| 				_dataName,
 | |
| 				index);
 | |
| 			auto config = account->prepareToStart(_localKey);
 | |
| 			const auto sessionId = account->willHaveSessionUniqueId(
 | |
| 				config.get());
 | |
| 			if (!sessions.contains(sessionId)
 | |
| 				&& (sessionId != 0 || (sessions.empty() && i + 1 == count))) {
 | |
| 				if (sessions.empty()) {
 | |
| 					active = index;
 | |
| 				}
 | |
| 				account->start(std::move(config));
 | |
| 				_owner->accountAddedInStorage({
 | |
| 					.index = index,
 | |
| 					.account = std::move(account)
 | |
| 				});
 | |
| 				sessions.emplace(sessionId);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if (sessions.empty()) {
 | |
| 		LOG(("App Error: no accounts read."));
 | |
| 		return StartModernResult::Failed;
 | |
| 	}
 | |
| 
 | |
| 	if (!info.stream.atEnd()) {
 | |
| 		info.stream >> active;
 | |
| 	}
 | |
| 	_owner->activateFromStorage(active);
 | |
| 
 | |
| 	Ensures(!sessions.empty());
 | |
| 	return StartModernResult::Success;
 | |
| }
 | |
| 
 | |
| void Domain::writeAccounts() {
 | |
| 	Expects(!_owner->accounts().empty());
 | |
| 
 | |
| 	const auto path = BaseGlobalPath();
 | |
| 	if (!QDir().exists(path)) {
 | |
| 		QDir().mkpath(path);
 | |
| 	}
 | |
| 
 | |
| 	FileWriteDescriptor key(ComputeKeyName(_dataName), path);
 | |
| 	key.writeData(_passcodeKeySalt);
 | |
| 	key.writeData(_passcodeKeyEncrypted);
 | |
| 
 | |
| 	const auto &list = _owner->accounts();
 | |
| 
 | |
| 	auto keySize = sizeof(qint32) + sizeof(qint32) * list.size();
 | |
| 
 | |
| 	EncryptedDescriptor keyData(keySize);
 | |
| 	keyData.stream << qint32(list.size());
 | |
| 	for (const auto &[index, account] : list) {
 | |
| 		keyData.stream << qint32(index);
 | |
| 	}
 | |
| 	keyData.stream << qint32(_owner->activeForStorage());
 | |
| 	key.writeEncrypted(keyData, _localKey);
 | |
| }
 | |
| 
 | |
| void Domain::startFromScratch() {
 | |
| 	startWithSingleAccount(
 | |
| 		QByteArray(),
 | |
| 		std::make_unique<Main::Account>(_owner, _dataName, 0));
 | |
| }
 | |
| 
 | |
| bool Domain::checkPasscode(const QByteArray &passcode) const {
 | |
| 	Expects(!_passcodeKeySalt.isEmpty());
 | |
| 	Expects(_passcodeKey != nullptr);
 | |
| 
 | |
| 	const auto checkKey = CreateLocalKey(passcode, _passcodeKeySalt);
 | |
| 	return checkKey->equals(_passcodeKey);
 | |
| }
 | |
| 
 | |
| void Domain::setPasscode(const QByteArray &passcode) {
 | |
| 	Expects(!_passcodeKeySalt.isEmpty());
 | |
| 	Expects(_localKey != nullptr);
 | |
| 
 | |
| 	encryptLocalKey(passcode);
 | |
| 	writeAccounts();
 | |
| 
 | |
| 	_passcodeKeyChanged.fire({});
 | |
| }
 | |
| 
 | |
| int Domain::oldVersion() const {
 | |
| 	return _oldVersion;
 | |
| }
 | |
| 
 | |
| void Domain::clearOldVersion() {
 | |
| 	_oldVersion = 0;
 | |
| }
 | |
| 
 | |
| QString Domain::webviewDataPath() const {
 | |
| 	return BaseGlobalPath() + "webview";
 | |
| }
 | |
| 
 | |
| rpl::producer<> Domain::localPasscodeChanged() const {
 | |
| 	return _passcodeKeyChanged.events();
 | |
| }
 | |
| 
 | |
| bool Domain::hasLocalPasscode() const {
 | |
| 	return _hasLocalPasscode;
 | |
| }
 | |
| 
 | |
| } // namespace Storage
 | 
