Add initial SQLCipher support

Add some basic initial support for SQLCipher. Note that this is more of
a POC than a final implementation.

This commit adds an option called 'sqlcipher' to the cmake and qmake
projects which - when enabled - replaces the default SQLite3 include and
library files by their SQLCipher counter-parts. Especially on MacOS X
there might be some more work required in finding the correct include
paths. The SQLCipher library supports unencrypted databases, too, so
even if the option is enabled the program behaves like before. You can
see the difference, though, in the About Dialog where the SQLite version
string will say 'SQLCipher version xy'.

When the sqlcipher option is enabled and you try to open a file which is
neither a project file nor a normal SQLite3 database it is assumed now
that the file is an encypted database. There is no way to tell between
an invalid file and an encypted file, so in both cases a password dialog
pops up. When the correct password and page size are entered the file is
opened and can be edited like any other database before.

Creating encrypted databases isn't supported yet. So for testing you
need to fall back to the sqlcipher command line tool.

See issue #12.
This commit is contained in:
Martin Kleusberg
2014-11-01 12:49:14 +01:00
parent e93392c436
commit 9ba36d02b2
9 changed files with 107 additions and 39 deletions

View File

@@ -29,7 +29,7 @@ endif()
set(SQLB_HDR
src/gen_version.h
src/sqlitetypes.h
src/csvparser.h
src/csvparser.h
src/grammar/sqlite3TokenTypes.hpp
src/grammar/Sqlite3Lexer.hpp
src/grammar/Sqlite3Parser.hpp
@@ -54,6 +54,7 @@ set(SQLB_MOC_HDR
src/sqltextedit.h
src/DbStructureModel.h
src/Application.h
src/sqlite.h
)
set(SQLB_SRC
@@ -179,13 +180,21 @@ if(APPLE)
add_definitions(-DCHECKNEWVERSION)
endif(APPLE)
# SQLCipher option
if(sqlcipher)
add_definitions(-DSQLCIPHER)
set(LIBSQLITE_NAME sqlcipher)
else(sqlcipher)
set(LIBSQLITE_NAME sqlite3)
endif(sqlcipher)
# add extra library path for MacOS and FreeBSD
set(EXTRAPATH APPLE OR ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
if(EXTRAPATH)
find_library(LIBSQLITE sqlite3 HINTS /usr/local/lib /usr/local/opt/sqlite/lib)
find_library(LIBSQLITE ${LIBSQLITE_NAME} HINTS /usr/local/lib /usr/local/opt/sqlite/lib)
set(ADDITIONAL_INCLUDE_PATHS /usr/local/include /usr/local/opt/sqlite/include)
else(EXTRAPATH)
find_library(LIBSQLITE sqlite3)
find_library(LIBSQLITE ${LIBSQLITE_NAME})
endif(EXTRAPATH)
include_directories(

View File

@@ -1,7 +1,7 @@
#include "AboutDialog.h"
#include "ui_AboutDialog.h"
#include "gen_version.h"
#include <sqlite3.h>
#include "sqlite.h"
AboutDialog::AboutDialog(QWidget *parent) :
QDialog(parent),
@@ -12,7 +12,11 @@ AboutDialog::AboutDialog(QWidget *parent) :
ui->label_version->setText(ui->label_version->text() + " " + APP_VERSION);
ui->label_versionqt->setText(ui->label_versionqt->text() + " " + QT_VERSION_STR);
#ifdef SQLCIPHER
ui->label_versionsqlite->setText(ui->label_versionsqlite->text().replace("SQLite", "SQLCipher") + " " + SQLITE_VERSION);
#else
ui->label_versionsqlite->setText(ui->label_versionsqlite->text() + " " + SQLITE_VERSION);
#endif
}
AboutDialog::~AboutDialog()

View File

@@ -3,12 +3,12 @@
#include "sqlitedb.h"
#include "PreferencesDialog.h"
#include "sqlitetablemodel.h"
#include "sqlite.h"
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QFileDialog>
#include <sqlite3.h>
ExportCsvDialog::ExportCsvDialog(DBBrowserDB* db, QWidget* parent, const QString& query, const QString& selection)
: QDialog(parent),

View File

@@ -2,6 +2,7 @@
#include "ui_ImportCsvDialog.h"
#include "sqlitedb.h"
#include "csvparser.h"
#include "sqlite.h"
#include <QMessageBox>
#include <QProgressDialog>
@@ -10,7 +11,6 @@
#include <QTextCodec>
#include <QCompleter>
#include <QComboBox>
#include <sqlite3.h>
#include <QFile>
#include <QTextStream>
#include <QSettings>

View File

@@ -15,6 +15,7 @@
#include "VacuumDialog.h"
#include "DbStructureModel.h"
#include "gen_version.h"
#include "sqlite.h"
#include <QFileDialog>
#include <QFile>
@@ -28,7 +29,6 @@
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include <QElapsedTimer>
#include <sqlite3.h>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
@@ -195,23 +195,30 @@ bool MainWindow::fileOpen(const QString& fileName, bool dontAddToRecentFiles)
if(QFile::exists(wFile) )
{
fileClose();
if(db.open(wFile))
// Try opening it as a project file first
if(loadProject(wFile))
{
statusEncodingLabel->setText(db.getPragma("encoding"));
setCurrentFile(wFile);
if(!dontAddToRecentFiles)
addToRecentFilesMenu(wFile);
retval = true;
} else {
// Failed opening file; so it might be a project file instead
return loadProject(wFile);
// No project file; so it should be a database file
if(db.open(wFile))
{
statusEncodingLabel->setText(db.getPragma("encoding"));
setCurrentFile(wFile);
if(!dontAddToRecentFiles)
addToRecentFilesMenu(wFile);
openSqlTab(true);
retval = true;
} else {
QMessageBox::warning(this, qApp->applicationName(), tr("Invalid file format."));
return false;
}
}
loadExtensionsFromSettings();
populateStructure();
resetBrowser();
if(ui->mainTab->currentIndex() == 2)
loadPragmas();
openSqlTab(true);
}
return retval;
@@ -1749,10 +1756,7 @@ bool MainWindow::loadProject(QString filename)
xml.readNext(); // token == QXmlStreamReader::StartDocument
xml.readNext(); // name == sqlb_project
if(xml.name() != "sqlb_project")
{
QMessageBox::warning(this, qApp->applicationName(), tr("Invalid file format."));
return false;
}
addToRecentFilesMenu(filename);

12
src/sqlite.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef SQLITE_H
#define SQLITE_H
#ifdef SQLCIPHER
#define SQLITE_TEMP_STORE 2
#define SQLITE_HAS_CODEC
#include <sqlcipher/sqlite3.h>
#else
#include <sqlite3.h>
#endif
#endif

View File

@@ -1,5 +1,6 @@
#include "sqlitedb.h"
#include "sqlitetablemodel.h"
#include "sqlite.h"
#include <QFile>
#include <QMessageBox>
@@ -7,7 +8,7 @@
#include <QApplication>
#include <QSettings>
#include <QDebug>
#include <sqlite3.h>
#include <QInputDialog>
// collation callbacks
int collCompare(void* /*pArg*/, int /*eTextRepA*/, const void* sA, int /*eTextRepB*/, const void* sB)
@@ -43,32 +44,62 @@ bool DBBrowserDB::getDirty() const
return !savepointList.empty();
}
bool DBBrowserDB::open ( const QString & db)
bool DBBrowserDB::open(const QString& db)
{
bool ok=false;
int err;
if (isOpen()) close();
//try to verify the SQLite version 3 file header
QFile dbfile(db);
if ( dbfile.open( QIODevice::ReadOnly ) ) {
char buffer[16+1];
dbfile.readLine(buffer, 16);
QString contents = QString(buffer);
dbfile.close();
if (!contents.startsWith("SQLite format 3")) {
lastErrorMessage = QObject::tr("File is not a SQLite 3 database");
return false;
}
} else {
lastErrorMessage = QObject::tr("File could not be read");
lastErrorMessage = QObject::tr("no error");
// Open database file
if(sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL) != SQLITE_OK)
{
lastErrorMessage = QString::fromUtf8((const char*)sqlite3_errmsg(_db));
return false;
}
lastErrorMessage = QObject::tr("no error");
err = sqlite3_open_v2(db.toUtf8(), &_db, SQLITE_OPEN_READWRITE, NULL);
// 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)
{
#ifdef SQLCIPHER
QString pass = QInputDialog::getText(0, qApp->applicationName(),
QObject::tr("Couldn't read from database file. This means it is either not a valid SQLite3 "
"database or it is encrypted.\nIn the latter case you can provide a passphrase to open the file. Note"
"that only databases encrypted using SQLCipher are supported."));
if(pass.isEmpty())
{
sqlite3_close(_db);
_db = 0;
return false;
} else {
int pagesize = QInputDialog::getInt(0, qApp->applicationName(),
QObject::tr("If the database has been encrypted using a different page size than normally (i.e. 1024 bytes) this "
"value is required, too."),
1024, 0);
if(pagesize == 0) pagesize = 1024;
sqlite3_key(_db, pass.toUtf8(), pass.toUtf8().length());
sqlite3_exec(_db, QString("PRAGMA cipher_page_size = %1;").arg(pagesize).toUtf8(), NULL, NULL, NULL);
}
#else
sqlite3_close(_db);
_db = 0;
return false;
#endif
} else {
done = true;
}
sqlite3_finalize(vm);
} while(!done);
// register collation callback
sqlite3_collation_needed(_db, NULL, collation_needed);

View File

@@ -1,10 +1,10 @@
#include "sqlitetablemodel.h"
#include "sqlitedb.h"
#include "sqlite.h"
#include <QDebug>
#include <QMessageBox>
#include <QApplication>
#include <sqlite3.h>
SqliteTableModel::SqliteTableModel(QObject* parent, DBBrowserDB* db, size_t chunkSize)
: QAbstractTableModel(parent)

View File

@@ -42,7 +42,8 @@ HEADERS += \
SqlExecutionArea.h \
VacuumDialog.h \
DbStructureModel.h \
Application.h
Application.h \
sqlite.h
SOURCES += \
sqlitedb.cpp \
@@ -88,6 +89,13 @@ TRANSLATIONS += \
translations/sqlb_fr.ts \
translations/sqlb_ru.ts
CONFIG(sqlcipher) {
QMAKE_CXXFLAGS += -DENABLE_SQLCIPHER
LIBS += -lsqlcipher
} else {
LIBS += -lsqlite3
}
LIBPATH_QHEXEDIT=$$PWD/../libs/qhexedit
LIBPATH_ANTLR=$$PWD/../libs/antlr-2.7.7
LIBPATH_QCUSTOMPLOT=$$PWD/../libs/qcustomplot-source
@@ -121,7 +129,7 @@ mac {
UI_DIR = .ui
INCLUDEPATH += $$PWD/../libs/antlr-2.7.7 $$PWD/../libs/qhexedit $$PWD/../libs/qcustomplot-source $$PWD/..
LIBS += -L$$LIBPATH_QHEXEDIT -L$$LIBPATH_ANTLR -L$$LIBPATH_QCUSTOMPLOT -lantlr -lqhexedit -lqcustomplot -lsqlite3
LIBS += -L$$LIBPATH_QHEXEDIT -L$$LIBPATH_ANTLR -L$$LIBPATH_QCUSTOMPLOT -lantlr -lqhexedit -lqcustomplot
DEPENDPATH += $$PWD/../libs/antlr-2.7.7 $$PWD/../libs/qhexedit $$PWD/../libs/qcustomplot-source
# Rules for creating/updating {ts|qm}-files