1 /*
2 Copyright © 2014-2019 by The qTox Project Contributors
3
4 This file is part of qTox, a Qt-based graphical interface for Tox.
5
6 qTox is libre software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 qTox is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "rawdatabase.h"
21
22 #include <cassert>
23 #include <tox/toxencryptsave.h>
24
25 #include <QCoreApplication>
26 #include <QDebug>
27 #include <QFile>
28 #include <QMetaObject>
29 #include <QMutexLocker>
30
31
32 /**
33 * @class RawDatabase
34 * @brief Implements a low level RAII interface to a SQLCipher (SQlite3) database.
35 *
36 * Thread-safe, does all database operations on a worker thread.
37 * The queries must not contain transaction commands (BEGIN/COMMIT/...) or the behavior is
38 * undefined.
39 *
40 * @var QMutex RawDatabase::transactionsMutex;
41 * @brief Protects pendingTransactions
42 */
43
44 /**
45 * @class Query
46 * @brief A query to be executed by the database.
47 *
48 * Can be composed of one or more SQL statements in the query,
49 * optional BLOB parameters to be bound, and callbacks fired when the query is executed
50 * Calling any database method from a query callback is undefined behavior.
51 *
52 * @var QByteArray RawDatabase::Query::query
53 * @brief UTF-8 query string
54 *
55 * @var QVector<QByteArray> RawDatabase::Query::blobs
56 * @brief Bound data blobs
57 *
58 * @var std::function<void(int64_t)> RawDatabase::Query::insertCallback
59 * @brief Called after execution with the last insert rowid
60 *
61 * @var std::function<void(const QVector<QVariant>&)> RawDatabase::Query::rowCallback
62 * @brief Called during execution for each row
63 *
64 * @var QVector<sqlite3_stmt*> RawDatabase::Query::statements
65 * @brief Statements to be compiled from the query
66 */
67
68 /**
69 * @struct Transaction
70 * @brief SQL transactions to be processed.
71 *
72 * A transaction is made of queries, which can have bound BLOBs.
73 *
74 * @var std::atomic_bool* RawDatabase::Transaction::success = nullptr;
75 * @brief If not a nullptr, the result of the transaction will be set
76 *
77 * @var std::atomic_bool* RawDatabase::Transaction::done = nullptr;
78 * @brief If not a nullptr, will be set to true when the transaction has been executed
79 */
80
81 /**
82 * @brief Tries to open a database.
83 * @param path Path to database.
84 * @param password If empty, the database will be opened unencrypted.
85 * Otherwise we will use toxencryptsave to derive a key and encrypt the database.
86 */
RawDatabase(const QString & path,const QString & password,const QByteArray & salt)87 RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt)
88 : workerThread{new QThread}
89 , path{path}
90 , currentSalt{salt} // we need the salt later if a new password should be set
91 , currentHexKey{deriveKey(password, salt)}
92 {
93 workerThread->setObjectName("qTox Database");
94 moveToThread(workerThread.get());
95 workerThread->start();
96
97 // first try with the new salt
98 if (open(path, currentHexKey)) {
99 return;
100 }
101
102 // avoid opening the same db twice
103 close();
104
105 // create a backup before trying to upgrade to new salt
106 bool upgrade = true;
107 if (!QFile::copy(path, path + ".bak")) {
108 qDebug() << "Couldn't create the backup of the database, won't upgrade";
109 upgrade = false;
110 }
111
112 // fall back to the old salt
113 currentHexKey = deriveKey(password);
114 if (open(path, currentHexKey)) {
115 // upgrade only if backup successful
116 if (upgrade) {
117 // still using old salt, upgrade
118 if (setPassword(password)) {
119 qDebug() << "Successfully upgraded to dynamic salt";
120 } else {
121 qWarning() << "Failed to set password with new salt";
122 }
123 }
124 } else {
125 qDebug() << "Failed to open database with old salt";
126 }
127 }
128
~RawDatabase()129 RawDatabase::~RawDatabase()
130 {
131 close();
132 workerThread->exit(0);
133 while (workerThread->isRunning())
134 workerThread->wait(50);
135 }
136
137 /**
138 * @brief Tries to open the database with the given (possibly empty) key.
139 * @param path Path to database.
140 * @param hexKey Hex representation of the key in string.
141 * @return True if success, false otherwise.
142 */
open(const QString & path,const QString & hexKey)143 bool RawDatabase::open(const QString& path, const QString& hexKey)
144 {
145 if (QThread::currentThread() != workerThread.get()) {
146 bool ret;
147 QMetaObject::invokeMethod(this, "open", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ret),
148 Q_ARG(const QString&, path), Q_ARG(const QString&, hexKey));
149 return ret;
150 }
151
152 if (!QFile::exists(path) && QFile::exists(path + ".tmp")) {
153 qWarning() << "Restoring database from temporary export file! Did we crash while changing "
154 "the password or upgrading?";
155 QFile::rename(path + ".tmp", path);
156 }
157
158 if (sqlite3_open_v2(path.toUtf8().data(), &sqlite,
159 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr)
160 != SQLITE_OK) {
161 qWarning() << "Failed to open database" << path << "with error:" << sqlite3_errmsg(sqlite);
162 return false;
163 }
164
165 if (sqlite3_create_function(sqlite, "regexp", 2, SQLITE_UTF8, nullptr, &RawDatabase::regexpInsensitive, nullptr, nullptr)) {
166 qWarning() << "Failed to create function regexp";
167 close();
168 return false;
169 }
170
171 if (sqlite3_create_function(sqlite, "regexpsensitive", 2, SQLITE_UTF8, nullptr, &RawDatabase::regexpSensitive, nullptr, nullptr)) {
172 qWarning() << "Failed to create function regexpsensitive";
173 close();
174 return false;
175 }
176
177 if (!hexKey.isEmpty()) {
178 if (!openEncryptedDatabaseAtLatestSupportedVersion(hexKey)) {
179 close();
180 return false;
181 }
182 }
183 return true;
184 }
185
openEncryptedDatabaseAtLatestSupportedVersion(const QString & hexKey)186 bool RawDatabase::openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey)
187 {
188 // old qTox database are saved with SQLCipher 3.x defaults. For a period after 1.16.3 but before 1.17.0, databases
189 // could be partially upgraded to SQLCipher 4.0 defaults, since SQLCipher 3.x isn't capable of setitng all the same
190 // params. If SQLCipher 4.x happened to be used, they would have been fully upgraded to 4.0 default params.
191 // We need to support all three of these cases, so also upgrade to the latest possible params while we're here
192 if (!setKey(hexKey)) {
193 return false;
194 }
195
196 auto highestSupportedVersion = highestSupportedParams();
197 if (setCipherParameters(highestSupportedVersion)) {
198 if (testUsable()) {
199 qInfo() << "Opened database with SQLCipher" << toString(highestSupportedVersion) << "parameters";
200 return true;
201 } else {
202 return updateSavedCipherParameters(hexKey, highestSupportedVersion);
203 }
204 } else {
205 qCritical() << "Failed to set latest supported SQLCipher params!";
206 return false;
207 }
208 }
209
testUsable()210 bool RawDatabase::testUsable()
211 {
212 // this will unfortunately log a warning if it fails, even though we may expect failure
213 return execNow("SELECT count(*) FROM sqlite_master;");
214 }
215
216 /**
217 * @brief Changes stored db encryption from SQLCipher 3.x defaults to 4.x defaults
218 */
updateSavedCipherParameters(const QString & hexKey,SqlCipherParams newParams)219 bool RawDatabase::updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams)
220 {
221 auto currentParams = readSavedCipherParams(hexKey, newParams);
222 setKey(hexKey); // setKey again because a SELECT has already been run, causing crypto settings to take effect
223 if (!setCipherParameters(currentParams)) {
224 return false;
225 }
226
227 const auto user_version = getUserVersion();
228 if (user_version < 0) {
229 return false;
230 }
231 if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS newParams KEY \"x'" + hexKey + "'\";")) {
232 return false;
233 }
234 if (!setCipherParameters(newParams, "newParams")) {
235 return false;
236 }
237 if (!execNow("SELECT sqlcipher_export('newParams');")) {
238 return false;
239 }
240 if (!execNow(QString("PRAGMA newParams.user_version = %1;").arg(user_version))) {
241 return false;
242 }
243 if (!execNow("DETACH DATABASE newParams;")) {
244 return false;
245 }
246 if (!commitDbSwap(hexKey)) {
247 return false;
248 }
249 qInfo() << "Upgraded database from SQLCipher" << toString(currentParams) << "params to" <<
250 toString(newParams) << "params complete";
251 return true;
252 }
253
setCipherParameters(SqlCipherParams params,const QString & database)254 bool RawDatabase::setCipherParameters(SqlCipherParams params, const QString& database)
255 {
256 QString prefix;
257 if (!database.isNull()) {
258 prefix = database + ".";
259 }
260 // from https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/
261 const QString default3_xParams{"PRAGMA database.cipher_page_size = 1024;"
262 "PRAGMA database.kdf_iter = 64000;"
263 "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA1;"
264 "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"};
265 // cipher_hmac_algorithm and cipher_kdf_algorithm weren't supported in sqlcipher 3.x, so our upgrade to 4 only
266 // applied some of the new params if sqlcipher 3.x was used at the time
267 const QString halfUpgradedTo4Params{"PRAGMA database.cipher_page_size = 4096;"
268 "PRAGMA database.kdf_iter = 256000;"
269 "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA1;"
270 "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"};
271 const QString default4_xParams{"PRAGMA database.cipher_page_size = 4096;"
272 "PRAGMA database.kdf_iter = 256000;"
273 "PRAGMA database.cipher_hmac_algorithm = HMAC_SHA512;"
274 "PRAGMA database.cipher_kdf_algorithm = PBKDF2_HMAC_SHA512;"
275 "PRAGMA database.cipher_memory_security = ON;"}; // got disabled by default in 4.5.0, so manually enable it
276
277 QString defaultParams;
278 switch(params) {
279 case SqlCipherParams::p3_0: {
280 defaultParams = default3_xParams;
281 break;
282 }
283 case SqlCipherParams::halfUpgradedTo4: {
284 defaultParams = halfUpgradedTo4Params;
285 break;
286 }
287 case SqlCipherParams::p4_0: {
288 defaultParams = default4_xParams;
289 break;
290 }
291 }
292
293 qDebug() << "Setting SQLCipher" << toString(params) << "parameters";
294 return execNow(defaultParams.replace("database.", prefix));
295 }
296
highestSupportedParams()297 RawDatabase::SqlCipherParams RawDatabase::highestSupportedParams()
298 {
299 // Note: This is just calling into the sqlcipher library, not touching the database.
300 QString cipherVersion;
301 if (!execNow(RawDatabase::Query("PRAGMA cipher_version", [&](const QVector<QVariant>& row) {
302 cipherVersion = row[0].toString();
303 }))) {
304 qCritical() << "Failed to read cipher_version";
305 return SqlCipherParams::p3_0;
306 }
307
308 auto majorVersion = cipherVersion.split('.')[0].toInt();
309
310 SqlCipherParams highestSupportedParams;
311 switch (majorVersion) {
312 case 3:
313 highestSupportedParams = SqlCipherParams::halfUpgradedTo4;
314 break;
315 case 4:
316 highestSupportedParams = SqlCipherParams::p4_0;
317 break;
318 default:
319 qCritical() << "Unsupported SQLCipher version detected!";
320 return SqlCipherParams::p3_0;
321 }
322 qDebug() << "Highest supported SQLCipher params on this system are" << toString(highestSupportedParams);
323 return highestSupportedParams;
324 }
325
readSavedCipherParams(const QString & hexKey,SqlCipherParams newParams)326 RawDatabase::SqlCipherParams RawDatabase::readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams)
327 {
328 for (int i = static_cast<int>(SqlCipherParams::p3_0); i < static_cast<int>(newParams); ++i)
329 {
330 if (!setKey(hexKey)) {
331 break;
332 }
333
334 if (!setCipherParameters(static_cast<SqlCipherParams>(i))) {
335 break;
336 }
337
338 if (testUsable()) {
339 return static_cast<SqlCipherParams>(i);
340 }
341 }
342 qCritical() << "Failed to check saved SQLCipher params";
343 return SqlCipherParams::p3_0;
344 }
345
setKey(const QString & hexKey)346 bool RawDatabase::setKey(const QString& hexKey)
347 {
348 // setKey again to clear old bad cipher settings
349 if (!execNow("PRAGMA key = \"x'" + hexKey + "'\"")) {
350 qWarning() << "Failed to set encryption key";
351 return false;
352 }
353 return true;
354 }
355
getUserVersion()356 int RawDatabase::getUserVersion()
357 {
358 int64_t user_version;
359 if (!execNow(RawDatabase::Query("PRAGMA user_version", [&](const QVector<QVariant>& row) {
360 user_version = row[0].toLongLong();
361 }))) {
362 qCritical() << "Failed to read user_version during cipher upgrade";
363 return -1;
364 }
365 return user_version;
366 }
367
368 /**
369 * @brief Close the database and free its associated resources.
370 */
close()371 void RawDatabase::close()
372 {
373 if (QThread::currentThread() != workerThread.get())
374 return (void)QMetaObject::invokeMethod(this, "close", Qt::BlockingQueuedConnection);
375
376 // We assume we're in the ctor or dtor, so we just need to finish processing our transactions
377 process();
378
379 if (sqlite3_close(sqlite) == SQLITE_OK)
380 sqlite = nullptr;
381 else
382 qWarning() << "Error closing database:" << sqlite3_errmsg(sqlite);
383 }
384
385 /**
386 * @brief Checks, that the database is open.
387 * @return True if the database was opened successfully.
388 */
isOpen()389 bool RawDatabase::isOpen()
390 {
391 // We don't need thread safety since only the ctor/dtor can write this pointer
392 return sqlite != nullptr;
393 }
394
395 /**
396 * @brief Executes a SQL transaction synchronously.
397 * @param statement Statement to execute.
398 * @return Whether the transaction was successful.
399 */
execNow(const QString & statement)400 bool RawDatabase::execNow(const QString& statement)
401 {
402 return execNow(Query{statement});
403 }
404
405 /**
406 * @brief Executes a SQL transaction synchronously.
407 * @param statement Statement to execute.
408 * @return Whether the transaction was successful.
409 */
execNow(const RawDatabase::Query & statement)410 bool RawDatabase::execNow(const RawDatabase::Query& statement)
411 {
412 return execNow(QVector<Query>{statement});
413 }
414
415 /**
416 * @brief Executes a SQL transaction synchronously.
417 * @param statements List of statements to execute.
418 * @return Whether the transaction was successful.
419 */
execNow(const QVector<RawDatabase::Query> & statements)420 bool RawDatabase::execNow(const QVector<RawDatabase::Query>& statements)
421 {
422 if (!sqlite) {
423 qWarning() << "Trying to exec, but the database is not open";
424 return false;
425 }
426
427 std::atomic_bool done{false};
428 std::atomic_bool success{false};
429
430 Transaction trans;
431 trans.queries = statements;
432 trans.done = &done;
433 trans.success = &success;
434 {
435 QMutexLocker locker{&transactionsMutex};
436 pendingTransactions.enqueue(trans);
437 }
438
439 // We can't use blocking queued here, otherwise we might process future transactions
440 // before returning, but we only want to wait until this transaction is done.
441 QMetaObject::invokeMethod(this, "process");
442 while (!done.load(std::memory_order_acquire))
443 QThread::msleep(10);
444
445 return success.load(std::memory_order_acquire);
446 }
447
448 /**
449 * @brief Executes a SQL transaction asynchronously.
450 * @param statement Statement to execute.
451 */
execLater(const QString & statement)452 void RawDatabase::execLater(const QString& statement)
453 {
454 execLater(Query{statement});
455 }
456
execLater(const RawDatabase::Query & statement)457 void RawDatabase::execLater(const RawDatabase::Query& statement)
458 {
459 execLater(QVector<Query>{statement});
460 }
461
execLater(const QVector<RawDatabase::Query> & statements)462 void RawDatabase::execLater(const QVector<RawDatabase::Query>& statements)
463 {
464 if (!sqlite) {
465 qWarning() << "Trying to exec, but the database is not open";
466 return;
467 }
468
469 Transaction trans;
470 trans.queries = statements;
471 {
472 QMutexLocker locker{&transactionsMutex};
473 pendingTransactions.enqueue(trans);
474 }
475
476 QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection);
477 }
478
479 /**
480 * @brief Waits until all the pending transactions are executed.
481 */
sync()482 void RawDatabase::sync()
483 {
484 QMetaObject::invokeMethod(this, "process", Qt::BlockingQueuedConnection);
485 }
486
487 /**
488 * @brief Changes the database password, encrypting or decrypting if necessary.
489 * @param password If password is empty, the database will be decrypted.
490 * @return True if success, false otherwise.
491 * @note Will process all transactions before changing the password.
492 */
setPassword(const QString & password)493 bool RawDatabase::setPassword(const QString& password)
494 {
495 if (!sqlite) {
496 qWarning() << "Trying to change the password, but the database is not open";
497 return false;
498 }
499
500 if (QThread::currentThread() != workerThread.get()) {
501 bool ret;
502 QMetaObject::invokeMethod(this, "setPassword", Qt::BlockingQueuedConnection,
503 Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, password));
504 return ret;
505 }
506
507 // If we need to decrypt or encrypt, we'll need to sync and close,
508 // so we always process the pending queue before rekeying for consistency
509 process();
510
511 if (QFile::exists(path + ".tmp")) {
512 qWarning() << "Found old temporary export file while rekeying, deleting it";
513 QFile::remove(path + ".tmp");
514 }
515
516 if (!password.isEmpty()) {
517 QString newHexKey = deriveKey(password, currentSalt);
518 if (!currentHexKey.isEmpty()) {
519 if (!execNow("PRAGMA rekey = \"x'" + newHexKey + "'\"")) {
520 qWarning() << "Failed to change encryption key";
521 close();
522 return false;
523 }
524 } else {
525 if (!encryptDatabase(newHexKey)) {
526 close();
527 return false;
528 }
529 currentHexKey = newHexKey;
530 }
531 } else {
532 if (currentHexKey.isEmpty())
533 return true;
534
535 if (!decryptDatabase()) {
536 close();
537 return false;
538 }
539 }
540 return true;
541 }
542
encryptDatabase(const QString & newHexKey)543 bool RawDatabase::encryptDatabase(const QString& newHexKey)
544 {
545 const auto user_version = getUserVersion();
546 if (user_version < 0) {
547 return false;
548 }
549 if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS encrypted KEY \"x'" + newHexKey
550 + "'\";")) {
551 qWarning() << "Failed to export encrypted database";
552 return false;
553 }
554 if (!setCipherParameters(SqlCipherParams::p4_0, "encrypted")) {
555 return false;
556 }
557 if (!execNow("SELECT sqlcipher_export('encrypted');")) {
558 return false;
559 }
560 if (!execNow(QString("PRAGMA encrypted.user_version = %1;").arg(user_version))) {
561 return false;
562 }
563 if (!execNow("DETACH DATABASE encrypted;")) {
564 return false;
565 }
566 return commitDbSwap(newHexKey);
567 }
568
decryptDatabase()569 bool RawDatabase::decryptDatabase()
570 {
571 const auto user_version = getUserVersion();
572 if (user_version < 0) {
573 return false;
574 }
575 if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS plaintext KEY '';"
576 "SELECT sqlcipher_export('plaintext');")) {
577 qWarning() << "Failed to export decrypted database";
578 return false;
579 }
580 if (!execNow(QString("PRAGMA plaintext.user_version = %1;").arg(user_version))) {
581 return false;
582 }
583 if (!execNow("DETACH DATABASE plaintext;")) {
584 return false;
585 }
586 return commitDbSwap({});
587 }
588
commitDbSwap(const QString & hexKey)589 bool RawDatabase::commitDbSwap(const QString& hexKey)
590 {
591 // This is racy as hell, but nobody will race with us since we hold the profile lock
592 // If we crash or die here, the rename should be atomic, so we can recover no matter
593 // what
594 close();
595 QFile::remove(path);
596 QFile::rename(path + ".tmp", path);
597 currentHexKey = hexKey;
598 if (!open(path, currentHexKey)) {
599 qCritical() << "Failed to swap db";
600 return false;
601 }
602 return true;
603 }
604
605 /**
606 * @brief Moves the database file on disk to match the new path.
607 * @param newPath Path to move database file.
608 * @return True if success, false otherwise.
609 *
610 * @note Will process all transactions before renaming
611 */
rename(const QString & newPath)612 bool RawDatabase::rename(const QString& newPath)
613 {
614 if (!sqlite) {
615 qWarning() << "Trying to change the password, but the database is not open";
616 return false;
617 }
618
619 if (QThread::currentThread() != workerThread.get()) {
620 bool ret;
621 QMetaObject::invokeMethod(this, "rename", Qt::BlockingQueuedConnection,
622 Q_RETURN_ARG(bool, ret), Q_ARG(const QString&, newPath));
623 return ret;
624 }
625
626 process();
627
628 if (path == newPath)
629 return true;
630
631 if (QFile::exists(newPath))
632 return false;
633
634 close();
635 if (!QFile::rename(path, newPath))
636 return false;
637 path = newPath;
638 return open(path, currentHexKey);
639 }
640
641 /**
642 * @brief Deletes the on disk database file after closing it.
643 * @note Will process all transactions before deletings.
644 * @return True if success, false otherwise.
645 */
remove()646 bool RawDatabase::remove()
647 {
648 if (!sqlite) {
649 qWarning() << "Trying to remove the database, but it is not open";
650 return false;
651 }
652
653 if (QThread::currentThread() != workerThread.get()) {
654 bool ret;
655 QMetaObject::invokeMethod(this, "remove", Qt::BlockingQueuedConnection,
656 Q_RETURN_ARG(bool, ret));
657 return ret;
658 }
659
660 qDebug() << "Removing database " << path;
661 close();
662 return QFile::remove(path);
663 }
664
665 /**
666 * @brief Functor used to free tox_pass_key memory.
667 *
668 * This functor can be used as Deleter for smart pointers.
669 * @note Doesn't take care of overwriting the key.
670 */
671 struct PassKeyDeleter
672 {
operator ()PassKeyDeleter673 void operator()(Tox_Pass_Key* pass_key)
674 {
675 tox_pass_key_free(pass_key);
676 }
677 };
678
679 /**
680 * @brief Derives a 256bit key from the password and returns it hex-encoded
681 * @param password Password to decrypt database
682 * @return String representation of key
683 * @deprecated deprecated on 2016-11-06, kept for compatibility, replaced by the salted version
684 */
deriveKey(const QString & password)685 QString RawDatabase::deriveKey(const QString& password)
686 {
687 if (password.isEmpty())
688 return {};
689
690 const QByteArray passData = password.toUtf8();
691
692 static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys");
693
694 static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH + 1] =
695 "L'ignorance est le pire des maux";
696 const std::unique_ptr<Tox_Pass_Key, PassKeyDeleter> key(tox_pass_key_derive_with_salt(
697 reinterpret_cast<const uint8_t*>(passData.data()),
698 static_cast<std::size_t>(passData.size()), expandConstant, nullptr));
699 return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();
700 }
701
702 /**
703 * @brief Derives a 256bit key from the password and returns it hex-encoded
704 * @param password Password to decrypt database
705 * @param salt Salt to improve password strength, must be TOX_PASS_SALT_LENGTH bytes
706 * @return String representation of key
707 */
deriveKey(const QString & password,const QByteArray & salt)708 QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt)
709 {
710 if (password.isEmpty()) {
711 return {};
712 }
713
714 if (salt.length() != TOX_PASS_SALT_LENGTH) {
715 qWarning() << "Salt length doesn't match toxencryptsave expections";
716 return {};
717 }
718
719 const QByteArray passData = password.toUtf8();
720
721 static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys");
722
723 const std::unique_ptr<Tox_Pass_Key, PassKeyDeleter> key(tox_pass_key_derive_with_salt(
724 reinterpret_cast<const uint8_t*>(passData.data()),
725 static_cast<std::size_t>(passData.size()),
726 reinterpret_cast<const uint8_t*>(salt.constData()), nullptr));
727 return QByteArray(reinterpret_cast<char*>(key.get()) + 32, 32).toHex();
728 }
729
730 /**
731 * @brief Implements the actual processing of pending transactions.
732 * Unqueues, compiles, binds and executes queries, then notifies of results
733 *
734 * @warning MUST only be called from the worker thread
735 */
process()736 void RawDatabase::process()
737 {
738 assert(QThread::currentThread() == workerThread.get());
739
740 if (!sqlite)
741 return;
742
743 forever
744 {
745 // Fetch the next transaction
746 Transaction trans;
747 {
748 QMutexLocker locker{&transactionsMutex};
749 if (pendingTransactions.isEmpty())
750 return;
751 trans = pendingTransactions.dequeue();
752 }
753
754 // In case we exit early, prepare to signal errors
755 if (trans.success != nullptr)
756 trans.success->store(false, std::memory_order_release);
757
758 // Add transaction commands if necessary
759 if (trans.queries.size() > 1) {
760 trans.queries.prepend({"BEGIN;"});
761 trans.queries.append({"COMMIT;"});
762 }
763
764 // Compile queries
765 for (Query& query : trans.queries) {
766 assert(query.statements.isEmpty());
767 // sqlite3_prepare_v2 only compiles one statement at a time in the query,
768 // we need to loop over them all
769 int curParam = 0;
770 const char* compileTail = query.query.data();
771 do {
772 // Compile the next statement
773 sqlite3_stmt* stmt;
774 int r;
775 if ((r = sqlite3_prepare_v2(sqlite, compileTail,
776 query.query.size()
777 - static_cast<int>(compileTail - query.query.data()),
778 &stmt, &compileTail))
779 != SQLITE_OK) {
780 qWarning() << "Failed to prepare statement" << anonymizeQuery(query.query)
781 << "and returned" << r;
782 qWarning("The full error is %d: %s", sqlite3_errcode(sqlite), sqlite3_errmsg(sqlite));
783 goto cleanupStatements;
784 }
785 query.statements += stmt;
786
787 // Now we can bind our params to this statement
788 int nParams = sqlite3_bind_parameter_count(stmt);
789 if (query.blobs.size() < curParam + nParams) {
790 qWarning() << "Not enough parameters to bind to query "
791 << anonymizeQuery(query.query);
792 goto cleanupStatements;
793 }
794 for (int i = 0; i < nParams; ++i) {
795 const QByteArray& blob = query.blobs[curParam + i];
796 if (sqlite3_bind_blob(stmt, i + 1, blob.data(), blob.size(), SQLITE_STATIC)
797 != SQLITE_OK) {
798 qWarning() << "Failed to bind param" << curParam + i << "to query"
799 << anonymizeQuery(query.query);
800 goto cleanupStatements;
801 }
802 }
803 curParam += nParams;
804 } while (compileTail != query.query.data() + query.query.size());
805
806
807 // Execute each statement of each query of our transaction
808 for (sqlite3_stmt* stmt : query.statements) {
809 int column_count = sqlite3_column_count(stmt);
810 int result;
811 do {
812 result = sqlite3_step(stmt);
813
814 // Execute our row callback
815 if (result == SQLITE_ROW && query.rowCallback) {
816 QVector<QVariant> row;
817 for (int i = 0; i < column_count; ++i)
818 row += extractData(stmt, i);
819
820 query.rowCallback(row);
821 }
822 } while (result == SQLITE_ROW);
823
824 if (result == SQLITE_DONE)
825 continue;
826
827 QString anonQuery = anonymizeQuery(query.query);
828 switch (result) {
829 case SQLITE_ERROR:
830 qWarning() << "Error executing query" << anonQuery;
831 goto cleanupStatements;
832 case SQLITE_MISUSE:
833 qWarning() << "Misuse executing query" << anonQuery;
834 goto cleanupStatements;
835 case SQLITE_CONSTRAINT:
836 qWarning() << "Constraint error executing query" << anonQuery;
837 goto cleanupStatements;
838 default:
839 qWarning() << "Unknown error" << result << "executing query" << anonQuery;
840 goto cleanupStatements;
841 }
842 }
843
844 if (query.insertCallback)
845 query.insertCallback(RowId{sqlite3_last_insert_rowid(sqlite)});
846 }
847
848 if (trans.success != nullptr)
849 trans.success->store(true, std::memory_order_release);
850
851 // Free our statements
852 cleanupStatements:
853 for (Query& query : trans.queries) {
854 for (sqlite3_stmt* stmt : query.statements)
855 sqlite3_finalize(stmt);
856 query.statements.clear();
857 }
858
859 // Signal transaction results
860 if (trans.done != nullptr)
861 trans.done->store(true, std::memory_order_release);
862 }
863 }
864
865 /**
866 * @brief Hides public keys and timestamps in query.
867 * @param query Source query, which should be anonymized.
868 * @return Query without timestamps and public keys.
869 */
anonymizeQuery(const QByteArray & query)870 QString RawDatabase::anonymizeQuery(const QByteArray& query)
871 {
872 QString queryString(query);
873 queryString.replace(QRegularExpression("chat.public_key='[A-F0-9]{64}'"),
874 "char.public_key='<HERE IS PUBLIC KEY>'");
875 queryString.replace(QRegularExpression("timestamp BETWEEN \\d{5,} AND \\d{5,}"),
876 "timestamp BETWEEN <START HERE> AND <END HERE>");
877
878 return queryString;
879 }
880
881 /**
882 * @brief Extracts a variant from one column of a result row depending on the column type.
883 * @param stmt Statement to execute.
884 * @param col Number of column to extract.
885 * @return Extracted data.
886 */
extractData(sqlite3_stmt * stmt,int col)887 QVariant RawDatabase::extractData(sqlite3_stmt* stmt, int col)
888 {
889 int type = sqlite3_column_type(stmt, col);
890 if (type == SQLITE_INTEGER) {
891 return sqlite3_column_int64(stmt, col);
892 } else if (type == SQLITE_TEXT) {
893 const char* str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, col));
894 int len = sqlite3_column_bytes(stmt, col);
895 return QString::fromUtf8(str, len);
896 } else if (type == SQLITE_NULL) {
897 return QVariant{};
898 } else {
899 const char* data = reinterpret_cast<const char*>(sqlite3_column_blob(stmt, col));
900 int len = sqlite3_column_bytes(stmt, col);
901 return QByteArray::fromRawData(data, len);
902 }
903 }
904
905 /**
906 * @brief Use for create function in db for search data use regular experessions without case sensitive
907 * @param ctx ctx the context in which an SQL function executes
908 * @param argc number of arguments
909 * @param argv arguments
910 */
regexpInsensitive(sqlite3_context * ctx,int argc,sqlite3_value ** argv)911 void RawDatabase::regexpInsensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv)
912 {
913 regexp(ctx, argc, argv, QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
914 }
915
916 /**
917 * @brief Use for create function in db for search data use regular experessions without case sensitive
918 * @param ctx the context in which an SQL function executes
919 * @param argc number of arguments
920 * @param argv arguments
921 */
regexpSensitive(sqlite3_context * ctx,int argc,sqlite3_value ** argv)922 void RawDatabase::regexpSensitive(sqlite3_context* ctx, int argc, sqlite3_value** argv)
923 {
924 regexp(ctx, argc, argv, QRegularExpression::UseUnicodePropertiesOption);
925 }
926
regexp(sqlite3_context * ctx,int argc,sqlite3_value ** argv,const QRegularExpression::PatternOptions cs)927 void RawDatabase::regexp(sqlite3_context* ctx, int argc, sqlite3_value** argv, const QRegularExpression::PatternOptions cs)
928 {
929 QRegularExpression regex;
930 const QString str1(reinterpret_cast<const char*>(sqlite3_value_text(argv[0])));
931 const QString str2(reinterpret_cast<const char*>(sqlite3_value_text(argv[1])));
932
933 regex.setPattern(str1);
934 regex.setPatternOptions(cs);
935
936 const bool b = str2.contains(regex);
937
938 if (b) {
939 sqlite3_result_int(ctx, 1);
940 } else {
941 sqlite3_result_int(ctx, 0);
942 }
943 }
944