aboutsummaryrefslogtreecommitdiffstats
path: root/src/wrap
diff options
context:
space:
mode:
authorlloyd <[email protected]>2010-01-22 04:15:54 +0000
committerlloyd <[email protected]>2010-01-22 04:15:54 +0000
commitc9672d69689b7bdbec89ab2a9193aaca948acb76 (patch)
tree7face090ccdf6fd6ba6742d42cc8a86f1f29eef2 /src/wrap
parent59471c89f3ef3f9c65fa5f05b1a2159d5b63d200 (diff)
Add SQLite3 encryption codec, contributed by Olivier de Gaalon. Uses
Twofish in XTS mode for encryption by default, but is easily tweakable.
Diffstat (limited to 'src/wrap')
-rw-r--r--src/wrap/sqlite/codec.cpp123
-rw-r--r--src/wrap/sqlite/codec.h127
-rw-r--r--src/wrap/sqlite/codecext.cpp261
-rw-r--r--src/wrap/sqlite/readme.txt36
-rw-r--r--src/wrap/sqlite/sqlite.diff77
-rw-r--r--src/wrap/sqlite/test_sqlite.cpp101
6 files changed, 725 insertions, 0 deletions
diff --git a/src/wrap/sqlite/codec.cpp b/src/wrap/sqlite/codec.cpp
new file mode 100644
index 000000000..5dfcea82e
--- /dev/null
+++ b/src/wrap/sqlite/codec.cpp
@@ -0,0 +1,123 @@
+/*
+ * Codec class for SQLite3 encryption codec.
+ * (C) 2010 Olivier de Gaalon
+ *
+ * Distributed under the terms of the Botan license
+ */
+
+#include "codec.h"
+#include <botan/init.h>
+
+Codec::Codec(void *db)
+{
+ InitializeCodec(db);
+}
+
+Codec::Codec(const Codec& other, void *db)
+{
+ //Only used to copy main db key for an attached db
+ InitializeCodec(db);
+ m_hasReadKey = other.m_hasReadKey;
+ m_hasWriteKey = other.m_hasWriteKey;
+ m_readKey = other.m_readKey;
+ m_ivReadKey = other.m_ivReadKey;
+ m_writeKey = other.m_writeKey;
+ m_ivWriteKey = other.m_ivWriteKey;
+}
+
+void
+Codec::InitializeCodec(void *db)
+{
+ bool botanInitialized = false;
+ Library_State* state = swap_global_state(0);
+ if(state)
+ {
+ botanInitialized = true;
+ swap_global_state(state); // should return NULL FIXME: what if not?
+ }
+
+ if (!botanInitialized)
+ LibraryInitializer::initialize();
+
+ m_hasReadKey = false;
+ m_hasWriteKey = false;
+ m_db = db;
+
+ m_encipherFilter = get_cipher(BLOCK_CIPHER_STR, ENCRYPTION);
+ m_decipherFilter = get_cipher(BLOCK_CIPHER_STR, DECRYPTION);
+ m_cmac = new MAC_Filter(MAC_STR);
+ m_encipherPipe.append(m_encipherFilter);
+ m_decipherPipe.append(m_decipherFilter);
+ m_macPipe.append(m_cmac);
+}
+
+void
+Codec::GenerateWriteKey(const char* userPassword, int passwordLength)
+{
+ S2K* s2k = get_s2k(S2K_STR);
+ s2k->set_iterations(S2K_ITERATIONS);
+ s2k->change_salt((const byte*)SALT_STR.c_str(), SALT_SIZE);
+
+ SymmetricKey masterKey =
+ s2k->derive_key(KEY_SIZE + IV_DERIVATION_KEY_SIZE, std::string(userPassword, passwordLength));
+
+ m_writeKey = SymmetricKey(masterKey.bits_of(), KEY_SIZE);
+ m_ivWriteKey = SymmetricKey(masterKey.bits_of() + KEY_SIZE, IV_DERIVATION_KEY_SIZE);
+
+ m_hasWriteKey = true;
+}
+
+void
+Codec::DropWriteKey()
+{
+ m_hasWriteKey = false;
+}
+
+void
+Codec::SetReadIsWrite()
+{
+ m_readKey = m_writeKey;
+ m_ivReadKey = m_ivWriteKey;
+ m_hasReadKey = m_hasWriteKey;
+}
+
+void
+Codec::SetWriteIsRead()
+{
+ m_writeKey = m_readKey;
+ m_ivWriteKey = m_ivReadKey;
+ m_hasWriteKey = m_hasReadKey;
+}
+
+unsigned char *
+Codec::Encrypt(int page, unsigned char* data, bool useWriteKey)
+{
+ memcpy(m_page, data, m_pageSize);
+
+ m_encipherFilter->set_key(useWriteKey ? m_writeKey : m_readKey);
+ m_encipherFilter->set_iv(GetIVForPage(page, useWriteKey));
+ m_encipherPipe.process_msg(m_page, m_pageSize);
+ m_encipherPipe.read(m_page, m_encipherPipe.remaining(Pipe::LAST_MESSAGE), Pipe::LAST_MESSAGE);
+
+ return m_page; //return location of newly ciphered data
+}
+
+void
+Codec::Decrypt(int page, unsigned char* data)
+{
+ m_decipherFilter->set_key(m_readKey);
+ m_decipherFilter->set_iv(GetIVForPage(page, false));
+ m_decipherPipe.process_msg(data, m_pageSize);
+ m_decipherPipe.read(data, m_decipherPipe.remaining(Pipe::LAST_MESSAGE), Pipe::LAST_MESSAGE);
+}
+
+InitializationVector
+Codec::GetIVForPage(u32bit page, bool useWriteKey)
+{
+ static unsigned char* intiv[4];
+ store_le(page, (byte*)intiv);
+ m_cmac->set_key(useWriteKey ? m_ivWriteKey : m_ivReadKey);
+ m_macPipe.process_msg((byte*)intiv, 4);
+ return m_macPipe.read_all(Pipe::LAST_MESSAGE);
+}
+
diff --git a/src/wrap/sqlite/codec.h b/src/wrap/sqlite/codec.h
new file mode 100644
index 000000000..8b753be62
--- /dev/null
+++ b/src/wrap/sqlite/codec.h
@@ -0,0 +1,127 @@
+/*
+ * Codec class for SQLite3 encryption codec.
+ * (C) 2010 Olivier de Gaalon
+ *
+ * Distributed under the terms of the Botan license
+ */
+
+#ifndef _CODEC_H_
+#define _CODEC_H_
+
+#include <string>
+#include <botan/botan.h>
+#include <botan/loadstor.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__BORLANDC__)
+#define __STDC__ 1
+#endif
+
+#include "./sqliteInt.h"
+
+#if defined(__BORLANDC__)
+#undef __STDC__
+#endif
+
+/* ATTENTION: Macro similar to that in pager.c
+ * Needed because pager is forward declared when needed most
+ * TODO: Check in case of new version of SQLite
+ * ... but it's VERY unlikely to change (it'd break all past DBs)
+ */
+#include "./os.h"
+#define CODEC_PAGER_MJ_PGNO(x) ((PENDING_BYTE/(x))+1)
+
+#ifdef __cplusplus
+} /* End of the 'extern "C"' block */
+#endif
+
+using namespace std;
+using namespace Botan;
+
+/*These constants can be used to tweak the codec behavior as follows
+ *Note that once you've encrypted a database with these settings,
+ *recompiling with any different settings will give you a library that
+ *cannot read that database, even given the same passphrase.*/
+
+//BLOCK_CIPHER_STR: Cipher and mode used for encrypting the database
+//make sure to add "/NoPadding" for modes that use padding schemes
+const string BLOCK_CIPHER_STR = "Twofish/XTS";
+
+//S2K_STR: Key derivation function used to derive both the encryption
+//and IV derivation keys from the given database passphrase
+const string S2K_STR = "PBKDF2(SHA-160)";
+
+//SALT_STR: Hard coded salt used to derive the key from the passphrase.
+const string SALT_STR = "&g#nB'9]";
+
+//MAC_STR: CMAC used to derive the IV that is used for db page
+//encryption
+const string MAC_STR = "CMAC(Twofish)";
+
+//S2K_ITERATIONS: Number of hash iterations used in the key derivation
+//process.
+const int S2K_ITERATIONS = 10000;
+
+//SALT_SIZE: Size of the salt in bytes (as given in SALT_STR)
+const int SALT_SIZE = 64/8; //64 bit, 8 byte salt
+
+//KEY_SIZE: Size of the encryption key. Note that XTS splits the key
+//between two ciphers, so if you're using XTS, double the intended key
+//size. (ie, "AES-128/XTS" should have a 256 bit KEY_SIZE)
+const int KEY_SIZE = 512/8; //512 bit, 64 byte key. (256 bit XTS key)
+
+//IV_DERIVATION_KEY_SIZE: Size of the key used with the CMAC (MAC_STR)
+//above.
+const int IV_DERIVATION_KEY_SIZE = 256/8; //256 bit, 32 byte key
+
+class Codec
+{
+public:
+ Codec(void *db);
+ Codec(const Codec& other, void *db);
+
+ void GenerateWriteKey(const char* userPassword, int passwordLength);
+ void DropWriteKey();
+ void SetWriteIsRead();
+ void SetReadIsWrite();
+
+ unsigned char* Encrypt(int page, unsigned char* data, bool useWriteKey);
+ void Decrypt(int page, unsigned char* data);
+
+ void SetPageSize(int pageSize) { m_pageSize = pageSize; }
+
+ bool HasReadKey() { return m_hasReadKey; }
+ bool HasWriteKey() { return m_hasWriteKey; }
+ void* GetDB() { return m_db; }
+
+private:
+ bool m_hasReadKey;
+ bool m_hasWriteKey;
+
+ SymmetricKey
+ m_readKey,
+ m_writeKey,
+ m_ivReadKey,
+ m_ivWriteKey;
+
+ Pipe
+ m_encipherPipe,
+ m_decipherPipe,
+ m_macPipe;
+
+ Keyed_Filter *m_encipherFilter;
+ Keyed_Filter *m_decipherFilter;
+ MAC_Filter *m_cmac;
+
+ int m_pageSize;
+ unsigned char m_page[SQLITE_MAX_PAGE_SIZE];
+ void* m_db;
+
+ InitializationVector GetIVForPage(u32bit page, bool useWriteKey);
+ void InitializeCodec(void *db);
+};
+
+#endif
diff --git a/src/wrap/sqlite/codecext.cpp b/src/wrap/sqlite/codecext.cpp
new file mode 100644
index 000000000..e542df975
--- /dev/null
+++ b/src/wrap/sqlite/codecext.cpp
@@ -0,0 +1,261 @@
+/*
+ * SQLite3 encryption extention codec
+ * (C) 2010 Olivier de Gaalon
+ *
+ * Distributed under the terms of the Botan license
+ */
+
+#ifndef SQLITE_OMIT_DISKIO
+#ifdef SQLITE_HAS_CODEC
+
+#include "codec.h"
+#include "sqlite3.h"
+
+// Required to implement, called from pragma.c, guessing that "see" is related to the
+// "SQLite Encryption Extension" (the semi-official, for-pay, encryption codec)
+extern "C"
+void sqlite3_activate_see(const char *info) { }
+
+// Free the encryption codec, called from pager.c (address passed in sqlite3PagerSetCodec)
+extern "C"
+void sqlite3PagerFreeCodec(void *pCodec)
+{
+ if (pCodec)
+ delete (Codec*) pCodec;
+}
+
+// Report the page size to the codec, called from pager.c (address passed in sqlite3PagerSetCodec)
+extern "C"
+void sqlite3CodecSizeChange(void *pCodec, int pageSize, int nReserve)
+{
+ Codec* codec = (Codec*) pCodec;
+ codec->SetPageSize(pageSize);
+}
+
+// Encrypt/Decrypt functionality, called by pager.c
+extern "C"
+void* sqlite3Codec(void* pCodec, void* data, Pgno nPageNum, int nMode)
+{
+ if (pCodec == NULL) //Db not encrypted
+ return data;
+
+ Codec* codec = (Codec*) pCodec;
+
+ try
+ {
+ switch(nMode)
+ {
+ case 0: // Undo a "case 7" journal file encryption
+ case 2: // Reload a page
+ case 3: // Load a page
+ if (codec->HasReadKey())
+ codec->Decrypt(nPageNum, (unsigned char*) data);
+ break;
+ case 6: // Encrypt a page for the main database file
+ if (codec->HasWriteKey())
+ data = codec->Encrypt(nPageNum, (unsigned char*) data, true);
+ break;
+ case 7: // Encrypt a page for the journal file
+ /*
+ *Under normal circumstances, the readkey is the same as the writekey. However,
+ *when the database is being rekeyed, the readkey is not the same as the writekey.
+ *(The writekey is the "destination key" for the rekey operation and the readkey
+ *is the key the db is currently encrypted with)
+ *Therefore, for case 7, when the rollback is being written, always encrypt using
+ *the database's readkey, which is guaranteed to be the same key that was used to
+ *read and write the original data.
+ */
+ if (codec->HasReadKey())
+ data = codec->Encrypt(nPageNum, (unsigned char*) data, false);
+ break;
+ }
+ }
+ catch(Botan::Exception e)
+ {
+ sqlite3Error((sqlite3*)codec->GetDB(), SQLITE_ERROR, "Botan Error: %s", e.what());
+ }
+
+ return data;
+}
+
+//These functions are defined in pager.c
+extern "C" void* sqlite3PagerGetCodec(Pager *pPager);
+extern "C" void sqlite3PagerSetCodec(
+ Pager *pPager,
+ void *(*xCodec)(void*,void*,Pgno,int),
+ void (*xCodecSizeChng)(void*,int,int),
+ void (*xCodecFree)(void*),
+ void *pCodec
+);
+
+
+extern "C"
+int sqlite3CodecAttach(sqlite3* db, int nDb, const void* zKey, int nKey)
+{
+ try {
+ if (zKey == NULL || nKey <= 0)
+ {
+ // No key specified, could mean either use the main db's encryption or no encryption
+ if (nDb != 0 && nKey < 0)
+ {
+ //Is an attached database, therefore use the key of main database, if main database is encrypted
+ Codec* mainCodec = (Codec*) sqlite3PagerGetCodec(sqlite3BtreePager(db->aDb[0].pBt));
+ if (mainCodec != NULL)
+ {
+ Codec* codec = new Codec(*mainCodec, db);
+ sqlite3PagerSetCodec(sqlite3BtreePager(db->aDb[nDb].pBt),
+ sqlite3Codec,
+ sqlite3CodecSizeChange,
+ sqlite3PagerFreeCodec, codec);
+ }
+ }
+ }
+ else
+ {
+ // Key specified, setup encryption key for database
+ Codec* codec = new Codec(db);
+ codec->GenerateWriteKey((const char*) zKey, nKey);
+ codec->SetReadIsWrite();
+ sqlite3PagerSetCodec(sqlite3BtreePager(db->aDb[nDb].pBt),
+ sqlite3Codec,
+ sqlite3CodecSizeChange,
+ sqlite3PagerFreeCodec, codec);
+ }
+ }
+ catch(Botan::Exception e) {
+ sqlite3Error(db, SQLITE_ERROR, "Botan Error: %s", e.what());
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
+extern "C"
+void sqlite3CodecGetKey(sqlite3* db, int nDb, void** zKey, int* nKey)
+{
+ // The unencrypted password is not stored for security reasons
+ // therefore always return NULL
+ *zKey = NULL;
+ *nKey = -1;
+}
+
+extern "C"
+int sqlite3_key(sqlite3 *db, const void *zKey, int nKey)
+{
+ // The key is only set for the main database, not the temp database
+ return sqlite3CodecAttach(db, 0, zKey, nKey);
+}
+
+extern "C"
+int sqlite3_rekey(sqlite3 *db, const void *zKey, int nKey)
+{
+ // Changes the encryption key for an existing database.
+ int rc = SQLITE_ERROR;
+ Btree* pbt = db->aDb[0].pBt;
+ Pager* pPager = sqlite3BtreePager(pbt);
+ Codec* codec = (Codec*) sqlite3PagerGetCodec(pPager);
+
+ if ((zKey == NULL || nKey == 0) && codec == NULL)
+ {
+ // Database not encrypted and key not specified. Do nothing
+ return SQLITE_OK;
+ }
+
+ if (codec == NULL)
+ {
+ // Database not encrypted, but key specified. Encrypt database
+ try {
+ codec = new Codec(db);
+ codec->GenerateWriteKey((const char*) zKey, nKey);
+ } catch (Botan::Exception e) {
+ sqlite3Error(db, SQLITE_ERROR, "Botan Error %s", e.what());
+ return SQLITE_ERROR;
+ }
+ sqlite3PagerSetCodec(pPager, sqlite3Codec, sqlite3CodecSizeChange, sqlite3PagerFreeCodec, codec);
+ }
+ else if (zKey == NULL || nKey == 0)
+ {
+ // Database encrypted, but key not specified. Decrypt database
+ // Keep read key, drop write key
+ codec->DropWriteKey();
+ }
+ else
+ {
+ // Database encrypted and key specified. Re-encrypt database with new key
+ // Keep read key, change write key to new key
+ try {
+ codec->GenerateWriteKey((const char*) zKey, nKey);
+ } catch (Botan::Exception e) {
+ sqlite3Error(db, SQLITE_ERROR, "Botan Error %s", e.what());
+ return SQLITE_ERROR;
+ }
+ }
+
+ // Start transaction
+ rc = sqlite3BtreeBeginTrans(pbt, 1);
+ if (rc == SQLITE_OK)
+ {
+ // Rewrite all pages using the new encryption key (if specified)
+ int nPageCount = -1;
+ int rc = sqlite3PagerPagecount(pPager, &nPageCount);
+ Pgno nPage = (Pgno) nPageCount;
+ int pageSize = sqlite3BtreeGetPageSize(pbt);
+ //Can't use SQLite3 macro here since pager is forward declared...sigh
+ Pgno nSkip = CODEC_PAGER_MJ_PGNO(pageSize);
+ DbPage *pPage;
+
+ for (Pgno n = 1; rc == SQLITE_OK && n <= nPage; n++)
+ {
+ if (n == nSkip)
+ continue;
+
+ rc = sqlite3PagerGet(pPager, n, &pPage);
+
+ if (!rc)
+ {
+ rc = sqlite3PagerWrite(pPage);
+ sqlite3PagerUnref(pPage);
+ }
+ else
+ sqlite3Error(db, SQLITE_ERROR, "%s", "Error while rekeying database page. Transaction Canceled.");
+ }
+ }
+ else
+ sqlite3Error(db, SQLITE_ERROR, "%s", "Error beginning rekey transaction. Make sure that the current encryption key is correct.");
+
+ if (rc == SQLITE_OK)
+ {
+ // All good, commit
+ rc = sqlite3BtreeCommit(pbt);
+
+ if (rc == SQLITE_OK)
+ {
+ //Database rekeyed and committed successfully, update read key
+ if (codec->HasWriteKey())
+ codec->SetReadIsWrite();
+ else //No write key == no longer encrypted
+ sqlite3PagerSetCodec(pPager, NULL, NULL, NULL, NULL);
+ }
+ else
+ {
+ //FIXME: can't trigger this, not sure if rollback is needed, reference implementation didn't rollback
+ sqlite3Error(db, SQLITE_ERROR, "%s", "Could not commit rekey transaction.");
+ }
+ }
+ else
+ {
+ // Rollback, rekey failed
+ sqlite3BtreeRollback(pbt);
+
+ // go back to read key
+ if (codec->HasReadKey())
+ codec->SetWriteIsRead();
+ else //Database wasn't encrypted to start with
+ sqlite3PagerSetCodec(pPager, NULL, NULL, NULL, NULL);
+ }
+
+ return rc;
+}
+
+#endif // SQLITE_HAS_CODEC
+
+#endif // SQLITE_OMIT_DISKIO
diff --git a/src/wrap/sqlite/readme.txt b/src/wrap/sqlite/readme.txt
new file mode 100644
index 000000000..e131f9e91
--- /dev/null
+++ b/src/wrap/sqlite/readme.txt
@@ -0,0 +1,36 @@
+Build instructions for Botan SQLite3 codec
+---
+
+1. Requires Botan 1.8.8 or later (earlier versions OK if you switch to
+ CBC mode from XTS)
+
+2. Download SQLite3 version 3.6.17 or later, get the version "as
+ extracted from the source control system", NOT the amalgamation.
+
+3. Apply the patch "sqlite.diff" [*]:
+ $ patch -p0 < ../sqlite.diff
+ patching file Makefile.in
+ patching file src/pager.c
+
+ If the patch to pager.c fails for some reason (ie, changes in
+ SQLite3), all that need be done is remove the "static" keyword from
+ the functions sqlite3PagerSetCodec and sqlite3PagerGetCodec.
+
+5. Create a folder called "botan" in the SQLite3 src dir and copy
+ "codec.cpp", "codec.h", and "codecext.cpp" into it.
+
+6. As desired, edit the constants in codec.h to tweak the encryption
+ type to your needs. (Currently, Twofish/XTS with 256 bit key)
+
+7. Run ./configure in the SQLite3 root directory with the
+ "--disable-amalgamation" and (if desired) "--disable-shared"
+ arguments, and then run make.
+
+And to make sure it all worked...
+
+8. Make the test_sqlite.cpp file:
+ $ g++ test_sqlite.cpp -o test_sqlite -lbotan /path/to/libsqlite3.a
+9. Run it
+ $ ./test_sqlite
+10. Look for "All seems good"
+
diff --git a/src/wrap/sqlite/sqlite.diff b/src/wrap/sqlite/sqlite.diff
new file mode 100644
index 000000000..1a3ef764c
--- /dev/null
+++ b/src/wrap/sqlite/sqlite.diff
@@ -0,0 +1,77 @@
+--- Makefile.in.orig 2009-10-30 09:34:59.000000000 -0400
++++ Makefile.in 2010-01-21 22:51:22.000000000 -0500
+@@ -133,7 +133,10 @@
+ GCOV_LDFLAGS1 = -lgcov
+ USE_GCOV = @USE_GCOV@
+ LTCOMPILE_EXTRAS += $(GCOV_CFLAGS$(USE_GCOV))
++LTCOMPILE_EXTRAS += -DSQLITE_HAS_CODEC
+ LTLINK_EXTRAS += $(GCOV_LDFLAGS$(USE_GCOV))
++LTLINK_EXTRAS += -lstdc++
++LTLINK_EXTRAS += -lbotan
+
+
+ # The directory into which to store package information for
+@@ -176,7 +179,8 @@
+ table.lo tokenize.lo trigger.lo update.lo \
+ util.lo vacuum.lo \
+ vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo \
+- walker.lo where.lo utf.lo vtab.lo
++ walker.lo where.lo utf.lo vtab.lo \
++ codec.lo codecext.lo
+
+ # Object files for the amalgamation.
+ #
+@@ -275,7 +279,10 @@
+ $(TOP)/src/vdbeInt.h \
+ $(TOP)/src/vtab.c \
+ $(TOP)/src/walker.c \
+- $(TOP)/src/where.c
++ $(TOP)/src/where.c \
++ $(TOP)/src/botan/codec.cpp \
++ $(TOP)/src/botan/codecext.cpp \
++ $(TOP)/src/botan/codec.h
+
+ # Generated source code files
+ #
+@@ -411,6 +418,7 @@
+ $(TOP)/src/sqlite3ext.h \
+ $(TOP)/src/sqliteInt.h \
+ $(TOP)/src/vdbe.h \
++ $(TOP)/src/botan/codec.h \
+ $(TOP)/src/vdbeInt.h \
+ parse.h \
+ config.h
+@@ -622,6 +630,13 @@
+ notify.lo: $(TOP)/src/notify.c $(HDR)
+ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/notify.c
+
++codec.lo: $(TOP)/src/botan/codec.cpp $(HDR) $(TOP)/src/botan/codec.h
++ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/botan/codec.cpp
++
++codecext.lo: $(TOP)/src/botan/codecext.cpp $(HDR) $(TOP)/src/botan/codec.h
++ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/botan/codecext.cpp
++
++
+ pager.lo: $(TOP)/src/pager.c $(HDR) $(TOP)/src/pager.h
+ $(LTCOMPILE) $(TEMP_STORE) -c $(TOP)/src/pager.c
+
+--- src/pager.c.orig 2009-10-30 17:01:07.000000000 -0400
++++ src/pager.c 2010-01-21 22:51:53.000000000 -0500
+@@ -5046,7 +5046,7 @@
+ /*
+ ** Set or retrieve the codec for this pager
+ */
+-static void sqlite3PagerSetCodec(
++void sqlite3PagerSetCodec(
+ Pager *pPager,
+ void *(*xCodec)(void*,void*,Pgno,int),
+ void (*xCodecSizeChng)(void*,int,int),
+@@ -5060,7 +5060,7 @@
+ pPager->pCodec = pCodec;
+ pagerReportSize(pPager);
+ }
+-static void *sqlite3PagerGetCodec(Pager *pPager){
++void *sqlite3PagerGetCodec(Pager *pPager){
+ return pPager->pCodec;
+ }
+ #endif
diff --git a/src/wrap/sqlite/test_sqlite.cpp b/src/wrap/sqlite/test_sqlite.cpp
new file mode 100644
index 000000000..8d8cad946
--- /dev/null
+++ b/src/wrap/sqlite/test_sqlite.cpp
@@ -0,0 +1,101 @@
+/*
+ * Quick and dirty test for SQLite3 encryption codec.
+ * (C) 2010 Olivier de Gaalon
+ *
+ * Distributed under the terms of the Botan license
+ */
+
+#include <sqlite3.h>
+#include <stdio.h>
+
+namespace SQL
+{
+ const char * CREATE_TABLE_TEST =
+ "create table 'test' (id INTEGER PRIMARY KEY, name TEXT, creationtime TEXT);";
+ const char * CREATE_TABLE_TEST2 =
+ "create table 'test2' (id INTEGER PRIMARY KEY, name TEXT, creationtime TEXT);";
+ const char * INSERT_INTO_TEST =
+ "INSERT INTO test (name, creationtime) VALUES ('widget', '1st time');\
+ INSERT INTO test (name, creationtime) VALUES ('widget', '2nd time');\
+ INSERT INTO test (name, creationtime) VALUES ('widget', '3rd time');\
+ INSERT INTO test (name, creationtime) VALUES ('widget', '4th time');\
+ INSERT INTO test (name, creationtime) VALUES ('widget', '5th time');";
+ const char * INSERT_INTO_TEST2 =
+ "INSERT INTO test2 (name, creationtime) VALUES ('widget2', '1st time2');\
+ INSERT INTO test2 (name, creationtime) VALUES ('widget2', '2nd time2');\
+ INSERT INTO test2 (name, creationtime) VALUES ('widget2', '3rd time2');\
+ INSERT INTO test2 (name, creationtime) VALUES ('widget2', '4th time2');\
+ INSERT INTO test2 (name, creationtime) VALUES ('widget2', '5th time2');";
+ const char * SELECT_FROM_TEST =
+ "SELECT * FROM test;";
+ const char * SELECT_FROM_TEST2 =
+ "SELECT * FROM test2;";
+};
+
+static int callback(void *NotUsed, int argc, char **argv, char **azColName){
+ int i;
+ fprintf(stderr, "\t");
+ for(i=0; i<argc; i++){
+ fprintf(stderr, "%s = %s | ", azColName[i], argv[i] ? argv[i] : "NULL");
+ }
+ fprintf(stderr, "\n");
+ return 0;
+}
+
+int main(int argc, char** argv)
+{
+ sqlite3 * db;
+ const char * key = "testkey";
+ const char * dbname = "./testdb";
+ int keylen = 7;
+ char * error=0;
+
+ fprintf(stderr, "Creating Database \"%s\"\n", dbname);
+ int rc = sqlite3_open(dbname, &db);
+ if (rc != SQLITE_OK) { fprintf(stderr, "Can't open/create database: %s\n", sqlite3_errmsg(db)); return 1; }
+
+ fprintf(stderr, "Keying Database with key \"%s\"\n", key);
+ rc = sqlite3_key(db, key, keylen);
+ if (rc != SQLITE_OK) { fprintf(stderr, "Can't key database: %s\n", sqlite3_errmsg(db)); return 1; }
+
+ fprintf(stderr, "Creating table \"test\"\n");
+ rc = sqlite3_exec(db, SQL::CREATE_TABLE_TEST, 0, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Creating table \"test2\"\n");
+ rc = sqlite3_exec(db, SQL::CREATE_TABLE_TEST2, 0, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Inserting into table \"test\"\n");
+ rc = sqlite3_exec(db, SQL::INSERT_INTO_TEST, 0, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Inserting into table \"test2\"\n");
+ rc = sqlite3_exec(db, SQL::INSERT_INTO_TEST2, 0, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Closing Database \"%s\"\n", dbname);
+ sqlite3_close(db);
+
+ fprintf(stderr, "Opening Database \"%s\"\n", dbname);
+ rc = sqlite3_open(dbname, &db);
+ if (rc != SQLITE_OK) { fprintf(stderr, "Can't open/create database: %s\n", sqlite3_errmsg(db)); return 1; }
+
+ fprintf(stderr, "Keying Database with key \"%s\"\n", key);
+ rc = sqlite3_key(db, key, keylen);
+ if (rc != SQLITE_OK) { fprintf(stderr, "Can't key database: %s\n", sqlite3_errmsg(db)); return 1; }
+
+ fprintf(stderr, "Selecting all from test\n");
+ rc = sqlite3_exec(db, SQL::SELECT_FROM_TEST, callback, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Selecting all from test2\n");
+ rc = sqlite3_exec(db, SQL::SELECT_FROM_TEST2, callback, 0, &error);
+ if (rc != SQLITE_OK) { fprintf(stderr, "SQL error: %s\n", error); return 1; }
+
+ fprintf(stderr, "Closing Database \"%s\"\n", dbname);
+ sqlite3_close(db);
+
+ fprintf(stderr, "All Seems Good \n");
+ return 0;
+}