mirror of
https://github.com/cuberite/SQLiteCpp.git
synced 2025-08-05 02:06:02 -04:00
Merge pull request #107 from jowr/master: encrypted databases
Added ability to open encrypted databases.
This commit is contained in:
commit
cb44cca41c
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#include <SQLiteCpp/Column.h>
|
#include <SQLiteCpp/Column.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string.h>
|
||||||
|
|
||||||
// Forward declarations to avoid inclusion of <sqlite3.h> in a header
|
// Forward declarations to avoid inclusion of <sqlite3.h> in a header
|
||||||
struct sqlite3;
|
struct sqlite3;
|
||||||
@ -369,6 +369,51 @@ public:
|
|||||||
*/
|
*/
|
||||||
void loadExtension(const char* apExtensionName, const char* apEntryPointName);
|
void loadExtension(const char* apExtensionName, const char* apEntryPointName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the key for the current sqlite database instance.
|
||||||
|
*
|
||||||
|
* This is the equivalent of the sqlite3_key call and should thus be called
|
||||||
|
* directly after opening the database.
|
||||||
|
* Open encrypted database -> call db.key("secret") -> database ready
|
||||||
|
*
|
||||||
|
* @param[in] aKey Key to decode/encode the database
|
||||||
|
*
|
||||||
|
* @throw SQLite::Exception in case of error
|
||||||
|
*/
|
||||||
|
void key(const std::string& aKey) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the key for the current sqlite database instance.
|
||||||
|
*
|
||||||
|
* This is the equivalent of the sqlite3_rekey call and should thus be called
|
||||||
|
* after the database has been opened with a valid key. To decrypt a
|
||||||
|
* database, call this method with an empty string.
|
||||||
|
* Open normal database -> call db.rekey("secret") -> encrypted database, database ready
|
||||||
|
* Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready
|
||||||
|
* Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready
|
||||||
|
*
|
||||||
|
* @param[in] aNewKey New key to encode the database
|
||||||
|
*
|
||||||
|
* @throw SQLite::Exception in case of error
|
||||||
|
*/
|
||||||
|
void rekey(const std::string& aNewKey) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test if a file contains an unencrypted database.
|
||||||
|
*
|
||||||
|
* This is a simple test that reads the first bytes of a database file and
|
||||||
|
* compares them to the standard header for unencrypted databases. If the
|
||||||
|
* header does not match the standard string, we assume that we have an
|
||||||
|
* encrypted file.
|
||||||
|
*
|
||||||
|
* @param[in] aFilename path/uri to a file
|
||||||
|
*
|
||||||
|
* @return true if the database has the standard header.
|
||||||
|
*
|
||||||
|
* @throw SQLite::Exception in case of error
|
||||||
|
*/
|
||||||
|
static const bool isUnencrypted(const std::string& aFilename);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// @{ Database must be non-copyable
|
/// @{ Database must be non-copyable
|
||||||
Database(const Database&);
|
Database(const Database&);
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
#include <SQLiteCpp/Exception.h>
|
#include <SQLiteCpp/Exception.h>
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#ifndef SQLITE_DETERMINISTIC
|
#ifndef SQLITE_DETERMINISTIC
|
||||||
#define SQLITE_DETERMINISTIC 0x800
|
#define SQLITE_DETERMINISTIC 0x800
|
||||||
@ -62,7 +64,6 @@ Database::Database(const char* apFilename,
|
|||||||
sqlite3_close(mpSQLite); // close is required even in case of error on opening
|
sqlite3_close(mpSQLite); // close is required even in case of error on opening
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aBusyTimeoutMs > 0)
|
if (aBusyTimeoutMs > 0)
|
||||||
{
|
{
|
||||||
setBusyTimeout(aBusyTimeoutMs);
|
setBusyTimeout(aBusyTimeoutMs);
|
||||||
@ -84,7 +85,6 @@ Database::Database(const std::string& aFilename,
|
|||||||
sqlite3_close(mpSQLite); // close is required even in case of error on opening
|
sqlite3_close(mpSQLite); // close is required even in case of error on opening
|
||||||
throw exception;
|
throw exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aBusyTimeoutMs > 0)
|
if (aBusyTimeoutMs > 0)
|
||||||
{
|
{
|
||||||
setBusyTimeout(aBusyTimeoutMs);
|
setBusyTimeout(aBusyTimeoutMs);
|
||||||
@ -108,8 +108,8 @@ Database::~Database() noexcept // nothrow
|
|||||||
* @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
|
* @brief Set a busy handler that sleeps for a specified amount of time when a table is locked.
|
||||||
*
|
*
|
||||||
* This is useful in multithreaded program to handle case where a table is locked for writting by a thread.
|
* This is useful in multithreaded program to handle case where a table is locked for writting by a thread.
|
||||||
* Any other thread cannot access the table and will receive a SQLITE_BUSY error:
|
* Any other thread cannot access the table and will receive a SQLITE_BUSY error:
|
||||||
* setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error.
|
* setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error.
|
||||||
* Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;".
|
* Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;".
|
||||||
* Default busy timeout is 0ms.
|
* Default busy timeout is 0ms.
|
||||||
*
|
*
|
||||||
@ -229,4 +229,59 @@ void Database::loadExtension(const char* apExtensionName, const char *apEntryPoi
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the key for the current sqlite database instance.
|
||||||
|
void Database::key(const std::string& aKey) const
|
||||||
|
{
|
||||||
|
int pass_len = aKey.length();
|
||||||
|
#ifdef SQLITE_HAS_CODEC
|
||||||
|
if (pass_len > 0) {
|
||||||
|
const int ret = sqlite3_key(mpSQLite, aKey.c_str(), pass_len);
|
||||||
|
check(ret);
|
||||||
|
}
|
||||||
|
#else // SQLITE_HAS_CODEC
|
||||||
|
if (pass_len > 0) {
|
||||||
|
const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
#endif // SQLITE_HAS_CODEC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the key for the current sqlite database instance.
|
||||||
|
void Database::rekey(const std::string& aNewKey) const
|
||||||
|
{
|
||||||
|
#ifdef SQLITE_HAS_CODEC
|
||||||
|
int pass_len = aNewKey.length();
|
||||||
|
if (pass_len > 0) {
|
||||||
|
const int ret = sqlite3_rekey(mpSQLite, aNewKey.c_str(), pass_len);
|
||||||
|
check(ret);
|
||||||
|
} else {
|
||||||
|
const int ret = sqlite3_rekey(mpSQLite, nullptr, 0);
|
||||||
|
check(ret);
|
||||||
|
}
|
||||||
|
#else // SQLITE_HAS_CODEC
|
||||||
|
const SQLite::Exception exception("No encryption support, recompile with SQLITE_HAS_CODEC to enable.");
|
||||||
|
throw exception;
|
||||||
|
#endif // SQLITE_HAS_CODEC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a file contains an unencrypted database.
|
||||||
|
const bool Database::isUnencrypted(const std::string& aFilename)
|
||||||
|
{
|
||||||
|
if (aFilename.length() > 0) {
|
||||||
|
std::ifstream fileBuffer(aFilename.c_str(), std::ios::in | std::ios::binary);
|
||||||
|
char header[16];
|
||||||
|
if (fileBuffer.is_open()) {
|
||||||
|
fileBuffer.seekg(0, std::ios::beg);
|
||||||
|
fileBuffer.getline(header, 16);
|
||||||
|
fileBuffer.close();
|
||||||
|
} else {
|
||||||
|
const SQLite::Exception exception("Error opening file: " + aFilename);
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
return strncmp(header, "SQLite format 3\000", 16) == 0;
|
||||||
|
}
|
||||||
|
const SQLite::Exception exception("Could not open database, the aFilename parameter was empty.");
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace SQLite
|
} // namespace SQLite
|
||||||
|
@ -258,3 +258,72 @@ TEST(Database, execException) {
|
|||||||
|
|
||||||
// TODO: test Database::createFunction()
|
// TODO: test Database::createFunction()
|
||||||
// TODO: test Database::loadExtension()
|
// TODO: test Database::loadExtension()
|
||||||
|
|
||||||
|
#ifdef SQLITE_HAS_CODEC
|
||||||
|
TEST(Database, encryptAndDecrypt) {
|
||||||
|
remove("test.db3");
|
||||||
|
{
|
||||||
|
// Try to open the non-existing database
|
||||||
|
EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
|
||||||
|
EXPECT_FALSE(db.tableExists("test"));
|
||||||
|
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)");
|
||||||
|
EXPECT_TRUE(db.tableExists("test"));
|
||||||
|
} // Close DB test.db3
|
||||||
|
{
|
||||||
|
// Reopen the database file and encrypt it
|
||||||
|
EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3"));
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE);
|
||||||
|
// Encrypt the database
|
||||||
|
db.rekey("123secret");
|
||||||
|
} // Close DB test.db3
|
||||||
|
{
|
||||||
|
// Reopen the database file and try to use it
|
||||||
|
EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3"));
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READONLY);
|
||||||
|
EXPECT_THROW(db.tableExists("test"), SQLite::Exception);
|
||||||
|
db.key("123secret");
|
||||||
|
EXPECT_TRUE(db.tableExists("test"));
|
||||||
|
} // Close DB test.db3
|
||||||
|
{
|
||||||
|
// Reopen the database file and decrypt it
|
||||||
|
EXPECT_FALSE(SQLite::Database::isUnencrypted("test.db3"));
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE);
|
||||||
|
// Decrypt the database
|
||||||
|
db.key("123secret");
|
||||||
|
db.rekey("");
|
||||||
|
} // Close DB test.db3
|
||||||
|
{
|
||||||
|
// Reopen the database file and use it
|
||||||
|
EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3"));
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE);
|
||||||
|
EXPECT_TRUE(db.tableExists("test"));
|
||||||
|
} // Close DB test.db3
|
||||||
|
remove("test.db3");
|
||||||
|
}
|
||||||
|
#else // SQLITE_HAS_CODEC
|
||||||
|
TEST(Database, encryptAndDecrypt) {
|
||||||
|
remove("test.db3");
|
||||||
|
{
|
||||||
|
// Try to open the non-existing database
|
||||||
|
EXPECT_THROW(SQLite::Database not_found("test.db3"), SQLite::Exception);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
|
||||||
|
EXPECT_FALSE(db.tableExists("test"));
|
||||||
|
db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)");
|
||||||
|
EXPECT_TRUE(db.tableExists("test"));
|
||||||
|
} // Close DB test.db3
|
||||||
|
{
|
||||||
|
// Reopen the database file and encrypt it
|
||||||
|
EXPECT_TRUE(SQLite::Database::isUnencrypted("test.db3"));
|
||||||
|
SQLite::Database db("test.db3", SQLite::OPEN_READWRITE);
|
||||||
|
// Encrypt the database
|
||||||
|
EXPECT_THROW(db.key("123secret"), SQLite::Exception);
|
||||||
|
EXPECT_THROW(db.rekey("123secret"), SQLite::Exception);
|
||||||
|
} // Close DB test.db3
|
||||||
|
remove("test.db3");
|
||||||
|
}
|
||||||
|
#endif // SQLITE_HAS_CODEC
|
Loading…
x
Reference in New Issue
Block a user