diff options
Diffstat (limited to 'src/wrap/sqlite/codecext.cpp')
-rw-r--r-- | src/wrap/sqlite/codecext.cpp | 261 |
1 files changed, 261 insertions, 0 deletions
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 |