mirror of
https://github.com/sqlitebrowser/sqlitebrowser.git
synced 2026-01-20 19:11:39 -06:00
cipher: Fix attaching unencrypted DB if main is encrypted and vice-versa
When attaching another database, SQLCipher assumes that it shares passphrase and salt with the main database. Usually this isn't the case, though. With this commit SQLiteBrowser tries to open the database to attach before actually attaching it and asks for encryption details if needed. This allows the user to attach unencrypted databases to an encrypted master database and vice-versa. Note though, that attaching an encrypted database with a non-default (i.e. <> 1024) cipher page size doesn't seem to work - this however, is apparently a SQLCipher limitation. See issue #174.
This commit is contained in:
@@ -2020,17 +2020,8 @@ void MainWindow::fileAttach()
|
||||
if(!QFile::exists(file))
|
||||
return;
|
||||
|
||||
// Ask for name to be given to the attached database
|
||||
QString attachAs = QInputDialog::getText(this,
|
||||
qApp->applicationName(),
|
||||
tr("Please specify the database name under which you want to access the attached database")
|
||||
).trimmed();
|
||||
if(attachAs.isEmpty())
|
||||
return;
|
||||
|
||||
// Attach database
|
||||
if(!db.executeSQL(QString("ATTACH '%1' AS `%2`").arg(file).arg(attachAs), false))
|
||||
QMessageBox::warning(this, qApp->applicationName(), db.lastErrorMessage);
|
||||
// Attach it
|
||||
db.attach(file);
|
||||
}
|
||||
|
||||
void MainWindow::updateFilter(int column, const QString& value)
|
||||
|
||||
185
src/sqlitedb.cpp
185
src/sqlitedb.cpp
@@ -47,15 +47,20 @@ bool DBBrowserDB::getDirty() const
|
||||
|
||||
bool DBBrowserDB::open(const QString& db)
|
||||
{
|
||||
bool ok=false;
|
||||
int err;
|
||||
|
||||
if (isOpen()) close();
|
||||
|
||||
lastErrorMessage = tr("no error");
|
||||
|
||||
isEncrypted = false;
|
||||
|
||||
// Get encryption settings for database file
|
||||
CipherDialog* cipher = 0;
|
||||
if(tryEncryptionSettings(db, &isEncrypted, cipher) == false)
|
||||
{
|
||||
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open database file
|
||||
if(sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
|
||||
{
|
||||
@@ -63,65 +68,22 @@ bool DBBrowserDB::open(const QString& db)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try reading from database
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
QString statement = "SELECT COUNT(*) FROM sqlite_master;";
|
||||
QByteArray utf8Statement = statement.toUtf8();
|
||||
sqlite3_stmt* vm;
|
||||
const char* tail;
|
||||
err = sqlite3_prepare_v2(_db, utf8Statement, utf8Statement.length(), &vm, &tail);
|
||||
if(sqlite3_step(vm) != SQLITE_ROW)
|
||||
{
|
||||
sqlite3_finalize(vm);
|
||||
// Set encryption details if database is encrypted
|
||||
#ifdef ENABLE_SQLCIPHER
|
||||
CipherDialog cipher(0, false);
|
||||
if(cipher.exec())
|
||||
{
|
||||
// Close and reopen database first to be in a clean state after the failed read attempt from above
|
||||
sqlite3_close(_db);
|
||||
if(sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
|
||||
{
|
||||
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set key and, if it differs from the default value, the page size
|
||||
sqlite3_key(_db, cipher.password().toUtf8(), cipher.password().toUtf8().length());
|
||||
if(cipher.pageSize() != 1024)
|
||||
sqlite3_exec(_db, QString("PRAGMA cipher_page_size = %1;").arg(cipher.pageSize()).toUtf8(), NULL, NULL, NULL);
|
||||
|
||||
isEncrypted = true;
|
||||
} else {
|
||||
sqlite3_close(_db);
|
||||
_db = 0;
|
||||
isEncrypted = false;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
sqlite3_close(_db);
|
||||
_db = 0;
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
sqlite3_finalize(vm);
|
||||
done = true;
|
||||
}
|
||||
} while(!done);
|
||||
|
||||
// register collation callback
|
||||
sqlite3_collation_needed(_db, NULL, collation_needed);
|
||||
|
||||
if ( err ) {
|
||||
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
|
||||
sqlite3_close(_db);
|
||||
_db = 0;
|
||||
return false;
|
||||
if(isEncrypted && cipher)
|
||||
{
|
||||
sqlite3_key(_db, cipher->password().toUtf8(), cipher->password().toUtf8().length());
|
||||
if(cipher->pageSize() != 1024)
|
||||
sqlite3_exec(_db, QString("PRAGMA cipher_page_size = %1;").arg(cipher->pageSize()).toUtf8(), NULL, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
delete cipher;
|
||||
|
||||
if (_db)
|
||||
{
|
||||
// register collation callback
|
||||
sqlite3_collation_needed(_db, NULL, collation_needed);
|
||||
|
||||
// set preference defaults
|
||||
QSettings settings(QApplication::organizationName(), QApplication::organizationName());
|
||||
settings.sync();
|
||||
@@ -131,11 +93,116 @@ bool DBBrowserDB::open(const QString& db)
|
||||
// Enable extension loading
|
||||
sqlite3_enable_load_extension(_db, 1);
|
||||
|
||||
ok = true;
|
||||
curDBFilename = db;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
bool DBBrowserDB::attach(const QString& filename, QString attach_as)
|
||||
{
|
||||
// Ask for name to be given to the attached database if none was provided
|
||||
if(attach_as.isEmpty())
|
||||
attach_as = QInputDialog::getText(0,
|
||||
qApp->applicationName(),
|
||||
tr("Please specify the database name under which you want to access the attached database")
|
||||
).trimmed();
|
||||
if(attach_as.isEmpty())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_SQLCIPHER
|
||||
// Try encryption settings
|
||||
CipherDialog* cipher = 0;
|
||||
bool is_encrypted;
|
||||
if(tryEncryptionSettings(filename, &is_encrypted, cipher) == false)
|
||||
return false;
|
||||
|
||||
// Attach database
|
||||
QString key;
|
||||
if(cipher) key = cipher->password();
|
||||
if(!executeSQL(QString("ATTACH '%1' AS `%2` KEY '%3'").arg(filename).arg(attach_as).arg(key), false))
|
||||
{
|
||||
QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage);
|
||||
return false;
|
||||
}
|
||||
if(cipher && cipher->pageSize() != 1024)
|
||||
{
|
||||
if(!executeSQL(QString("PRAGMA `%1`.cipher_page_size = %2").arg(attach_as).arg(cipher->pageSize()), false))
|
||||
{
|
||||
QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Attach database
|
||||
if(!executeSQL(QString("ATTACH '%1' AS `%2`").arg(filename).arg(attach_as), false))
|
||||
{
|
||||
QMessageBox::warning(0, qApp->applicationName(), lastErrorMessage);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBBrowserDB::tryEncryptionSettings(const QString& filename, bool* encrypted, CipherDialog*& cipherSettings)
|
||||
{
|
||||
// Open database file
|
||||
sqlite3* dbHandle;
|
||||
if(sqlite3_open_v2(filename.toUtf8(), &dbHandle, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
// Try reading from database
|
||||
*encrypted = false;
|
||||
cipherSettings = 0;
|
||||
while(true)
|
||||
{
|
||||
QString statement = "SELECT COUNT(*) FROM sqlite_master;";
|
||||
QByteArray utf8Statement = statement.toUtf8();
|
||||
sqlite3_stmt* vm;
|
||||
const char* tail;
|
||||
sqlite3_prepare_v2(dbHandle, utf8Statement, utf8Statement.length(), &vm, &tail);
|
||||
if(sqlite3_step(vm) != SQLITE_ROW)
|
||||
{
|
||||
sqlite3_finalize(vm);
|
||||
#ifdef ENABLE_SQLCIPHER
|
||||
delete cipherSettings;
|
||||
cipherSettings = new CipherDialog(0, false);
|
||||
if(cipherSettings->exec())
|
||||
{
|
||||
// Close and reopen database first to be in a clean state after the failed read attempt from above
|
||||
sqlite3_close(dbHandle);
|
||||
if(sqlite3_open_v2(filename.toUtf8(), &dbHandle, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
|
||||
{
|
||||
delete cipherSettings;
|
||||
cipherSettings = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set key and, if it differs from the default value, the page size
|
||||
sqlite3_key(dbHandle, cipherSettings->password().toUtf8(), cipherSettings->password().toUtf8().length());
|
||||
if(cipherSettings->pageSize() != 1024)
|
||||
sqlite3_exec(dbHandle, QString("PRAGMA cipher_page_size = %1;").arg(cipherSettings->pageSize()).toUtf8(), NULL, NULL, NULL);
|
||||
|
||||
*encrypted = true;
|
||||
} else {
|
||||
sqlite3_close(dbHandle);
|
||||
*encrypted = false;
|
||||
delete cipherSettings;
|
||||
cipherSettings = 0;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
sqlite3_close(dbHandle);
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
sqlite3_finalize(vm);
|
||||
sqlite3_close(dbHandle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DBBrowserDB::setRestorePoint(const QString& pointname)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QMultiMap>
|
||||
#include <QByteArray>
|
||||
|
||||
class CipherDialog;
|
||||
class sqlite3;
|
||||
|
||||
enum
|
||||
@@ -45,6 +46,7 @@ public:
|
||||
explicit DBBrowserDB () : _db( 0 ) {}
|
||||
virtual ~DBBrowserDB (){}
|
||||
bool open ( const QString & db);
|
||||
bool attach(const QString& filename, QString attach_as = "");
|
||||
bool create ( const QString & db);
|
||||
bool close();
|
||||
bool setRestorePoint(const QString& pointname = "RESTOREPOINT");
|
||||
@@ -130,6 +132,8 @@ private:
|
||||
QStringList savepointList;
|
||||
|
||||
bool isEncrypted;
|
||||
|
||||
bool tryEncryptionSettings(const QString& filename, bool* encrypted, CipherDialog*& cipherSettings);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user