Browse Source

fix(db): Support opening and upgrading to any of three SQLCipher params

Fix #5952
reviewable/pr6105/r6
Anthony Bilinski 5 years ago
parent
commit
2c59c92030
No known key found for this signature in database
GPG Key ID: 2AA8E0DA1B31FB3C
  1. 96
      src/persistence/db/rawdatabase.cpp
  2. 6
      src/persistence/db/rawdatabase.h

96
src/persistence/db/rawdatabase.cpp

@ -175,7 +175,7 @@ bool RawDatabase::open(const QString& path, const QString& hexKey)
} }
if (!hexKey.isEmpty()) { if (!hexKey.isEmpty()) {
if (!openEncryptedDatabaseAtLatestVersion(hexKey)) { if (!openEncryptedDatabaseAtLatestSupportedVersion(hexKey)) {
close(); close();
return false; return false;
} }
@ -183,33 +183,29 @@ bool RawDatabase::open(const QString& path, const QString& hexKey)
return true; return true;
} }
bool RawDatabase::openEncryptedDatabaseAtLatestVersion(const QString& hexKey) bool RawDatabase::openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey)
{ {
// old qTox database are saved with SQLCipher 3.x defaults. New qTox (and for a period during 1.16.3 master) are stored // old qTox database are saved with SQLCipher 3.x defaults. For a period after 1.16.3 but before 1.17.0, databases
// with 4.x defaults. We need to support opening both databases saved with 3.x defaults and 4.x defaults // could be partially upgraded to SQLCipher 4.0 defaults, since SQLCipher 3.x isn't capable of setitng all the same
// so upgrade from 3.x default to 4.x defaults while we're at it // params. If SQLCipher 4.x happened to be used, they would have been fully upgraded to 4.0 default params.
// We need to support all three of these cases, so also upgrade to the latest possible params while we're here
if (!setKey(hexKey)) { if (!setKey(hexKey)) {
return false; return false;
} }
if (setCipherParameters(SqlCipherParams::p4_0)) { auto highestSupportedVersion = highestSupportedParams();
if (setCipherParameters(highestSupportedVersion)) {
if (testUsable()) { if (testUsable()) {
qInfo() << "Opened database with SQLCipher 4.x parameters"; qInfo() << "Opened database with SQLCipher" << toString(highestSupportedVersion) << "parameters";
return true; return true;
} else { } else {
return updateSavedCipherParameters(hexKey); return updateSavedCipherParameters(hexKey, highestSupportedVersion);
} }
} else { } else {
// setKey again to clear old bad cipher settings qCritical() << "Failed to set latest supported SQLCipher params!";
if (setKey(hexKey) && setCipherParameters(SqlCipherParams::p3_0) && testUsable()) {
qInfo() << "Opened database with SQLCipher 3.x parameters";
return true;
} else {
qCritical() << "Failed to open database with SQLCipher 3.x parameters";
return false; return false;
} }
} }
}
bool RawDatabase::testUsable() bool RawDatabase::testUsable()
{ {
@ -220,10 +216,11 @@ bool RawDatabase::testUsable()
/** /**
* @brief Changes stored db encryption from SQLCipher 3.x defaults to 4.x defaults * @brief Changes stored db encryption from SQLCipher 3.x defaults to 4.x defaults
*/ */
bool RawDatabase::updateSavedCipherParameters(const QString& hexKey) bool RawDatabase::updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams)
{ {
auto currentParams = readSavedCipherParams(hexKey, newParams);
setKey(hexKey); // setKey again because a SELECT has already been run, causing crypto settings to take effect setKey(hexKey); // setKey again because a SELECT has already been run, causing crypto settings to take effect
if (!setCipherParameters(SqlCipherParams::p3_0)) { if (!setCipherParameters(currentParams)) {
return false; return false;
} }
@ -231,25 +228,26 @@ bool RawDatabase::updateSavedCipherParameters(const QString& hexKey)
if (user_version < 0) { if (user_version < 0) {
return false; return false;
} }
if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS sqlcipher4 KEY \"x'" + hexKey + "'\";")) { if (!execNow("ATTACH DATABASE '" + path + ".tmp' AS newParams KEY \"x'" + hexKey + "'\";")) {
return false; return false;
} }
if (!setCipherParameters(SqlCipherParams::p4_0, "sqlcipher4")) { if (!setCipherParameters(newParams, "newParams")) {
return false; return false;
} }
if (!execNow("SELECT sqlcipher_export('sqlcipher4');")) { if (!execNow("SELECT sqlcipher_export('newParams');")) {
return false; return false;
} }
if (!execNow(QString("PRAGMA sqlcipher4.user_version = %1;").arg(user_version))) { if (!execNow(QString("PRAGMA newParams.user_version = %1;").arg(user_version))) {
return false; return false;
} }
if (!execNow("DETACH DATABASE sqlcipher4;")) { if (!execNow("DETACH DATABASE newParams;")) {
return false; return false;
} }
if (!commitDbSwap(hexKey)) { if (!commitDbSwap(hexKey)) {
return false; return false;
} }
qInfo() << "Upgraded database from SQLCipher 3.x defaults to SQLCipher 4.x defaults"; qInfo() << "Upgraded database from SQLCipher" << toString(currentParams) << "params to" <<
toString(newParams) << "params complete";
return true; return true;
} }
@ -290,10 +288,60 @@ bool RawDatabase::setCipherParameters(SqlCipherParams params, const QString& dat
break; break;
} }
} }
qDebug() << "Setting SQLCipher" << static_cast<int>(params) << "parameters";
qDebug() << "Setting SQLCipher" << toString(params) << "parameters";
return execNow(defaultParams.replace("database.", prefix)); return execNow(defaultParams.replace("database.", prefix));
} }
RawDatabase::SqlCipherParams RawDatabase::highestSupportedParams()
{
// Note: This is just calling into the sqlcipher library, not touching the database.
QString cipherVersion;
if (!execNow(RawDatabase::Query("PRAGMA cipher_version", [&](const QVector<QVariant>& row) {
cipherVersion = row[0].toString();
}))) {
qCritical() << "Failed to read cipher_version";
return SqlCipherParams::p3_0;
}
auto majorVersion = cipherVersion.split('.')[0].toInt();
SqlCipherParams highestSupportedParams;
switch (majorVersion) {
case 3:
highestSupportedParams = SqlCipherParams::halfUpgradedTo4;
break;
case 4:
highestSupportedParams = SqlCipherParams::p4_0;
break;
default:
qCritical() << "Unsupported SQLCipher version detected!";
return SqlCipherParams::p3_0;
}
qDebug() << "Highest supported SQLCipher params on this system are" << toString(highestSupportedParams);
return highestSupportedParams;
}
RawDatabase::SqlCipherParams RawDatabase::readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams)
{
for (int i = static_cast<int>(SqlCipherParams::p3_0); i < static_cast<int>(newParams); ++i)
{
if (!setKey(hexKey)) {
break;
}
if (!setCipherParameters(static_cast<SqlCipherParams>(i))) {
break;
}
if (testUsable()) {
return static_cast<SqlCipherParams>(i);
}
}
qCritical() << "Failed to check saved SQLCipher params";
return SqlCipherParams::p3_0;
}
bool RawDatabase::setKey(const QString& hexKey) bool RawDatabase::setKey(const QString& hexKey)
{ {
// setKey again to clear old bad cipher settings // setKey again to clear old bad cipher settings

6
src/persistence/db/rawdatabase.h

@ -136,9 +136,11 @@ protected slots:
private: private:
QString anonymizeQuery(const QByteArray& query); QString anonymizeQuery(const QByteArray& query);
bool openEncryptedDatabaseAtLatestVersion(const QString& hexKey); bool openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey);
bool updateSavedCipherParameters(const QString& hexKey); bool updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams);
bool setCipherParameters(SqlCipherParams params, const QString& database = {}); bool setCipherParameters(SqlCipherParams params, const QString& database = {});
SqlCipherParams highestSupportedParams();
SqlCipherParams readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams);
bool setKey(const QString& hexKey); bool setKey(const QString& hexKey);
int getUserVersion(); int getUserVersion();
bool encryptDatabase(const QString& newHexKey); bool encryptDatabase(const QString& newHexKey);

Loading…
Cancel
Save