diff --git a/src/persistence/db/rawdatabase.cpp b/src/persistence/db/rawdatabase.cpp index 9c85ed294..a7399e629 100644 --- a/src/persistence/db/rawdatabase.cpp +++ b/src/persistence/db/rawdatabase.cpp @@ -91,15 +91,55 @@ * @param password If empty, the database will be opened unencrypted. * Otherwise we will use toxencryptsave to derive a key and encrypt the database. */ -RawDatabase::RawDatabase(const QString &path, const QString& password) - : workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)} +RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt) + : workerThread{new QThread} + , path{path} + , currentSalt{salt} // we need the salt later if a new password should be set + , currentHexKey{deriveKey(password, salt)} { workerThread->setObjectName("qTox Database"); moveToThread(workerThread.get()); workerThread->start(); - if (!open(path, currentHexKey)) + // first try with the new salt + if (open(path, currentHexKey)) + { return; + } + + // avoid opening the same db twice + close(); + + // create a backup before trying to upgrade to new salt + bool upgrade = true; + if(!QFile::copy(path, path + ".bak")) + { + qDebug() << "Couldn't create the backup of the database, won't upgrade"; + upgrade = false; + } + + // fall back to the old salt + currentHexKey = deriveKey(password); + if(open(path, currentHexKey)) + { + // upgrade only if backup successful + if(upgrade) + { + // still using old salt, upgrade + if(setPassword(password)) + { + qDebug() << "Successfully upgraded to dynamic salt"; + } + else + { + qWarning() << "Failed to set password with new salt"; + } + } + } + else + { + qDebug() << "Failed to open database with old salt"; + } } RawDatabase::~RawDatabase() @@ -313,7 +353,7 @@ bool RawDatabase::setPassword(const QString& password) if (!password.isEmpty()) { - QString newHexKey = deriveKey(password); + QString newHexKey = deriveKey(password, currentSalt); if (!currentHexKey.isEmpty()) { if (!execNow("PRAGMA rekey = \"x'"+newHexKey+"'\"")) @@ -445,6 +485,7 @@ bool RawDatabase::remove() * @brief Derives a 256bit key from the password and returns it hex-encoded * @param password Password to decrypt database * @return String representation of key + * @deprecated deprecated on 2016-11-06, kept for compatibility, replaced by the salted version */ QString RawDatabase::deriveKey(const QString &password) { @@ -457,8 +498,39 @@ QString RawDatabase::deriveKey(const QString &password) static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH+1] = "L'ignorance est le pire des maux"; TOX_PASS_KEY key; - tox_derive_key_with_salt((uint8_t*)passData.data(), passData.size(), expandConstant, &key, nullptr); - return QByteArray((char*)key.key, 32).toHex(); + tox_derive_key_with_salt(reinterpret_cast(passData.data()), + static_cast(passData.size()), expandConstant, &key, nullptr); + return QByteArray(reinterpret_cast(key.key), 32).toHex(); +} + +/** + * @brief Derives a 256bit key from the password and returns it hex-encoded + * @param password Password to decrypt database + * @param salt Salt to improve password strength, must be TOX_PASS_SALT_LENGTH bytes + * @return String representation of key + */ +QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt) +{ + if (password.isEmpty()) + { + return {}; + } + + if (salt.length() != TOX_PASS_SALT_LENGTH) + { + qWarning() << "Salt length doesn't match toxencryptsave expections"; + return {}; + } + + QByteArray passData = password.toUtf8(); + + static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys"); + + TOX_PASS_KEY key; + tox_derive_key_with_salt(reinterpret_cast(passData.data()), + static_cast(passData.size()), + reinterpret_cast(salt.constData()), &key, nullptr); + return QByteArray(reinterpret_cast(key.key), 32).toHex(); } /** @@ -500,7 +572,8 @@ void RawDatabase::process() for (Query& query : trans.queries) { assert(query.statements.isEmpty()); - // sqlite3_prepare_v2 only compiles one statement at a time in the query, we need to loop over them all + // sqlite3_prepare_v2 only compiles one statement at a time in the query, + // we need to loop over them all int curParam=0; const char* compileTail = query.query.data(); do { diff --git a/src/persistence/db/rawdatabase.h b/src/persistence/db/rawdatabase.h index 510fcc90d..95908bbec 100644 --- a/src/persistence/db/rawdatabase.h +++ b/src/persistence/db/rawdatabase.h @@ -41,7 +41,7 @@ public: }; public: - RawDatabase(const QString& path, const QString& password); + RawDatabase(const QString& path, const QString& password, const QByteArray& salt); ~RawDatabase(); bool isOpen(); @@ -68,7 +68,8 @@ private: QString anonymizeQuery(const QByteArray& query); protected: - static QString deriveKey(const QString &password); + static QString deriveKey(const QString& password, const QByteArray& salt); + static QString deriveKey(const QString& password); static QVariant extractData(sqlite3_stmt* stmt, int col); private: @@ -85,6 +86,7 @@ private: QQueue pendingTransactions; QMutex transactionsMutex; QString path; + QByteArray currentSalt; QString currentHexKey; }; diff --git a/src/persistence/profile.cpp b/src/persistence/profile.cpp index 1fa6cebf5..4577b78bf 100644 --- a/src/persistence/profile.cpp +++ b/src/persistence/profile.cpp @@ -56,7 +56,6 @@ QVector Profile::profiles; Profile::Profile(QString name, const QString& password, bool isNewProfile) : name{name}, password{password} - , database(std::make_shared(getDbPath(name), password)) , newProfile{isNewProfile}, isRemoved{false} { if (!password.isEmpty()) @@ -68,22 +67,10 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile) s.setCurrentProfile(name); s.saveGlobal(); - // At this point it's too early to load the personal settings (Nexus will do - // it), so we always load the history, and if it fails we can't change the - // setting now, but we keep a nullptr - if (database->isOpen()) - { - history.reset(new History(database)); - } - else - { - qWarning() << "Failed to open history for profile" << name; - GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); - } - coreThread = new QThread(); coreThread->setObjectName("qTox Core"); core = new Core(coreThread, *this); + QObject::connect(core, &Core::idSet, this, &Profile::loadDatabase, Qt::QueuedConnection); core->moveToThread(coreThread); QObject::connect(coreThread, &QThread::started, core, &Core::start); } @@ -512,6 +499,34 @@ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& passwo return pic; } +void Profile::loadDatabase(const QString& id) +{ + if(isRemoved) + { + qDebug() << "Can't load database of removed profile"; + return; + } + + QByteArray salt = QByteArray::fromHex(ToxId {id}.publicKey.toUtf8()); + if(salt.size() != TOX_PASS_SALT_LENGTH) + { + qWarning() << "Couldn't compute salt from public key" << name; + GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); + } + // At this point it's too early to load the personal settings (Nexus will do it), so we always load + // the history, and if it fails we can't change the setting now, but we keep a nullptr + database = std::make_shared(getDbPath(name), password, salt); + if (database && database->isOpen()) + { + history.reset(new History(database)); + } + else + { + qWarning() << "Failed to open database for profile" << name; + GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled.")); + } +} + /** * @brief Save an avatar to cache. * @param pic Picture to save. @@ -688,11 +703,12 @@ QVector Profile::remove() } QString dbPath = getDbPath(name); - if (database->isOpen() && !database->remove() && QFile::exists(dbPath)) + if (database && database->isOpen() && !database->remove() && QFile::exists(dbPath)) { ret.push_back(dbPath); qWarning() << "Could not remove file " << dbPath; } + history.release(); database.reset(); diff --git a/src/persistence/profile.h b/src/persistence/profile.h index b88921916..11ef83362 100644 --- a/src/persistence/profile.h +++ b/src/persistence/profile.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include "src/persistence/history.h" @@ -32,8 +33,10 @@ class Core; class QThread; -class Profile +class Profile : public QObject { + Q_OBJECT + public: static Profile* loadProfile(QString name, const QString& password = QString()); static Profile* createProfile(QString name, QString password); @@ -78,6 +81,8 @@ public: static bool isEncrypted(QString name); static QString getDbPath(const QString& profileName); +private slots: + void loadDatabase(const QString& id); private: Profile(QString name, const QString& password, bool newProfile); static QVector getFilesByExt(QString extension);