From 2c59c9203035e35d81adc982ffe7e661d37e9b1f Mon Sep 17 00:00:00 2001 From: Anthony Bilinski Date: Sun, 12 Apr 2020 19:35:29 -0700 Subject: [PATCH] fix(db): Support opening and upgrading to any of three SQLCipher params Fix #5952 --- src/persistence/db/rawdatabase.cpp | 98 ++++++++++++++++++++++-------- src/persistence/db/rawdatabase.h | 6 +- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/persistence/db/rawdatabase.cpp b/src/persistence/db/rawdatabase.cpp index 027d64b25..05265e67c 100644 --- a/src/persistence/db/rawdatabase.cpp +++ b/src/persistence/db/rawdatabase.cpp @@ -175,7 +175,7 @@ bool RawDatabase::open(const QString& path, const QString& hexKey) } if (!hexKey.isEmpty()) { - if (!openEncryptedDatabaseAtLatestVersion(hexKey)) { + if (!openEncryptedDatabaseAtLatestSupportedVersion(hexKey)) { close(); return false; } @@ -183,31 +183,27 @@ bool RawDatabase::open(const QString& path, const QString& hexKey) 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 - // with 4.x defaults. We need to support opening both databases saved with 3.x defaults and 4.x defaults - // so upgrade from 3.x default to 4.x defaults while we're at it + // old qTox database are saved with SQLCipher 3.x defaults. For a period after 1.16.3 but before 1.17.0, databases + // could be partially upgraded to SQLCipher 4.0 defaults, since SQLCipher 3.x isn't capable of setitng all the same + // 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)) { return false; } - if (setCipherParameters(SqlCipherParams::p4_0)) { + auto highestSupportedVersion = highestSupportedParams(); + if (setCipherParameters(highestSupportedVersion)) { if (testUsable()) { - qInfo() << "Opened database with SQLCipher 4.x parameters"; + qInfo() << "Opened database with SQLCipher" << toString(highestSupportedVersion) << "parameters"; return true; } else { - return updateSavedCipherParameters(hexKey); + return updateSavedCipherParameters(hexKey, highestSupportedVersion); } } else { - // setKey again to clear old bad cipher settings - 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; - } + qCritical() << "Failed to set latest supported SQLCipher params!"; + return false; } } @@ -220,10 +216,11 @@ bool RawDatabase::testUsable() /** * @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 - if (!setCipherParameters(SqlCipherParams::p3_0)) { + if (!setCipherParameters(currentParams)) { return false; } @@ -231,25 +228,26 @@ bool RawDatabase::updateSavedCipherParameters(const QString& hexKey) if (user_version < 0) { 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; } - if (!setCipherParameters(SqlCipherParams::p4_0, "sqlcipher4")) { + if (!setCipherParameters(newParams, "newParams")) { return false; } - if (!execNow("SELECT sqlcipher_export('sqlcipher4');")) { + if (!execNow("SELECT sqlcipher_export('newParams');")) { 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; } - if (!execNow("DETACH DATABASE sqlcipher4;")) { + if (!execNow("DETACH DATABASE newParams;")) { return false; } if (!commitDbSwap(hexKey)) { 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; } @@ -290,10 +288,60 @@ bool RawDatabase::setCipherParameters(SqlCipherParams params, const QString& dat break; } } - qDebug() << "Setting SQLCipher" << static_cast(params) << "parameters"; + + qDebug() << "Setting SQLCipher" << toString(params) << "parameters"; 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& 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(SqlCipherParams::p3_0); i < static_cast(newParams); ++i) + { + if (!setKey(hexKey)) { + break; + } + + if (!setCipherParameters(static_cast(i))) { + break; + } + + if (testUsable()) { + return static_cast(i); + } + } + qCritical() << "Failed to check saved SQLCipher params"; + return SqlCipherParams::p3_0; +} + bool RawDatabase::setKey(const QString& hexKey) { // setKey again to clear old bad cipher settings diff --git a/src/persistence/db/rawdatabase.h b/src/persistence/db/rawdatabase.h index fb2ce907f..0387e9aa9 100644 --- a/src/persistence/db/rawdatabase.h +++ b/src/persistence/db/rawdatabase.h @@ -136,9 +136,11 @@ protected slots: private: QString anonymizeQuery(const QByteArray& query); - bool openEncryptedDatabaseAtLatestVersion(const QString& hexKey); - bool updateSavedCipherParameters(const QString& hexKey); + bool openEncryptedDatabaseAtLatestSupportedVersion(const QString& hexKey); + bool updateSavedCipherParameters(const QString& hexKey, SqlCipherParams newParams); bool setCipherParameters(SqlCipherParams params, const QString& database = {}); + SqlCipherParams highestSupportedParams(); + SqlCipherParams readSavedCipherParams(const QString& hexKey, SqlCipherParams newParams); bool setKey(const QString& hexKey); int getUserVersion(); bool encryptDatabase(const QString& newHexKey);