mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge pull request #3827
sudden6 (3): feat(database): use own public key as salt to encrypt the database feat(database): make a backup before upgrading refactor(database): reduce line length in some places
This commit is contained in:
commit
330d6405f7
|
@ -91,15 +91,55 @@
|
||||||
* @param password If empty, the database will be opened unencrypted.
|
* @param password If empty, the database will be opened unencrypted.
|
||||||
* Otherwise we will use toxencryptsave to derive a key and encrypt the database.
|
* Otherwise we will use toxencryptsave to derive a key and encrypt the database.
|
||||||
*/
|
*/
|
||||||
RawDatabase::RawDatabase(const QString &path, const QString& password)
|
RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt)
|
||||||
: workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)}
|
: 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");
|
workerThread->setObjectName("qTox Database");
|
||||||
moveToThread(workerThread.get());
|
moveToThread(workerThread.get());
|
||||||
workerThread->start();
|
workerThread->start();
|
||||||
|
|
||||||
if (!open(path, currentHexKey))
|
// first try with the new salt
|
||||||
|
if (open(path, currentHexKey))
|
||||||
|
{
|
||||||
return;
|
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()
|
RawDatabase::~RawDatabase()
|
||||||
|
@ -313,7 +353,7 @@ bool RawDatabase::setPassword(const QString& password)
|
||||||
|
|
||||||
if (!password.isEmpty())
|
if (!password.isEmpty())
|
||||||
{
|
{
|
||||||
QString newHexKey = deriveKey(password);
|
QString newHexKey = deriveKey(password, currentSalt);
|
||||||
if (!currentHexKey.isEmpty())
|
if (!currentHexKey.isEmpty())
|
||||||
{
|
{
|
||||||
if (!execNow("PRAGMA rekey = \"x'"+newHexKey+"'\""))
|
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
|
* @brief Derives a 256bit key from the password and returns it hex-encoded
|
||||||
* @param password Password to decrypt database
|
* @param password Password to decrypt database
|
||||||
* @return String representation of key
|
* @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)
|
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";
|
static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH+1] = "L'ignorance est le pire des maux";
|
||||||
TOX_PASS_KEY key;
|
TOX_PASS_KEY key;
|
||||||
tox_derive_key_with_salt((uint8_t*)passData.data(), passData.size(), expandConstant, &key, nullptr);
|
tox_derive_key_with_salt(reinterpret_cast<uint8_t*>(passData.data()),
|
||||||
return QByteArray((char*)key.key, 32).toHex();
|
static_cast<std::size_t>(passData.size()), expandConstant, &key, nullptr);
|
||||||
|
return QByteArray(reinterpret_cast<char*>(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<uint8_t*>(passData.data()),
|
||||||
|
static_cast<std::size_t>(passData.size()),
|
||||||
|
reinterpret_cast<const uint8_t*>(salt.constData()), &key, nullptr);
|
||||||
|
return QByteArray(reinterpret_cast<char*>(key.key), 32).toHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -500,7 +572,8 @@ void RawDatabase::process()
|
||||||
for (Query& query : trans.queries)
|
for (Query& query : trans.queries)
|
||||||
{
|
{
|
||||||
assert(query.statements.isEmpty());
|
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;
|
int curParam=0;
|
||||||
const char* compileTail = query.query.data();
|
const char* compileTail = query.query.data();
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -41,7 +41,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RawDatabase(const QString& path, const QString& password);
|
RawDatabase(const QString& path, const QString& password, const QByteArray& salt);
|
||||||
~RawDatabase();
|
~RawDatabase();
|
||||||
bool isOpen();
|
bool isOpen();
|
||||||
|
|
||||||
|
@ -68,7 +68,8 @@ private:
|
||||||
QString anonymizeQuery(const QByteArray& query);
|
QString anonymizeQuery(const QByteArray& query);
|
||||||
|
|
||||||
protected:
|
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);
|
static QVariant extractData(sqlite3_stmt* stmt, int col);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -85,6 +86,7 @@ private:
|
||||||
QQueue<Transaction> pendingTransactions;
|
QQueue<Transaction> pendingTransactions;
|
||||||
QMutex transactionsMutex;
|
QMutex transactionsMutex;
|
||||||
QString path;
|
QString path;
|
||||||
|
QByteArray currentSalt;
|
||||||
QString currentHexKey;
|
QString currentHexKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ QVector<QString> Profile::profiles;
|
||||||
|
|
||||||
Profile::Profile(QString name, const QString& password, bool isNewProfile)
|
Profile::Profile(QString name, const QString& password, bool isNewProfile)
|
||||||
: name{name}, password{password}
|
: name{name}, password{password}
|
||||||
, database(std::make_shared<RawDatabase>(getDbPath(name), password))
|
|
||||||
, newProfile{isNewProfile}, isRemoved{false}
|
, newProfile{isNewProfile}, isRemoved{false}
|
||||||
{
|
{
|
||||||
if (!password.isEmpty())
|
if (!password.isEmpty())
|
||||||
|
@ -68,22 +67,10 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile)
|
||||||
s.setCurrentProfile(name);
|
s.setCurrentProfile(name);
|
||||||
s.saveGlobal();
|
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 = new QThread();
|
||||||
coreThread->setObjectName("qTox Core");
|
coreThread->setObjectName("qTox Core");
|
||||||
core = new Core(coreThread, *this);
|
core = new Core(coreThread, *this);
|
||||||
|
QObject::connect(core, &Core::idSet, this, &Profile::loadDatabase, Qt::QueuedConnection);
|
||||||
core->moveToThread(coreThread);
|
core->moveToThread(coreThread);
|
||||||
QObject::connect(coreThread, &QThread::started, core, &Core::start);
|
QObject::connect(coreThread, &QThread::started, core, &Core::start);
|
||||||
}
|
}
|
||||||
|
@ -512,6 +499,34 @@ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& passwo
|
||||||
return pic;
|
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<RawDatabase>(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.
|
* @brief Save an avatar to cache.
|
||||||
* @param pic Picture to save.
|
* @param pic Picture to save.
|
||||||
|
@ -688,11 +703,12 @@ QVector<QString> Profile::remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
QString dbPath = getDbPath(name);
|
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);
|
ret.push_back(dbPath);
|
||||||
qWarning() << "Could not remove file " << dbPath;
|
qWarning() << "Could not remove file " << dbPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
history.release();
|
history.release();
|
||||||
database.reset();
|
database.reset();
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QObject>
|
||||||
#include <tox/toxencryptsave.h>
|
#include <tox/toxencryptsave.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "src/persistence/history.h"
|
#include "src/persistence/history.h"
|
||||||
|
@ -32,8 +33,10 @@
|
||||||
class Core;
|
class Core;
|
||||||
class QThread;
|
class QThread;
|
||||||
|
|
||||||
class Profile
|
class Profile : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Profile* loadProfile(QString name, const QString& password = QString());
|
static Profile* loadProfile(QString name, const QString& password = QString());
|
||||||
static Profile* createProfile(QString name, QString password);
|
static Profile* createProfile(QString name, QString password);
|
||||||
|
@ -78,6 +81,8 @@ public:
|
||||||
static bool isEncrypted(QString name);
|
static bool isEncrypted(QString name);
|
||||||
static QString getDbPath(const QString& profileName);
|
static QString getDbPath(const QString& profileName);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void loadDatabase(const QString& id);
|
||||||
private:
|
private:
|
||||||
Profile(QString name, const QString& password, bool newProfile);
|
Profile(QString name, const QString& password, bool newProfile);
|
||||||
static QVector<QString> getFilesByExt(QString extension);
|
static QVector<QString> getFilesByExt(QString extension);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user