23 #include <tox/toxencryptsave.h>
25 #include <QCoreApplication>
28 #include <QMetaObject>
29 #include <QMutexLocker>
88 : workerThread{
new QThread}
91 , currentHexKey{deriveKey(password, salt)}
93 workerThread->setObjectName(
"qTox Database");
94 moveToThread(workerThread.get());
95 workerThread->start();
98 if (open(path, currentHexKey)) {
107 if (!QFile::copy(path, path +
".bak")) {
108 qDebug() <<
"Couldn't create the backup of the database, won't upgrade";
113 currentHexKey = deriveKey(password);
114 if (open(path, currentHexKey)) {
118 if (setPassword(password)) {
119 qDebug() <<
"Successfully upgraded to dynamic salt";
121 qWarning() <<
"Failed to set password with new salt";
125 qDebug() <<
"Failed to open database with old salt";
147 QMetaObject::invokeMethod(
this,
"open", Qt::BlockingQueuedConnection, Q_RETURN_ARG(
bool, ret),
148 Q_ARG(
const QString&,
path), Q_ARG(
const QString&, hexKey));
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?";
158 if (sqlite3_open_v2(
path.toUtf8().data(), &
sqlite,
159 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX,
nullptr)
161 qWarning() <<
"Failed to open database" <<
path <<
"with error:" << sqlite3_errmsg(
sqlite);
166 qWarning() <<
"Failed to create function regexp";
172 qWarning() <<
"Failed to create function regexpsensitive";
177 if (!hexKey.isEmpty()) {
199 qInfo() <<
"Opened database with SQLCipher" <<
toString(highestSupportedVersion) <<
"parameters";
205 qCritical() <<
"Failed to set latest supported SQLCipher params!";
213 return execNow(
"SELECT count(*) FROM sqlite_master;");
228 if (user_version < 0) {
231 if (!
execNow(
"ATTACH DATABASE '" +
path +
".tmp' AS newParams KEY \"x'" + hexKey +
"'\";")) {
237 if (!
execNow(
"SELECT sqlcipher_export('newParams');")) {
240 if (!
execNow(QString(
"PRAGMA newParams.user_version = %1;").arg(user_version))) {
243 if (!
execNow(
"DETACH DATABASE newParams;")) {
249 qInfo() <<
"Upgraded database from SQLCipher" <<
toString(currentParams) <<
"params to" <<
250 toString(newParams) <<
"params complete";
257 if (!database.isNull()) {
258 prefix = database +
".";
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;"};
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;"};
277 QString defaultParams;
280 defaultParams = default3_xParams;
284 defaultParams = halfUpgradedTo4Params;
288 defaultParams = default4_xParams;
293 qDebug() <<
"Setting SQLCipher" <<
toString(params) <<
"parameters";
294 return execNow(defaultParams.replace(
"database.", prefix));
300 QString cipherVersion;
302 cipherVersion = row[0].toString();
304 qCritical() <<
"Failed to read cipher_version";
308 auto majorVersion = cipherVersion.split(
'.')[0].toInt();
311 switch (majorVersion) {
319 qCritical() <<
"Unsupported SQLCipher version detected!";
342 qCritical() <<
"Failed to check saved SQLCipher params";
349 if (!
execNow(
"PRAGMA key = \"x'" + hexKey +
"'\"")) {
350 qWarning() <<
"Failed to set encryption key";
358 int64_t user_version;
360 user_version = row[0].toLongLong();
362 qCritical() <<
"Failed to read user_version during cipher upgrade";
374 return (
void)QMetaObject::invokeMethod(
this,
"close", Qt::BlockingQueuedConnection);
379 if (sqlite3_close(
sqlite) == SQLITE_OK)
382 qWarning() <<
"Error closing database:" << sqlite3_errmsg(
sqlite);
412 return execNow(QVector<Query>{statement});
423 qWarning() <<
"Trying to exec, but the database is not open";
427 std::atomic_bool done{
false};
428 std::atomic_bool success{
false};
431 trans.queries = statements;
433 trans.success = &success;
441 QMetaObject::invokeMethod(
this,
"process");
442 while (!done.load(std::memory_order_acquire))
445 return success.load(std::memory_order_acquire);
465 qWarning() <<
"Trying to exec, but the database is not open";
470 trans.queries = statements;
476 QMetaObject::invokeMethod(
this,
"process", Qt::QueuedConnection);
484 QMetaObject::invokeMethod(
this,
"process", Qt::BlockingQueuedConnection);
496 qWarning() <<
"Trying to change the password, but the database is not open";
502 QMetaObject::invokeMethod(
this,
"setPassword", Qt::BlockingQueuedConnection,
503 Q_RETURN_ARG(
bool, ret), Q_ARG(
const QString&, password));
511 if (QFile::exists(
path +
".tmp")) {
512 qWarning() <<
"Found old temporary export file while rekeying, deleting it";
513 QFile::remove(
path +
".tmp");
516 if (!password.isEmpty()) {
519 if (!
execNow(
"PRAGMA rekey = \"x'" + newHexKey +
"'\"")) {
520 qWarning() <<
"Failed to change encryption key";
546 if (user_version < 0) {
549 if (!
execNow(
"ATTACH DATABASE '" +
path +
".tmp' AS encrypted KEY \"x'" + newHexKey
551 qWarning() <<
"Failed to export encrypted database";
557 if (!
execNow(
"SELECT sqlcipher_export('encrypted');")) {
560 if (!
execNow(QString(
"PRAGMA encrypted.user_version = %1;").arg(user_version))) {
563 if (!
execNow(
"DETACH DATABASE encrypted;")) {
572 if (user_version < 0) {
575 if (!
execNow(
"ATTACH DATABASE '" +
path +
".tmp' AS plaintext KEY '';"
576 "SELECT sqlcipher_export('plaintext');")) {
577 qWarning() <<
"Failed to export decrypted database";
580 if (!
execNow(QString(
"PRAGMA plaintext.user_version = %1;").arg(user_version))) {
583 if (!
execNow(
"DETACH DATABASE plaintext;")) {
599 qCritical() <<
"Failed to swap db";
615 qWarning() <<
"Trying to change the password, but the database is not open";
621 QMetaObject::invokeMethod(
this,
"rename", Qt::BlockingQueuedConnection,
622 Q_RETURN_ARG(
bool, ret), Q_ARG(
const QString&, newPath));
631 if (QFile::exists(newPath))
635 if (!QFile::rename(
path, newPath))
649 qWarning() <<
"Trying to remove the database, but it is not open";
655 QMetaObject::invokeMethod(
this,
"remove", Qt::BlockingQueuedConnection,
656 Q_RETURN_ARG(
bool, ret));
660 qDebug() <<
"Removing database " <<
path;
662 return QFile::remove(
path);
675 tox_pass_key_free(pass_key);
687 if (password.isEmpty())
690 const QByteArray passData = password.toUtf8();
692 static_assert(TOX_PASS_KEY_LENGTH >= 32,
"toxcore must provide 256bit or longer keys");
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();
710 if (password.isEmpty()) {
714 if (salt.length() != TOX_PASS_SALT_LENGTH) {
715 qWarning() <<
"Salt length doesn't match toxencryptsave expections";
719 const QByteArray passData = password.toUtf8();
721 static_assert(TOX_PASS_KEY_LENGTH >= 32,
"toxcore must provide 256bit or longer keys");
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();
756 trans.
success->store(
false, std::memory_order_release);
759 if (trans.
queries.size() > 1) {
760 trans.
queries.prepend({
"BEGIN;"});
761 trans.
queries.append({
"COMMIT;"});
770 const char* compileTail = query.
query.data();
775 if ((r = sqlite3_prepare_v2(
sqlite, compileTail,
777 -
static_cast<int>(compileTail - query.
query.data()),
778 &stmt, &compileTail))
781 <<
"and returned" << r;
782 qWarning(
"The full error is %d: %s", sqlite3_errcode(
sqlite), sqlite3_errmsg(
sqlite));
783 goto cleanupStatements;
788 int nParams = sqlite3_bind_parameter_count(stmt);
789 if (query.
blobs.size() < curParam + nParams) {
790 qWarning() <<
"Not enough parameters to bind to query "
792 goto cleanupStatements;
794 for (
int i = 0; i < nParams; ++i) {
795 const QByteArray& blob = query.
blobs[curParam + i];
796 #pragma GCC diagnostic push
797 #pragma GCC diagnostic ignored "-Wold-style-cast"
798 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
801 auto sqliteDataType = SQLITE_STATIC;
802 #pragma GCC diagnostic pop
803 if (sqlite3_bind_blob(stmt, i + 1, blob.data(), blob.size(), sqliteDataType)
805 qWarning() <<
"Failed to bind param" << curParam + i <<
"to query"
807 goto cleanupStatements;
811 }
while (compileTail != query.
query.data() + query.
query.size());
816 int column_count = sqlite3_column_count(stmt);
819 result = sqlite3_step(stmt);
823 QVector<QVariant> row;
824 for (
int i = 0; i < column_count; ++i)
829 }
while (result == SQLITE_ROW);
831 if (result == SQLITE_DONE)
837 qWarning() <<
"Error executing query" << anonQuery;
838 goto cleanupStatements;
840 qWarning() <<
"Misuse executing query" << anonQuery;
841 goto cleanupStatements;
842 case SQLITE_CONSTRAINT:
843 qWarning() <<
"Constraint error executing query" << anonQuery;
844 goto cleanupStatements;
846 qWarning() <<
"Unknown error" << result <<
"executing query" << anonQuery;
847 goto cleanupStatements;
856 trans.
success->store(
true, std::memory_order_release);
862 sqlite3_finalize(stmt);
867 if (trans.
done !=
nullptr)
868 trans.
done->store(
true, std::memory_order_release);
879 QString queryString(query);
880 queryString.replace(QRegularExpression(
"chat.public_key='[A-F0-9]{64}'"),
881 "char.public_key='<HERE IS PUBLIC KEY>'");
882 queryString.replace(QRegularExpression(
"timestamp BETWEEN \\d{5,} AND \\d{5,}"),
883 "timestamp BETWEEN <START HERE> AND <END HERE>");
896 int type = sqlite3_column_type(stmt, col);
897 if (type == SQLITE_INTEGER) {
898 return sqlite3_column_int64(stmt, col);
899 }
else if (type == SQLITE_TEXT) {
900 const char* str =
reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
901 int len = sqlite3_column_bytes(stmt, col);
902 return QString::fromUtf8(str, len);
903 }
else if (type == SQLITE_NULL) {
906 const char* data =
reinterpret_cast<const char*
>(sqlite3_column_blob(stmt, col));
907 int len = sqlite3_column_bytes(stmt, col);
908 return QByteArray::fromRawData(data, len);
920 regexp(ctx, argc, argv, QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
931 regexp(ctx, argc, argv, QRegularExpression::UseUnicodePropertiesOption);
934 void RawDatabase::regexp(sqlite3_context* ctx,
int argc, sqlite3_value** argv,
const QRegularExpression::PatternOptions cs)
936 QRegularExpression regex;
937 const QString str1(
reinterpret_cast<const char*
>(sqlite3_value_text(argv[0])));
938 const QString str2(
reinterpret_cast<const char*
>(sqlite3_value_text(argv[1])));
940 regex.setPattern(str1);
941 regex.setPatternOptions(cs);
943 const bool b = str2.contains(regex);
946 sqlite3_result_int(ctx, 1);
948 sqlite3_result_int(ctx, 0);