diff --git a/.travis.yml b/.travis.yml index 2f4b3bd6..4b76e670 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,10 @@ addons: - gcc-5 - g++-5 - libsqlite3-dev + - libsqlcipher-dev - libboost-all-dev before_install: - export CXX="g++-5" CC="gcc-5" + +script: ./configure && make test && make clean && make LDFLAGS="-lsqlcipher -DENABLE_SQLCIPHER_TESTS" test diff --git a/Makefile.in b/Makefile.in index ded867ce..3752dc18 100644 --- a/Makefile.in +++ b/Makefile.in @@ -84,7 +84,7 @@ tests/%.result_: tests/%.test a=$$? ;\ if [ $$a != 0 ]; \ then \ - if [ $$a -ge 128 and ] ; \ + if [ $$a -ge 128 ] ; \ then \ echo Crash!! > $@ ; \ elif [ $$a -eq 42 ] ;\ diff --git a/README.md b/README.md index 82f1b4c0..67be08a1 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,47 @@ NDK support Just Make sure you are using the full path of your database file : `sqlite::database db("/data/data/com.your.package/dbfile.db")`. +SQLCipher +---- + +The library has native support for [SQLCipher](https://www.zetetic.net/sqlcipher/). If you want to use encrypted databases, you have to include the `sqlite_moder_cpp/sqlcipher.h` header. +Then you can create a `sqlcipher_database`. + +```c++ +#include +#include +using namespace sqlite; +using namespace std; + +int main() { + + try { + // creates a database file 'dbfile.db' if it does not exists with password 'secret' + sqlcipher_config config; + config.key = secret; + sqlcipher_database db("dbfile.db", config); + + // executes the query and creates a 'user' table + db << + "create table if not exists user (" + " _id integer primary key autoincrement not null," + " age int," + " name text," + " weight real" + ");"; + + // More queries + + db.rekey("new_secret"); // Change the password of the already encrypted database. + + // Even more queries + } + catch (exception& e) { + cout << e.what() << endl; + } +} +``` + Building and Installing ---- diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 0e68deff..4b3682b7 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -232,7 +232,7 @@ namespace sqlite { std::is_floating_point::value || std::is_integral::value || std::is_same::value - > { }; + > { }; template friend database_binder& operator <<(database_binder& db, const T& val); @@ -318,8 +318,11 @@ namespace sqlite { } }; + struct sqlite_config { + }; + class database { - private: + protected: std::shared_ptr _db; public: @@ -342,6 +345,14 @@ namespace sqlite { database(std::shared_ptr db): _db(db) {} + database(const std::string &db_name, const sqlite_config &config): database(db_name) { + (void)config; // Suppress unused warning + } + + database(const std::u16string &db_name, const sqlite_config &config): database(db_name) { + (void)config; // Suppress unused warning + } + database_binder operator<<(const std::string& sql) { return database_binder(_db, sql); } @@ -363,7 +374,6 @@ namespace sqlite { sqlite3_int64 last_insert_rowid() const { return sqlite3_last_insert_rowid(_db.get()); } - }; template diff --git a/hdr/sqlite_modern_cpp/sqlcipher.h b/hdr/sqlite_modern_cpp/sqlcipher.h new file mode 100644 index 00000000..6d2c3d88 --- /dev/null +++ b/hdr/sqlite_modern_cpp/sqlcipher.h @@ -0,0 +1,44 @@ +#pragma once + +#ifndef SQLITE_HAS_CODEC +#define SQLITE_HAS_CODEC +#endif + +#include "../sqlite_modern_cpp.h" + +namespace sqlite { + struct sqlcipher_config : public sqlite_config { + std::string key; + }; + + class sqlcipher_database : public database { + public: + sqlcipher_database(std::string db, const sqlcipher_config &config): database(db, config) { + set_key(config.key); + } + + sqlcipher_database(std::u16string db, const sqlcipher_config &config): database(db, config) { + set_key(config.key); + } + + void set_key(const std::string &key) { + if(auto ret = sqlite3_key(_db.get(), key.data(), key.size())) + exceptions::throw_sqlite_error(ret); + } + + void set_key(const std::string &key, const std::string &db_name) { + if(auto ret = sqlite3_key_v2(_db.get(), db_name.c_str(), key.data(), key.size())) + exceptions::throw_sqlite_error(ret); + } + + void rekey(const std::string &new_key) { + if(auto ret = sqlite3_rekey(_db.get(), new_key.data(), new_key.size())) + exceptions::throw_sqlite_error(ret); + } + + void rekey(const std::string &new_key, const std::string &db_name) { + if(auto ret = sqlite3_rekey_v2(_db.get(), db_name.c_str(), new_key.data(), new_key.size())) + exceptions::throw_sqlite_error(ret); + } + }; +} diff --git a/tests/sqlcipher.cc b/tests/sqlcipher.cc new file mode 100644 index 00000000..be95b5eb --- /dev/null +++ b/tests/sqlcipher.cc @@ -0,0 +1,90 @@ +#ifdef ENABLE_SQLCIPHER_TESTS +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +struct TmpFile +{ + string fname; + + TmpFile() + { + char f[]="/tmp/sqlite_modern_cpp_test_XXXXXX"; + int fid = mkstemp(f); + close(fid); + + fname = f; + } + + ~TmpFile() + { + unlink(fname.c_str()); + } +}; + +int main() +{ + try + { + TmpFile file; + sqlcipher_config config; + { + config.key = "DebugKey"; + sqlcipher_database db(file.fname, config); + + db << "CREATE TABLE foo (a integer, b string);"; + db << "INSERT INTO foo VALUES (?, ?)" << 1 << "hello"; + db << "INSERT INTO foo VALUES (?, ?)" << 2 << "world"; + + string str; + db << "SELECT b from FOO where a=?;" << 2 >> str; + + if(str != "world") + { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + } + try { + config.key = "DebugKey2"; + sqlcipher_database db(file.fname, config); + db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; + + cout << "Can open with wrong key"; + exit(EXIT_FAILURE); + } catch(exceptions::notadb) { + // Expected, wrong key + } + { + config.key = "DebugKey"; + sqlcipher_database db(file.fname, config); + db.rekey("DebugKey2"); + } + { + config.key = "DebugKey2"; + sqlcipher_database db(file.fname, config); + db << "INSERT INTO foo VALUES (?, ?)" << 3 << "fail"; + } + } + catch(sqlite_exception e) + { + cout << "Unexpected error " << e.what() << endl; + exit(EXIT_FAILURE); + } + catch(...) + { + cout << "Unknown error\n"; + exit(EXIT_FAILURE); + } + + cout << "OK\n"; + exit(EXIT_SUCCESS); +} +#else +int main() { + return 42; //Skip test +} +#endif