From 2c9cbf68abfb313aef36e01ac3421a68700fd5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Wed, 1 Feb 2017 07:39:47 +0100 Subject: [PATCH 1/6] Minimal SQLCipher support --- hdr/sqlite_modern_cpp.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index f74b085b..61de191b 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -340,6 +340,34 @@ namespace sqlite { return sqlite3_last_insert_rowid(_db.get()); } +#ifdef SQLITE_HAS_CODEC + database(const std::string &db_name, const std::string &key): database(db_name) { + set_key(key); + } + database(const std::u16string &db_name, const std::string &key): database(db_name) { + set_key(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); + } +#endif }; template From 9cef37d2b253113c149aae51cb0b61a8041c6ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Wed, 1 Feb 2017 12:33:22 +0100 Subject: [PATCH 2/6] Move additional constructor arguments into struct --- hdr/sqlite_modern_cpp.h | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 61de191b..9930249f 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -213,7 +214,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); @@ -297,6 +298,10 @@ namespace sqlite { } }; + struct sqlite_config { + std::string key{}; + }; + class database { private: std::shared_ptr _db; @@ -318,6 +323,18 @@ namespace sqlite { database(std::shared_ptr db): _db(db) {} + database(const std::string &db_name, const sqlite_config &config): + database(std::u16string(db_name.begin(), db_name.end()), config) {} + + database(const std::u16string &db_name, const sqlite_config &config): database(db_name) { +#ifdef SQLITE_HAS_CODEC + if(!config.key.empty()) set_key(config.key); +#else + assert(config.key.empty() && "Encryption supported is disabled."); + (void)config; // Suppress unused warning +#endif + } + database_binder operator<<(const std::string& sql) { return database_binder(_db, sql); } @@ -341,13 +358,6 @@ namespace sqlite { } #ifdef SQLITE_HAS_CODEC - database(const std::string &db_name, const std::string &key): database(db_name) { - set_key(key); - } - database(const std::u16string &db_name, const std::string &key): database(db_name) { - set_key(key); - } - void set_key(const std::string &key) { if(auto ret = sqlite3_key(_db.get(), key.data(), key.size())) exceptions::throw_sqlite_error(ret); From 0027f0ba1f006fe40bd87197607aeca8bf0b9fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Wed, 1 Feb 2017 18:50:31 +0100 Subject: [PATCH 3/6] Don't try to autodetect SQLCipher --- hdr/sqlite_modern_cpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 9930249f..bf7112ae 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -327,7 +327,7 @@ namespace sqlite { database(std::u16string(db_name.begin(), db_name.end()), config) {} database(const std::u16string &db_name, const sqlite_config &config): database(db_name) { -#ifdef SQLITE_HAS_CODEC +#ifdef ENABLE_SQLCIPHER if(!config.key.empty()) set_key(config.key); #else assert(config.key.empty() && "Encryption supported is disabled."); @@ -357,7 +357,7 @@ namespace sqlite { return sqlite3_last_insert_rowid(_db.get()); } -#ifdef SQLITE_HAS_CODEC +#ifdef ENABLE_SQLCIPHER void set_key(const std::string &key) { if(auto ret = sqlite3_key(_db.get(), key.data(), key.size())) exceptions::throw_sqlite_error(ret); From 3a5cfb5e3bb9a86c40f51fe611d12236db7ca4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Fri, 10 Feb 2017 06:01:50 +0100 Subject: [PATCH 4/6] Move SQLCipher support into separate file --- hdr/sqlite_modern_cpp.h | 36 +++---------------------- hdr/sqlite_modern_cpp/sqlcipher.h | 44 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 hdr/sqlite_modern_cpp/sqlcipher.h diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index bf7112ae..6e63b7e6 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -299,11 +298,10 @@ namespace sqlite { }; struct sqlite_config { - std::string key{}; }; class database { - private: + protected: std::shared_ptr _db; public: @@ -323,16 +321,12 @@ namespace sqlite { database(std::shared_ptr db): _db(db) {} - database(const std::string &db_name, const sqlite_config &config): - database(std::u16string(db_name.begin(), db_name.end()), config) {} + 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) { -#ifdef ENABLE_SQLCIPHER - if(!config.key.empty()) set_key(config.key); -#else - assert(config.key.empty() && "Encryption supported is disabled."); (void)config; // Suppress unused warning -#endif } database_binder operator<<(const std::string& sql) { @@ -356,28 +350,6 @@ namespace sqlite { sqlite3_int64 last_insert_rowid() const { return sqlite3_last_insert_rowid(_db.get()); } - -#ifdef ENABLE_SQLCIPHER - 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); - } -#endif }; 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); + } + }; +} From d2033434b7294e62de99aa98b94324ded4376e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Mon, 13 Feb 2017 21:17:58 +0100 Subject: [PATCH 5/6] Document SQLCipher in README --- README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/README.md b/README.md index 7c99b24a..7fe7cfff 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,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 ---- From 9eb50358277b1ebfb9642706185f8f0ad883aef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Thu, 16 Feb 2017 14:33:19 +0100 Subject: [PATCH 6/6] Add SQLCipher test --- .travis.yml | 3 ++ Makefile.in | 2 +- tests/sqlcipher.cc | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/sqlcipher.cc 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/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