diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 7c98f9905..775acc654 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -100,6 +100,7 @@ void PreferencesDialog::loadSettings() ui->spinStructureFontSize->setValue(Settings::getValue("db", "fontsize").toInt()); ui->watcherCheckBox->setChecked(Settings::getValue("db", "watcher").toBool()); + ui->checkSQLCipherParentDotenvLookup->setChecked(Settings::getValue("db", "sqlcipherparentdotenvlookup").toBool()); // Gracefully handle the preferred Data Browser font not being available int matchingFont = ui->comboDataBrowserFont->findText(Settings::getValue("databrowser", "font").toString(), Qt::MatchExactly); @@ -199,6 +200,7 @@ void PreferencesDialog::saveSettings(bool accept) Settings::setValue("db", "defaultfieldtype", ui->defaultFieldTypeComboBox->currentIndex()); Settings::setValue("db", "fontsize", ui->spinStructureFontSize->value()); Settings::setValue("db", "watcher", ui->watcherCheckBox->isChecked()); + Settings::setValue("db", "sqlcipherparentdotenvlookup", ui->checkSQLCipherParentDotenvLookup->isChecked()); Settings::setValue("checkversion", "enabled", ui->checkUpdates->isChecked()); diff --git a/src/PreferencesDialog.ui b/src/PreferencesDialog.ui index 45f9f1ee3..bef09cc63 100644 --- a/src/PreferencesDialog.ui +++ b/src/PreferencesDialog.ui @@ -752,16 +752,36 @@ in new project file - - - - enabled - - - true - - - + + + + enabled + + + true + + + + + + + When enabled, DB4S searches parent folders for .db4s.env files, which can slow down opening encrypted databases. + + + Search parent folders for .db4s.env SQLCipher passwords + + + checkSQLCipherParentDotenvLookup + + + + + + + enabled + + + diff --git a/src/Settings.cpp b/src/Settings.cpp index 6fe1b5d7f..856680d32 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -163,6 +163,10 @@ QVariant Settings::getDefaultValue(const std::string& group, const std::string& if(group == "db" && name == "watcher") return false; + // db/sqlcipherparentdotenvlookup? + if(group == "db" && name == "sqlcipherparentdotenvlookup") + return false; + // exportcsv/firstrowheader? if(group == "exportcsv" && name == "firstrowheader") return true; diff --git a/src/sqlitedb.cpp b/src/sqlitedb.cpp index 329a57012..6e58e94ab 100644 --- a/src/sqlitedb.cpp +++ b/src/sqlitedb.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -478,32 +479,31 @@ bool DBBrowserDB::tryEncryptionSettings(const QString& filePath, bool* encrypted // Being in a while loop, we don't want to check the same file multiple times if (!isDotenvChecked) { - QFile databaseFile(filePath); - QFileInfo databaseFileInfo(databaseFile); + const QFileInfo databaseFileInfo(filePath); - QString databaseDirectoryPath = databaseFileInfo.dir().path(); - QString databaseFileName(databaseFileInfo.fileName()); + const QDir databaseDirectory(databaseFileInfo.absoluteDir()); + const QString databaseFileName(databaseFileInfo.fileName()); - QString dotenvFilePath = databaseDirectoryPath + "/.env"; - QSettings dotenv(dotenvFilePath, QSettings::IniFormat); + auto tryLoadDotenv = [&](const QString& dotenvFilePath) -> bool { + if (!QFile::exists(dotenvFilePath)) + return false; - QVariant passwordValue = dotenv.value(databaseFileName); + const QSettings dotenv(dotenvFilePath, QSettings::IniFormat); + const QVariant passwordValue = dotenv.value(databaseFileName); - foundDotenvPassword = !passwordValue.isNull(); - isDotenvChecked = true; + if (passwordValue.isNull()) + return false; - if (foundDotenvPassword) - { - std::string password = passwordValue.toString().toStdString(); + const std::string password = passwordValue.toString().toStdString(); - QVariant keyFormatValue = dotenv.value(databaseFileName + "_keyFormat", QVariant(CipherSettings::KeyFormats::Passphrase)); - CipherSettings::KeyFormats keyFormat = CipherSettings::getKeyFormat(keyFormatValue.toInt()); + const QVariant keyFormatValue = dotenv.value(databaseFileName + "_keyFormat", QVariant(CipherSettings::KeyFormats::Passphrase)); + const CipherSettings::KeyFormats keyFormat = CipherSettings::getKeyFormat(keyFormatValue.toInt()); - int pageSize = dotenv.value(databaseFileName + "_pageSize", enc_default_page_size).toInt(); - int kdfIterations = dotenv.value(databaseFileName + "_kdfIter", enc_default_kdf_iter).toInt(); - int plaintextHeaderSize = dotenv.value(databaseFileName + "_plaintextHeaderSize", enc_default_plaintext_header_size).toInt(); - std::string hmacAlgorithm = dotenv.value(databaseFileName + "_hmacAlgorithm", QString::fromStdString(enc_default_hmac_algorithm)).toString().toStdString(); - std::string kdfAlgorithm = dotenv.value(databaseFileName + "_kdfAlgorithm", QString::fromStdString(enc_default_kdf_algorithm)).toString().toStdString(); + const int pageSize = dotenv.value(databaseFileName + "_pageSize", enc_default_page_size).toInt(); + const int kdfIterations = dotenv.value(databaseFileName + "_kdfIter", enc_default_kdf_iter).toInt(); + const int plaintextHeaderSize = dotenv.value(databaseFileName + "_plaintextHeaderSize", enc_default_plaintext_header_size).toInt(); + const std::string hmacAlgorithm = dotenv.value(databaseFileName + "_hmacAlgorithm", QString::fromStdString(enc_default_hmac_algorithm)).toString().toStdString(); + const std::string kdfAlgorithm = dotenv.value(databaseFileName + "_kdfAlgorithm", QString::fromStdString(enc_default_kdf_algorithm)).toString().toStdString(); cipherSettings->setKeyFormat(keyFormat); cipherSettings->setPassword(password); @@ -512,7 +512,36 @@ bool DBBrowserDB::tryEncryptionSettings(const QString& filePath, bool* encrypted cipherSettings->setHmacAlgorithm("HMAC_" + hmacAlgorithm); cipherSettings->setKdfAlgorithm("PBKDF2_HMAC_" + kdfAlgorithm); cipherSettings->setPlaintextHeaderSize(plaintextHeaderSize); + + return true; + }; + + const QString db4sEnvPath = databaseDirectory.filePath(".db4s.env"); + const bool hasDB4SEnv = QFile::exists(db4sEnvPath); + + if (hasDB4SEnv) + { + foundDotenvPassword = tryLoadDotenv(db4sEnvPath); + } else { + // Deprecated: legacy .env in the database directory only. Remove once .db4s.env is adopted. + foundDotenvPassword = tryLoadDotenv(databaseDirectory.filePath(".env")); + + // Parent folder search is opt-in because it can slow down opening encrypted databases. + if (!foundDotenvPassword && Settings::getValue("db", "sqlcipherparentdotenvlookup").toBool()) + { + QDir searchDirectory(databaseDirectory); + while (searchDirectory.cdUp()) + { + if (tryLoadDotenv(searchDirectory.filePath(".db4s.env"))) + { + foundDotenvPassword = true; + break; + } + } + } } + + isDotenvChecked = true; } if(foundDotenvPassword)