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:
Martin Kleusberg
2015-02-04 17:16:13 +01:00
parent 0457b7803e
commit f63b11925d
3 changed files with 132 additions and 70 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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