2020-06-15 16:25:02 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
2020-06-18 18:04:16 +00:00
|
|
|
#include "storage/storage_domain.h"
|
2020-06-15 16:25:02 +00:00
|
|
|
|
|
|
|
#include "storage/details/storage_file_utilities.h"
|
|
|
|
#include "storage/serialize_common.h"
|
2020-06-17 09:36:25 +00:00
|
|
|
#include "mtproto/mtproto_config.h"
|
2020-06-18 18:04:16 +00:00
|
|
|
#include "main/main_domain.h"
|
2020-06-15 16:25:02 +00:00
|
|
|
#include "main/main_account.h"
|
|
|
|
|
|
|
|
namespace Storage {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
using namespace details;
|
|
|
|
|
|
|
|
[[nodiscard]] QString BaseGlobalPath() {
|
|
|
|
return cWorkingDir() + qsl("tdata/");
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] QString ComputeKeyName(const QString &dataName) {
|
2020-06-17 09:36:25 +00:00
|
|
|
// We dropped old test authorizations when migrated to multi auth.
|
|
|
|
//return "key_" + dataName + (cTestMode() ? "[test]" : "");
|
|
|
|
return "key_" + dataName;
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
Domain::Domain(not_null<Main::Domain*> owner, const QString &dataName)
|
2020-06-15 16:25:02 +00:00
|
|
|
: _owner(owner)
|
|
|
|
, _dataName(dataName) {
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
Domain::~Domain() = default;
|
2020-06-15 16:25:02 +00:00
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
StartResult Domain::start(const QByteArray &passcode) {
|
2020-06-16 09:40:43 +00:00
|
|
|
const auto modern = startModern(passcode);
|
2020-06-15 16:25:02 +00:00
|
|
|
if (modern == StartModernResult::Success) {
|
|
|
|
if (_oldVersion < AppVersion) {
|
|
|
|
writeAccounts();
|
|
|
|
}
|
|
|
|
return StartResult::Success;
|
|
|
|
} else if (modern == StartModernResult::IncorrectPasscode) {
|
|
|
|
return StartResult::IncorrectPasscode;
|
|
|
|
} else if (modern == StartModernResult::Failed) {
|
2020-06-16 09:40:43 +00:00
|
|
|
startFromScratch();
|
2020-06-15 16:25:02 +00:00
|
|
|
return StartResult::Success;
|
|
|
|
}
|
2020-06-18 18:04:16 +00:00
|
|
|
auto legacy = std::make_unique<Main::Account>(_owner, _dataName, 0);
|
2020-06-15 16:25:02 +00:00
|
|
|
const auto result = legacy->legacyStart(passcode);
|
|
|
|
if (result == StartResult::Success) {
|
|
|
|
_oldVersion = legacy->local().oldMapVersion();
|
2020-06-16 09:40:43 +00:00
|
|
|
startWithSingleAccount(passcode, std::move(legacy));
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::startAdded(
|
2020-06-17 09:36:25 +00:00
|
|
|
not_null<Main::Account*> account,
|
|
|
|
std::unique_ptr<MTP::Config> config) {
|
2020-06-16 06:42:47 +00:00
|
|
|
Expects(_localKey != nullptr);
|
|
|
|
|
2020-06-18 11:17:58 +00:00
|
|
|
account->prepareToStartAdded(_localKey);
|
|
|
|
account->start(std::move(config));
|
2020-06-16 06:42:47 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::startWithSingleAccount(
|
2020-06-15 16:25:02 +00:00
|
|
|
const QByteArray &passcode,
|
|
|
|
std::unique_ptr<Main::Account> account) {
|
|
|
|
Expects(account != nullptr);
|
|
|
|
|
|
|
|
if (auto localKey = account->local().peekLegacyLocalKey()) {
|
|
|
|
_localKey = std::move(localKey);
|
|
|
|
encryptLocalKey(passcode);
|
2020-06-29 16:07:56 +00:00
|
|
|
account->start(nullptr);
|
2020-06-15 16:25:02 +00:00
|
|
|
} else {
|
|
|
|
generateLocalKey();
|
2020-06-18 11:17:58 +00:00
|
|
|
account->start(account->prepareToStart(_localKey));
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
2020-06-26 08:27:54 +00:00
|
|
|
_owner->accountAddedInStorage(Main::Domain::AccountWithIndex{
|
|
|
|
.account = std::move(account)
|
|
|
|
});
|
2020-06-15 16:25:02 +00:00
|
|
|
writeAccounts();
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::generateLocalKey() {
|
2020-06-15 16:25:02 +00:00
|
|
|
Expects(_localKey == nullptr);
|
|
|
|
Expects(_passcodeKeySalt.isEmpty());
|
|
|
|
Expects(_passcodeKeyEncrypted.isEmpty());
|
|
|
|
|
|
|
|
auto pass = QByteArray(MTP::AuthKey::kSize, Qt::Uninitialized);
|
|
|
|
auto salt = QByteArray(LocalEncryptSaltSize, Qt::Uninitialized);
|
|
|
|
memset_rand(pass.data(), pass.size());
|
|
|
|
memset_rand(salt.data(), salt.size());
|
|
|
|
_localKey = CreateLocalKey(pass, salt);
|
|
|
|
|
|
|
|
encryptLocalKey(QByteArray());
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::encryptLocalKey(const QByteArray &passcode) {
|
2020-06-15 16:25:02 +00:00
|
|
|
_passcodeKeySalt.resize(LocalEncryptSaltSize);
|
|
|
|
memset_rand(_passcodeKeySalt.data(), _passcodeKeySalt.size());
|
|
|
|
_passcodeKey = CreateLocalKey(passcode, _passcodeKeySalt);
|
|
|
|
|
|
|
|
EncryptedDescriptor passKeyData(MTP::AuthKey::kSize);
|
|
|
|
_localKey->write(passKeyData.stream);
|
|
|
|
_passcodeKeyEncrypted = PrepareEncrypted(passKeyData, _passcodeKey);
|
2021-05-25 20:38:01 +00:00
|
|
|
_hasLocalPasscode = !passcode.isEmpty();
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
Domain::StartModernResult Domain::startModern(
|
2020-06-16 09:40:43 +00:00
|
|
|
const QByteArray &passcode) {
|
2020-06-15 16:25:02 +00:00
|
|
|
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;
|
2021-05-25 20:38:01 +00:00
|
|
|
_hasLocalPasscode = !passcode.isEmpty();
|
2020-06-15 16:25:02 +00:00
|
|
|
|
|
|
|
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;
|
2020-06-18 18:04:16 +00:00
|
|
|
if (count <= 0 || count > Main::Domain::kMaxAccounts) {
|
2020-06-15 16:25:02 +00:00
|
|
|
LOG(("App Error: bad accounts count: %1").arg(count));
|
|
|
|
return StartModernResult::Failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
_oldVersion = keyData.version;
|
|
|
|
|
|
|
|
auto tried = base::flat_set<int>();
|
2020-06-24 09:32:07 +00:00
|
|
|
auto sessions = base::flat_set<uint64>();
|
2020-06-19 12:59:31 +00:00
|
|
|
auto active = 0;
|
2020-06-15 16:25:02 +00:00
|
|
|
for (auto i = 0; i != count; ++i) {
|
|
|
|
auto index = qint32();
|
|
|
|
info.stream >> index;
|
|
|
|
if (index >= 0
|
2020-06-18 18:04:16 +00:00
|
|
|
&& index < Main::Domain::kMaxAccounts
|
2020-06-15 16:25:02 +00:00
|
|
|
&& tried.emplace(index).second) {
|
2020-06-18 18:04:16 +00:00
|
|
|
auto account = std::make_unique<Main::Account>(
|
|
|
|
_owner,
|
|
|
|
_dataName,
|
|
|
|
index);
|
2020-06-18 11:17:58 +00:00
|
|
|
auto config = account->prepareToStart(_localKey);
|
2020-06-24 09:32:07 +00:00
|
|
|
const auto sessionId = account->willHaveSessionUniqueId(
|
|
|
|
config.get());
|
|
|
|
if (!sessions.contains(sessionId)
|
|
|
|
&& (sessionId != 0 || (sessions.empty() && i + 1 == count))) {
|
|
|
|
if (sessions.empty()) {
|
2020-06-19 12:59:31 +00:00
|
|
|
active = index;
|
|
|
|
}
|
2020-06-18 11:17:58 +00:00
|
|
|
account->start(std::move(config));
|
2020-06-19 12:59:31 +00:00
|
|
|
_owner->accountAddedInStorage({
|
|
|
|
.index = index,
|
|
|
|
.account = std::move(account)
|
|
|
|
});
|
2020-06-24 09:32:07 +00:00
|
|
|
sessions.emplace(sessionId);
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-24 09:32:07 +00:00
|
|
|
if (sessions.empty()) {
|
|
|
|
LOG(("App Error: no accounts read."));
|
|
|
|
return StartModernResult::Failed;
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:59:31 +00:00
|
|
|
if (!info.stream.atEnd()) {
|
|
|
|
info.stream >> active;
|
|
|
|
}
|
|
|
|
_owner->activateFromStorage(active);
|
2020-06-15 16:25:02 +00:00
|
|
|
|
2020-06-24 09:32:07 +00:00
|
|
|
Ensures(!sessions.empty());
|
2020-06-15 16:25:02 +00:00
|
|
|
return StartModernResult::Success;
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::writeAccounts() {
|
|
|
|
Expects(!_owner->accounts().empty());
|
2020-06-15 16:25:02 +00:00
|
|
|
|
|
|
|
const auto path = BaseGlobalPath();
|
|
|
|
if (!QDir().exists(path)) {
|
|
|
|
QDir().mkpath(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
FileWriteDescriptor key(ComputeKeyName(_dataName), path);
|
|
|
|
key.writeData(_passcodeKeySalt);
|
|
|
|
key.writeData(_passcodeKeyEncrypted);
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
const auto &list = _owner->accounts();
|
2020-06-15 16:25:02 +00:00
|
|
|
|
|
|
|
auto keySize = sizeof(qint32) + sizeof(qint32) * list.size();
|
|
|
|
|
|
|
|
EncryptedDescriptor keyData(keySize);
|
|
|
|
keyData.stream << qint32(list.size());
|
|
|
|
for (const auto &[index, account] : list) {
|
2020-06-19 12:59:31 +00:00
|
|
|
keyData.stream << qint32(index);
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
2020-06-19 17:13:43 +00:00
|
|
|
keyData.stream << qint32(_owner->activeForStorage());
|
2020-06-15 16:25:02 +00:00
|
|
|
key.writeEncrypted(keyData, _localKey);
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::startFromScratch() {
|
2020-06-16 09:40:43 +00:00
|
|
|
startWithSingleAccount(
|
|
|
|
QByteArray(),
|
2020-06-18 18:04:16 +00:00
|
|
|
std::make_unique<Main::Account>(_owner, _dataName, 0));
|
2020-06-16 09:40:43 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
bool Domain::checkPasscode(const QByteArray &passcode) const {
|
2020-06-15 16:25:02 +00:00
|
|
|
Expects(!_passcodeKeySalt.isEmpty());
|
|
|
|
Expects(_passcodeKey != nullptr);
|
|
|
|
|
|
|
|
const auto checkKey = CreateLocalKey(passcode, _passcodeKeySalt);
|
|
|
|
return checkKey->equals(_passcodeKey);
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::setPasscode(const QByteArray &passcode) {
|
2020-06-15 16:25:02 +00:00
|
|
|
Expects(!_passcodeKeySalt.isEmpty());
|
|
|
|
Expects(_localKey != nullptr);
|
|
|
|
|
|
|
|
encryptLocalKey(passcode);
|
|
|
|
writeAccounts();
|
|
|
|
|
2021-05-25 20:38:01 +00:00
|
|
|
_passcodeKeyChanged.fire({});
|
2020-06-15 16:25:02 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
int Domain::oldVersion() const {
|
2020-06-15 16:25:02 +00:00
|
|
|
return _oldVersion;
|
|
|
|
}
|
|
|
|
|
2020-06-18 18:04:16 +00:00
|
|
|
void Domain::clearOldVersion() {
|
2020-06-15 16:25:02 +00:00
|
|
|
_oldVersion = 0;
|
|
|
|
}
|
|
|
|
|
2021-03-24 11:30:01 +00:00
|
|
|
QString Domain::webviewDataPath() const {
|
|
|
|
return BaseGlobalPath() + "webview";
|
|
|
|
}
|
|
|
|
|
2021-05-25 20:38:01 +00:00
|
|
|
rpl::producer<> Domain::localPasscodeChanged() const {
|
|
|
|
return _passcodeKeyChanged.events();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Domain::hasLocalPasscode() const {
|
|
|
|
return _hasLocalPasscode;
|
|
|
|
}
|
|
|
|
|
2020-06-15 16:25:02 +00:00
|
|
|
} // namespace Storage
|