storage_file_utilities.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "storage/details/storage_file_utilities.h"
  8. #include "mtproto/mtproto_auth_key.h"
  9. #include "base/platform/base_platform_file_utilities.h"
  10. #include "base/openssl_help.h"
  11. #include "base/random.h"
  12. #include <crl/crl_object_on_thread.h>
  13. #include <QtCore/QtEndian>
  14. #include <QtCore/QSaveFile>
  15. namespace Storage {
  16. namespace details {
  17. namespace {
  18. constexpr char TdfMagic[] = { 'T', 'D', 'F', '$' };
  19. constexpr auto TdfMagicLen = int(sizeof(TdfMagic));
  20. constexpr auto kStrongIterationsCount = 100'000;
  21. struct WriteEntry {
  22. QString basePath;
  23. QString base;
  24. QByteArray data;
  25. QByteArray md5;
  26. };
  27. class WriteManager final {
  28. public:
  29. explicit WriteManager(crl::weak_on_thread<WriteManager> weak);
  30. void write(WriteEntry &&entry);
  31. void writeSync(WriteEntry &&entry);
  32. void writeSyncAll();
  33. private:
  34. void scheduleWrite();
  35. void writeScheduled();
  36. bool writeOneScheduledNow();
  37. void writeNow(WriteEntry &&entry);
  38. template <typename File>
  39. [[nodiscard]] bool open(File &file, const WriteEntry &entry, char postfix);
  40. [[nodiscard]] QString path(const WriteEntry &entry, char postfix) const;
  41. [[nodiscard]] bool writeHeader(
  42. const QString &basePath,
  43. QFileDevice &file);
  44. crl::weak_on_thread<WriteManager> _weak;
  45. std::deque<WriteEntry> _scheduled;
  46. };
  47. class AsyncWriteManager final {
  48. public:
  49. void write(WriteEntry &&entry);
  50. void writeSync(WriteEntry &&entry);
  51. void sync();
  52. void stop();
  53. private:
  54. std::optional<crl::object_on_thread<WriteManager>> _manager;
  55. bool _finished = false;
  56. };
  57. WriteManager::WriteManager(crl::weak_on_thread<WriteManager> weak)
  58. : _weak(std::move(weak)) {
  59. }
  60. void WriteManager::write(WriteEntry &&entry) {
  61. const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base);
  62. if (i == end(_scheduled)) {
  63. _scheduled.push_back(std::move(entry));
  64. } else {
  65. *i = std::move(entry);
  66. }
  67. scheduleWrite();
  68. }
  69. void WriteManager::writeSync(WriteEntry &&entry) {
  70. const auto i = ranges::find(_scheduled, entry.base, &WriteEntry::base);
  71. if (i != end(_scheduled)) {
  72. _scheduled.erase(i);
  73. }
  74. writeNow(std::move(entry));
  75. }
  76. void WriteManager::writeNow(WriteEntry &&entry) {
  77. const auto path = [&](char postfix) {
  78. return this->path(entry, postfix);
  79. };
  80. const auto open = [&](auto &file, char postfix) {
  81. return this->open(file, entry, postfix);
  82. };
  83. const auto write = [&](auto &file) {
  84. file.write(entry.data);
  85. file.write(entry.md5);
  86. };
  87. const auto safe = path('s');
  88. const auto simple = path('0');
  89. const auto backup = path('1');
  90. QSaveFile save;
  91. if (open(save, 's')) {
  92. write(save);
  93. if (save.commit()) {
  94. QFile::remove(simple);
  95. QFile::remove(backup);
  96. return;
  97. }
  98. LOG(("Storage Error: Could not commit '%1'.").arg(safe));
  99. }
  100. QFile plain;
  101. if (open(plain, '0')) {
  102. write(plain);
  103. base::Platform::FlushFileData(plain);
  104. plain.close();
  105. QFile::remove(backup);
  106. if (base::Platform::RenameWithOverwrite(simple, safe)) {
  107. return;
  108. }
  109. QFile::remove(safe);
  110. LOG(("Storage Error: Could not rename '%1' to '%2', removing.").arg(
  111. simple,
  112. safe));
  113. }
  114. }
  115. void WriteManager::writeSyncAll() {
  116. while (writeOneScheduledNow()) {
  117. }
  118. }
  119. bool WriteManager::writeOneScheduledNow() {
  120. if (_scheduled.empty()) {
  121. return false;
  122. }
  123. auto entry = std::move(_scheduled.front());
  124. _scheduled.pop_front();
  125. writeNow(std::move(entry));
  126. return true;
  127. }
  128. bool WriteManager::writeHeader(const QString &basePath, QFileDevice &file) {
  129. if (!file.open(QIODevice::WriteOnly)) {
  130. const auto dir = QDir(basePath);
  131. if (dir.exists()) {
  132. return false;
  133. } else if (!QDir().mkpath(dir.absolutePath())) {
  134. return false;
  135. } else if (!file.open(QIODevice::WriteOnly)) {
  136. return false;
  137. }
  138. }
  139. file.write(TdfMagic, TdfMagicLen);
  140. const auto version = qint32(AppVersion);
  141. file.write((const char*)&version, sizeof(version));
  142. return true;
  143. }
  144. QString WriteManager::path(const WriteEntry &entry, char postfix) const {
  145. return entry.base + postfix;
  146. }
  147. template <typename File>
  148. bool WriteManager::open(File &file, const WriteEntry &entry, char postfix) {
  149. const auto name = path(entry, postfix);
  150. file.setFileName(name);
  151. if (!writeHeader(entry.basePath, file)) {
  152. LOG(("Storage Error: Could not open '%1' for writing.").arg(name));
  153. return false;
  154. }
  155. return true;
  156. }
  157. void WriteManager::scheduleWrite() {
  158. _weak.with([](WriteManager &that) {
  159. that.writeScheduled();
  160. });
  161. }
  162. void WriteManager::writeScheduled() {
  163. if (writeOneScheduledNow() && !_scheduled.empty()) {
  164. scheduleWrite();
  165. }
  166. }
  167. void AsyncWriteManager::write(WriteEntry &&entry) {
  168. Expects(!_finished);
  169. if (!_manager) {
  170. _manager.emplace();
  171. }
  172. _manager->with([entry = std::move(entry)](WriteManager &manager) mutable {
  173. manager.write(std::move(entry));
  174. });
  175. }
  176. void AsyncWriteManager::writeSync(WriteEntry &&entry) {
  177. Expects(!_finished);
  178. if (!_manager) {
  179. _manager.emplace();
  180. }
  181. _manager->with_sync([&](WriteManager &manager) {
  182. manager.writeSync(std::move(entry));
  183. });
  184. }
  185. void AsyncWriteManager::sync() {
  186. if (_manager) {
  187. _manager->with_sync([](WriteManager &manager) {
  188. manager.writeSyncAll();
  189. });
  190. }
  191. }
  192. void AsyncWriteManager::stop() {
  193. if (_manager) {
  194. sync();
  195. _manager.reset();
  196. }
  197. _finished = true;
  198. }
  199. AsyncWriteManager Manager;
  200. } // namespace
  201. QString ToFilePart(FileKey val) {
  202. QString result;
  203. result.reserve(0x10);
  204. for (int32 i = 0; i < 0x10; ++i) {
  205. uchar v = (val & 0x0F);
  206. result.push_back((v < 0x0A) ? QChar('0' + v) : QChar('A' + (v - 0x0A)));
  207. val >>= 4;
  208. }
  209. return result;
  210. }
  211. bool KeyAlreadyUsed(QString &name) {
  212. name += '0';
  213. if (QFileInfo::exists(name)) {
  214. return true;
  215. }
  216. name[name.size() - 1] = '1';
  217. if (QFileInfo::exists(name)) {
  218. return true;
  219. }
  220. name[name.size() - 1] = 's';
  221. if (QFileInfo::exists(name)) {
  222. return true;
  223. }
  224. return false;
  225. }
  226. FileKey GenerateKey(const QString &basePath) {
  227. FileKey result;
  228. QString path;
  229. path.reserve(basePath.size() + 0x11);
  230. path += basePath;
  231. do {
  232. result = base::RandomValue<FileKey>();
  233. path.resize(basePath.size());
  234. path += ToFilePart(result);
  235. } while (!result || KeyAlreadyUsed(path));
  236. return result;
  237. }
  238. void ClearKey(const FileKey &key, const QString &basePath) {
  239. QString name;
  240. name.reserve(basePath.size() + 0x11);
  241. name.append(basePath).append(ToFilePart(key)).append('0');
  242. QFile::remove(name);
  243. name[name.size() - 1] = '1';
  244. QFile::remove(name);
  245. name[name.size() - 1] = 's';
  246. QFile::remove(name);
  247. }
  248. bool CheckStreamStatus(QDataStream &stream) {
  249. if (stream.status() != QDataStream::Ok) {
  250. LOG(("Bad data stream status: %1").arg(stream.status()));
  251. return false;
  252. }
  253. return true;
  254. }
  255. MTP::AuthKeyPtr CreateLocalKey(
  256. const QByteArray &passcode,
  257. const QByteArray &salt) {
  258. const auto s = bytes::make_span(salt);
  259. const auto hash = openssl::Sha512(s, bytes::make_span(passcode), s);
  260. const auto iterationsCount = passcode.isEmpty()
  261. ? 1 // Don't slow down for no password.
  262. : kStrongIterationsCount;
  263. auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
  264. PKCS5_PBKDF2_HMAC(
  265. reinterpret_cast<const char*>(hash.data()),
  266. hash.size(),
  267. reinterpret_cast<const unsigned char*>(s.data()),
  268. s.size(),
  269. iterationsCount,
  270. EVP_sha512(),
  271. key.size(),
  272. reinterpret_cast<unsigned char*>(key.data()));
  273. return std::make_shared<MTP::AuthKey>(key);
  274. }
  275. MTP::AuthKeyPtr CreateLegacyLocalKey(
  276. const QByteArray &passcode,
  277. const QByteArray &salt) {
  278. auto key = MTP::AuthKey::Data{ { gsl::byte{} } };
  279. const auto iterationsCount = passcode.isEmpty()
  280. ? LocalEncryptNoPwdIterCount // Don't slow down for no password.
  281. : LocalEncryptIterCount;
  282. PKCS5_PBKDF2_HMAC_SHA1(
  283. passcode.constData(),
  284. passcode.size(),
  285. (uchar*)salt.data(),
  286. salt.size(),
  287. iterationsCount,
  288. key.size(),
  289. (uchar*)key.data());
  290. return std::make_shared<MTP::AuthKey>(key);
  291. }
  292. FileReadDescriptor::~FileReadDescriptor() {
  293. if (version) {
  294. stream.setDevice(nullptr);
  295. if (buffer.isOpen()) {
  296. buffer.close();
  297. }
  298. buffer.setBuffer(nullptr);
  299. }
  300. }
  301. EncryptedDescriptor::EncryptedDescriptor() {
  302. }
  303. EncryptedDescriptor::EncryptedDescriptor(uint32 size) {
  304. uint32 fullSize = sizeof(uint32) + size;
  305. if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
  306. data.reserve(fullSize);
  307. data.resize(sizeof(uint32));
  308. buffer.setBuffer(&data);
  309. buffer.open(QIODevice::WriteOnly);
  310. buffer.seek(sizeof(uint32));
  311. stream.setDevice(&buffer);
  312. stream.setVersion(QDataStream::Qt_5_1);
  313. }
  314. EncryptedDescriptor::~EncryptedDescriptor() {
  315. finish();
  316. }
  317. void EncryptedDescriptor::finish() {
  318. if (stream.device()) stream.setDevice(nullptr);
  319. if (buffer.isOpen()) buffer.close();
  320. buffer.setBuffer(nullptr);
  321. }
  322. FileWriteDescriptor::FileWriteDescriptor(
  323. const FileKey &key,
  324. const QString &basePath,
  325. bool sync)
  326. : FileWriteDescriptor(ToFilePart(key), basePath, sync) {
  327. }
  328. FileWriteDescriptor::FileWriteDescriptor(
  329. const QString &name,
  330. const QString &basePath,
  331. bool sync)
  332. : _basePath(basePath)
  333. , _sync(sync) {
  334. init(name);
  335. }
  336. FileWriteDescriptor::~FileWriteDescriptor() {
  337. finish();
  338. }
  339. void FileWriteDescriptor::init(const QString &name) {
  340. _base = _basePath + name;
  341. _buffer.setBuffer(&_safeData);
  342. const auto opened = _buffer.open(QIODevice::WriteOnly);
  343. Assert(opened);
  344. _stream.setDevice(&_buffer);
  345. }
  346. void FileWriteDescriptor::writeData(const QByteArray &data) {
  347. if (!_stream.device()) {
  348. return;
  349. }
  350. _stream << data;
  351. quint32 len = data.isNull() ? 0xffffffff : data.size();
  352. if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
  353. len = qbswap(len);
  354. }
  355. _md5.feed(&len, sizeof(len));
  356. _md5.feed(data.constData(), data.size());
  357. _fullSize += sizeof(len) + data.size();
  358. }
  359. void FileWriteDescriptor::writeEncrypted(
  360. EncryptedDescriptor &data,
  361. const MTP::AuthKeyPtr &key) {
  362. writeData(PrepareEncrypted(data, key));
  363. }
  364. void FileWriteDescriptor::finish() {
  365. if (!_stream.device()) {
  366. return;
  367. }
  368. _stream.setDevice(nullptr);
  369. _md5.feed(&_fullSize, sizeof(_fullSize));
  370. qint32 version = AppVersion;
  371. _md5.feed(&version, sizeof(version));
  372. _md5.feed(TdfMagic, TdfMagicLen);
  373. _buffer.close();
  374. auto entry = WriteEntry{
  375. .basePath = _basePath,
  376. .base = _base,
  377. .data = _safeData,
  378. .md5 = QByteArray((const char*)_md5.result(), 0x10)
  379. };
  380. if (_sync) {
  381. Manager.writeSync(std::move(entry));
  382. } else {
  383. Manager.write(std::move(entry));
  384. }
  385. }
  386. [[nodiscard]] QByteArray PrepareEncrypted(
  387. EncryptedDescriptor &data,
  388. const MTP::AuthKeyPtr &key) {
  389. data.finish();
  390. QByteArray &toEncrypt(data.data);
  391. // prepare for encryption
  392. uint32 size = toEncrypt.size(), fullSize = size;
  393. if (fullSize & 0x0F) {
  394. fullSize += 0x10 - (fullSize & 0x0F);
  395. toEncrypt.resize(fullSize);
  396. base::RandomFill(toEncrypt.data() + size, fullSize - size);
  397. }
  398. *(uint32*)toEncrypt.data() = size;
  399. QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
  400. hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
  401. MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, key, encrypted.constData());
  402. return encrypted;
  403. }
  404. bool ReadFile(
  405. FileReadDescriptor &result,
  406. const QString &name,
  407. const QString &basePath) {
  408. const auto base = basePath + name;
  409. // detect order of read attempts
  410. QString toTry[2];
  411. const auto modern = base + 's';
  412. if (QFileInfo::exists(modern)) {
  413. toTry[0] = modern;
  414. } else {
  415. // Legacy way.
  416. toTry[0] = base + '0';
  417. QFileInfo toTry0(toTry[0]);
  418. if (toTry0.exists()) {
  419. toTry[1] = basePath + name + '1';
  420. QFileInfo toTry1(toTry[1]);
  421. if (toTry1.exists()) {
  422. QDateTime mod0 = toTry0.lastModified();
  423. QDateTime mod1 = toTry1.lastModified();
  424. if (mod0 < mod1) {
  425. qSwap(toTry[0], toTry[1]);
  426. }
  427. } else {
  428. toTry[1] = QString();
  429. }
  430. } else {
  431. toTry[0][toTry[0].size() - 1] = '1';
  432. }
  433. }
  434. for (int32 i = 0; i < 2; ++i) {
  435. QString fname(toTry[i]);
  436. if (fname.isEmpty()) break;
  437. QFile f(fname);
  438. if (!f.open(QIODevice::ReadOnly)) {
  439. DEBUG_LOG(("App Info: failed to open '%1' for reading"
  440. ).arg(name));
  441. continue;
  442. }
  443. // check magic
  444. char magic[TdfMagicLen];
  445. if (f.read(magic, TdfMagicLen) != TdfMagicLen) {
  446. DEBUG_LOG(("App Info: failed to read magic from '%1'"
  447. ).arg(name));
  448. continue;
  449. }
  450. if (memcmp(magic, TdfMagic, TdfMagicLen)) {
  451. DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(
  452. Logs::mb(magic, TdfMagicLen).str(),
  453. name));
  454. continue;
  455. }
  456. // read app version
  457. qint32 version;
  458. if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
  459. DEBUG_LOG(("App Info: failed to read version from '%1'"
  460. ).arg(name));
  461. continue;
  462. }
  463. if (version > AppVersion) {
  464. DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3"
  465. ).arg(version
  466. ).arg(name
  467. ).arg(AppVersion));
  468. continue;
  469. }
  470. // read data
  471. QByteArray bytes = f.read(f.size());
  472. int32 dataSize = bytes.size() - 16;
  473. if (dataSize < 0) {
  474. DEBUG_LOG(("App Info: bad file '%1', could not read sign part"
  475. ).arg(name));
  476. continue;
  477. }
  478. // check signature
  479. HashMd5 md5;
  480. md5.feed(bytes.constData(), dataSize);
  481. md5.feed(&dataSize, sizeof(dataSize));
  482. md5.feed(&version, sizeof(version));
  483. md5.feed(magic, TdfMagicLen);
  484. if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
  485. DEBUG_LOG(("App Info: bad file '%1', signature did not match"
  486. ).arg(name));
  487. continue;
  488. }
  489. bytes.resize(dataSize);
  490. result.data = bytes;
  491. bytes = QByteArray();
  492. result.version = version;
  493. result.buffer.setBuffer(&result.data);
  494. result.buffer.open(QIODevice::ReadOnly);
  495. result.stream.setDevice(&result.buffer);
  496. result.stream.setVersion(QDataStream::Qt_5_1);
  497. if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
  498. QFile::remove(toTry[1 - i]);
  499. }
  500. return true;
  501. }
  502. return false;
  503. }
  504. bool DecryptLocal(
  505. EncryptedDescriptor &result,
  506. const QByteArray &encrypted,
  507. const MTP::AuthKeyPtr &key) {
  508. if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
  509. LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
  510. return false;
  511. }
  512. uint32 fullLen = encrypted.size() - 16;
  513. QByteArray decrypted;
  514. decrypted.resize(fullLen);
  515. const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
  516. aesDecryptLocal(encryptedData, decrypted.data(), fullLen, key, encryptedKey);
  517. uchar sha1Buffer[20];
  518. if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
  519. LOG(("App Info: bad decrypt key, data not decrypted - incorrect password?"));
  520. return false;
  521. }
  522. uint32 dataLen = *(const uint32*)decrypted.constData();
  523. if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
  524. LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
  525. return false;
  526. }
  527. decrypted.resize(dataLen);
  528. result.data = decrypted;
  529. decrypted = QByteArray();
  530. result.buffer.setBuffer(&result.data);
  531. result.buffer.open(QIODevice::ReadOnly);
  532. result.buffer.seek(sizeof(uint32)); // skip len
  533. result.stream.setDevice(&result.buffer);
  534. result.stream.setVersion(QDataStream::Qt_5_1);
  535. return true;
  536. }
  537. bool ReadEncryptedFile(
  538. FileReadDescriptor &result,
  539. const QString &name,
  540. const QString &basePath,
  541. const MTP::AuthKeyPtr &key) {
  542. if (!ReadFile(result, name, basePath)) {
  543. return false;
  544. }
  545. QByteArray encrypted;
  546. result.stream >> encrypted;
  547. EncryptedDescriptor data;
  548. if (!DecryptLocal(data, encrypted, key)) {
  549. result.stream.setDevice(nullptr);
  550. if (result.buffer.isOpen()) result.buffer.close();
  551. result.buffer.setBuffer(nullptr);
  552. result.data = QByteArray();
  553. result.version = 0;
  554. return false;
  555. }
  556. result.stream.setDevice(0);
  557. if (result.buffer.isOpen()) {
  558. result.buffer.close();
  559. }
  560. result.buffer.setBuffer(0);
  561. result.data = data.data;
  562. result.buffer.setBuffer(&result.data);
  563. result.buffer.open(QIODevice::ReadOnly);
  564. result.buffer.seek(data.buffer.pos());
  565. result.stream.setDevice(&result.buffer);
  566. result.stream.setVersion(QDataStream::Qt_5_1);
  567. return true;
  568. }
  569. bool ReadEncryptedFile(
  570. FileReadDescriptor &result,
  571. const FileKey &fkey,
  572. const QString &basePath,
  573. const MTP::AuthKeyPtr &key) {
  574. return ReadEncryptedFile(result, ToFilePart(fkey), basePath, key);
  575. }
  576. void Sync() {
  577. Manager.sync();
  578. }
  579. void Finish() {
  580. Manager.stop();
  581. }
  582. } // namespace details
  583. } // namespace Storage