/* 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 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(_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 account, std::unique_ptr config) { Expects(_localKey != nullptr); account->prepareToStartAdded(_localKey); account->start(std::move(config)); } void Domain::startWithSingleAccount( const QByteArray &passcode, std::unique_ptr 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(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(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(); auto sessions = base::flat_set(); 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( _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(_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