int libsql_db_has_codec(sqlite3_vfs* pVfs, const char* zFilename);
SQLITE_PRIVATE int
sqlite3mcBtreeSetPageSize(Btree* p, int pageSize, int nReserve, int iFix)
{
int rc = SQLITE_OK;
int x;
BtShared* pBt = p->pBt;
assert(nReserve >= 0 && nReserve <= 255);
sqlite3BtreeEnter(p);
pBt->nReserveWanted = nReserve;
x = pBt->pageSize - pBt->usableSize;
if (nReserve < 0) nReserve = x;
if (pBt->btsFlags & BTS_PAGESIZE_FIXED)
{
sqlite3BtreeLeave(p);
return SQLITE_READONLY;
}
assert(nReserve >= 0 && nReserve <= 255);
if (pageSize >= 512 && pageSize <= SQLITE_MAX_PAGE_SIZE &&
((pageSize - 1) & pageSize) == 0)
{
assert((pageSize & 7) == 0);
assert(!pBt->pCursor);
pBt->pageSize = (u32)pageSize;
freeTempSpace(pBt);
}
rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, nReserve);
pBt->usableSize = pBt->pageSize - (u16)nReserve;
if (iFix) pBt->btsFlags |= BTS_PAGESIZE_FIXED;
sqlite3BtreeLeave(p);
return rc;
}
#include "rekeyvacuum.c"
#include "cipher_common.h"
SQLITE_API void
sqlite3_activate_see(const char *info)
{
}
SQLITE_PRIVATE void
sqlite3mcCodecFree(void *pCodecArg)
{
if (pCodecArg)
{
sqlite3mcCodecTerm(pCodecArg);
sqlite3_free(pCodecArg);
pCodecArg = NULL;
}
}
SQLITE_PRIVATE void
sqlite3mcCodecSizeChange(void *pArg, int pageSize, int reservedSize)
{
Codec* pCodec = (Codec*) pArg;
pCodec->m_pageSize = pageSize;
pCodec->m_reserved = reservedSize;
}
static void
mcReportCodecError(BtShared* pBt, int error)
{
pBt->db->errCode = error;
pBt->pPager->errCode = error;
if (error != SQLITE_OK)
{
pBt->pPager->eState = PAGER_ERROR;
}
setGetterMethod(pBt->pPager);
if (error == SQLITE_OK)
{
sqlite3PagerClearCache(pBt->pPager);
}
}
SQLITE_PRIVATE void*
sqlite3mcCodec(void* pCodecArg, void* data, Pgno nPageNum, int nMode)
{
int rc = SQLITE_OK;
Codec* codec = NULL;
int pageSize;
if (pCodecArg == NULL)
{
return data;
}
codec = (Codec*) pCodecArg;
if (!sqlite3mcIsEncrypted(codec))
{
return data;
}
pageSize = sqlite3mcGetPageSize(codec);
switch(nMode)
{
case 0:
case 2:
case 3:
if (sqlite3mcHasReadCipher(codec))
{
rc = sqlite3mcDecrypt(codec, nPageNum, (unsigned char*) data, pageSize);
if (rc != SQLITE_OK) mcReportCodecError(sqlite3mcGetBtShared(codec), rc);
}
break;
case 6:
if (sqlite3mcHasWriteCipher(codec))
{
unsigned char* pageBuffer = sqlite3mcGetPageBuffer(codec);
memcpy(pageBuffer, data, pageSize);
data = pageBuffer;
rc = sqlite3mcEncrypt(codec, nPageNum, (unsigned char*) data, pageSize, 1);
if (rc != SQLITE_OK) mcReportCodecError(sqlite3mcGetBtShared(codec), rc);
}
break;
case 7:
if (sqlite3mcHasReadCipher(codec))
{
unsigned char* pageBuffer = sqlite3mcGetPageBuffer(codec);
memcpy(pageBuffer, data, pageSize);
data = pageBuffer;
rc = sqlite3mcEncrypt(codec, nPageNum, (unsigned char*) data, pageSize, 0);
if (rc != SQLITE_OK) mcReportCodecError(sqlite3mcGetBtShared(codec), rc);
}
break;
}
return data;
}
SQLITE_PRIVATE Codec*
sqlite3mcGetMainCodec(sqlite3* db);
SQLITE_PRIVATE void
sqlite3mcSetCodec(sqlite3* db, const char* zDbName, const char* zFileName, Codec* codec);
static int
mcAdjustBtree(Btree* pBt, int nPageSize, int nReserved, int isLegacy)
{
int rc = SQLITE_OK;
Pager* pager = sqlite3BtreePager(pBt);
int pagesize = sqlite3BtreeGetPageSize(pBt);
sqlite3BtreeSecureDelete(pBt, 1);
if (nPageSize > 0)
{
pagesize = nPageSize;
}
if (pager->pageSize != pagesize || pager->nReserve != nReserved)
{
if (isLegacy != 0)
{
pBt->pBt->btsFlags &= ~BTS_PAGESIZE_FIXED;
}
rc = sqlite3BtreeSetPageSize(pBt, pagesize, nReserved, 0);
}
return rc;
}
static int
sqlite3mcCodecAttach(sqlite3* db, int nDb, const char* zPath, const void* zKey, int nKey)
{
const char* zDbName = db->aDb[nDb].zDbSName;
const char* dbFileName = sqlite3_db_filename(db, zDbName);
Codec* codec = (Codec*) sqlite3_malloc(sizeof(Codec));
int rc = (codec != NULL) ? sqlite3mcCodecInit(codec) : SQLITE_NOMEM;
if (rc != SQLITE_OK)
{
return rc;
}
sqlite3_mutex_enter(db->mutex);
sqlite3mcSetDb(codec, db);
if (zKey == NULL || nKey <= 0)
{
if (nDb != 0 && nKey > 0)
{
Codec* mainCodec = sqlite3mcGetMainCodec(db);
if (mainCodec != NULL && sqlite3mcIsEncrypted(mainCodec))
{
rc = sqlite3mcCodecCopy(codec, mainCodec);
if (rc == SQLITE_OK)
{
int pageSize = sqlite3mcGetPageSizeWriteCipher(codec);
int reserved = sqlite3mcGetReservedWriteCipher(codec);
sqlite3mcSetBtree(codec, db->aDb[nDb].pBt);
mcAdjustBtree(db->aDb[nDb].pBt, pageSize, reserved, sqlite3mcGetLegacyWriteCipher(codec));
sqlite3mcCodecSizeChange(codec, pageSize, reserved);
sqlite3mcSetCodec(db, zDbName, dbFileName, codec);
}
else
{
sqlite3mcCodecFree(codec);
}
}
else
{
sqlite3mcCodecFree(codec);
}
}
else
{
sqlite3mcCodecFree(codec);
if (nDb == 0 && nKey == 0)
{
sqlite3mcSetCodec(db, zDbName, dbFileName, NULL);
}
}
}
else
{
if (dbFileName != NULL)
{
const unsigned char* cipherSalt = (const unsigned char*)sqlite3_uri_parameter(dbFileName, "cipher_salt");
if ((cipherSalt != NULL) && (strlen((const char*)cipherSalt) >= 2 * KEYSALT_LENGTH) && sqlite3mcIsHexKey(cipherSalt, 2 * KEYSALT_LENGTH))
{
codec->m_hasKeySalt = 1;
sqlite3mcConvertHex2Bin(cipherSalt, 2 * KEYSALT_LENGTH, codec->m_keySalt);
}
}
if (nDb > 0)
{
rc = sqlite3mcConfigureFromUri(db, dbFileName, 0);
}
if (rc == SQLITE_OK)
{
sqlite3mcSetBtree(codec, db->aDb[nDb].pBt);
rc = sqlite3mcCodecSetup(codec, sqlite3mcGetCipherType(db), (char*) zKey, nKey);
sqlite3mcClearKeySalt(codec);
}
if (rc == SQLITE_OK)
{
int pageSize = sqlite3mcGetPageSizeWriteCipher(codec);
int reserved = sqlite3mcGetReservedWriteCipher(codec);
mcAdjustBtree(db->aDb[nDb].pBt, pageSize, reserved, sqlite3mcGetLegacyWriteCipher(codec));
sqlite3mcCodecSizeChange(codec, pageSize, reserved);
sqlite3mcSetCodec(db, zDbName, dbFileName, codec);
}
else
{
sqlite3mcCodecFree(codec);
}
}
sqlite3_mutex_leave(db->mutex);
return rc;
}
SQLITE_PRIVATE void
sqlite3mcCodecGetKey(sqlite3* db, int nDb, void** zKey, int* nKey)
{
Codec* codec = sqlite3mcGetCodec(db, db->aDb[nDb].zDbSName);
int keylen = (codec != NULL && sqlite3mcIsEncrypted(codec)) ? 1 : 0;
*zKey = NULL;
*nKey = keylen;
}
SQLITE_API int
sqlite3_key(sqlite3 *db, const void *zKey, int nKey)
{
return sqlite3_key_v2(db, "main", zKey, nKey);
}
SQLITE_API int
sqlite3_key_v2(sqlite3* db, const char* zDbName, const void* zKey, int nKey)
{
int rc = SQLITE_ERROR;
if (zKey != NULL && nKey < 0)
{
nKey = sqlite3Strlen30((const char*) zKey);
}
if ((db != NULL) && (zKey != NULL) && (nKey >= 0))
{
int dbIndex;
const char* dbFileName = sqlite3_db_filename(db, zDbName);
if (dbFileName == NULL || dbFileName[0] == 0)
{
sqlite3ErrorWithMsg(db, rc, "Setting key not supported for in-memory or temporary databases.");
return rc;
}
if (sqlite3FindFunction(db, "sqlite3mc_config_table", 0, SQLITE_UTF8, 0) == NULL)
{
rc = sqlite3mcConfigureFromUri(db, dbFileName, 0);
}
dbIndex = (zDbName) ? sqlite3FindDbName(db, zDbName) : 0;
if (dbIndex >= 0)
{
rc = sqlite3mcCodecAttach(db, dbIndex, dbFileName, zKey, nKey);
}
else
{
rc = SQLITE_ERROR;
sqlite3ErrorWithMsg(db, rc, "Setting key failed. Database '%s' not found.", zDbName);
}
}
return rc;
}
SQLITE_API int
sqlite3_rekey_v2(sqlite3* db, const char* zDbName, const void* zKey, int nKey)
{
const char* dbFileName;
int dbIndex;
Btree* pBt;
int nPagesize;
int nReserved;
Pager* pPager;
Codec* codec;
int rc = SQLITE_ERROR;
if (zKey != NULL && nKey < 0)
{
nKey = sqlite3Strlen30((const char*) zKey);
}
dbFileName = sqlite3_db_filename(db, zDbName);
dbIndex = (zDbName) ? sqlite3FindDbName(db, zDbName) : 0;
if (dbIndex < 0)
{
sqlite3ErrorWithMsg(db, rc, "Rekeying failed. Database '%s' not found.", zDbName);
return rc;
}
if (dbFileName == NULL || dbFileName[0] == 0)
{
sqlite3ErrorWithMsg(db, rc, "Rekeying not supported for in-memory or temporary databases.");
return rc;
}
pBt = db->aDb[dbIndex].pBt;
nPagesize = sqlite3BtreeGetPageSize(pBt);
sqlite3BtreeEnter(pBt);
nReserved = sqlite3BtreeGetReserveNoMutex(pBt);
sqlite3BtreeLeave(pBt);
pPager = sqlite3BtreePager(pBt);
codec = sqlite3mcGetCodec(db, zDbName);
if (pagerUseWal(pPager))
{
sqlite3ErrorWithMsg(db, rc, "Rekeying is not supported in WAL journal mode.");
return rc;
}
if ((zKey == NULL || nKey == 0) && (codec == NULL || !sqlite3mcIsEncrypted(codec)))
{
return SQLITE_OK;
}
sqlite3_mutex_enter(db->mutex);
if (codec == NULL || !sqlite3mcIsEncrypted(codec))
{
if (codec == NULL)
{
codec = (Codec*) sqlite3_malloc(sizeof(Codec));
rc = (codec != NULL) ? sqlite3mcCodecInit(codec) : SQLITE_NOMEM;
}
if (rc == SQLITE_OK)
{
sqlite3mcSetDb(codec, db);
sqlite3mcSetBtree(codec, pBt);
rc = sqlite3mcSetupWriteCipher(codec, sqlite3mcGetCipherType(db), (char*) zKey, nKey);
}
if (rc == SQLITE_OK)
{
int nPagesizeWriteCipher = sqlite3mcGetPageSizeWriteCipher(codec);
if (nPagesizeWriteCipher <= 0 || nPagesize == nPagesizeWriteCipher)
{
int nReservedWriteCipher;
sqlite3mcSetHasReadCipher(codec, 0);
mcAdjustBtree(pBt, sqlite3mcGetPageSizeWriteCipher(codec), sqlite3mcGetReservedWriteCipher(codec), sqlite3mcGetLegacyWriteCipher(codec));
sqlite3mcSetCodec(db, zDbName, dbFileName, codec);
nReservedWriteCipher = sqlite3mcGetReservedWriteCipher(codec);
sqlite3mcCodecSizeChange(codec, nPagesize, nReservedWriteCipher);
if (nReserved != nReservedWriteCipher)
{
char* err = NULL;
sqlite3mcSetReadReserved(codec, nReserved);
sqlite3mcSetWriteReserved(codec, nReservedWriteCipher);
rc = sqlite3mcRunVacuumForRekey(&err, db, dbIndex, NULL, nReservedWriteCipher);
if (rc != SQLITE_OK && err != NULL)
{
sqlite3ErrorWithMsg(db, rc, err);
}
goto leave_rekey;
}
}
else
{
rc = SQLITE_ERROR;
sqlite3ErrorWithMsg(db, rc, "Rekeying failed. Pagesize cannot be changed for an encrypted database.");
goto leave_rekey;
}
}
else
{
return rc;
}
}
else if (zKey == NULL || nKey == 0)
{
sqlite3mcSetHasWriteCipher(codec, 0);
if (nReserved > 0)
{
char* err = NULL;
sqlite3mcSetReadReserved(codec, nReserved);
sqlite3mcSetWriteReserved(codec, 0);
rc = sqlite3mcRunVacuumForRekey(&err, db, dbIndex, NULL, 0);
if (rc != SQLITE_OK && err != NULL)
{
sqlite3ErrorWithMsg(db, rc, err);
}
goto leave_rekey;
}
}
else
{
rc = sqlite3mcSetupWriteCipher(codec, sqlite3mcGetCipherType(db), (char*) zKey, nKey);
if (rc == SQLITE_OK)
{
int nPagesizeWriteCipher = sqlite3mcGetPageSizeWriteCipher(codec);
if (nPagesizeWriteCipher <= 0 || nPagesize == nPagesizeWriteCipher)
{
int nReservedWriteCipher = sqlite3mcGetReservedWriteCipher(codec);
if (nReserved != nReservedWriteCipher)
{
char* err = NULL;
sqlite3mcSetReadReserved(codec, nReserved);
sqlite3mcSetWriteReserved(codec, nReservedWriteCipher);
rc = sqlite3mcRunVacuumForRekey(&err, db, dbIndex, NULL, nReservedWriteCipher);
if (rc != SQLITE_OK && err != NULL)
{
sqlite3ErrorWithMsg(db, rc, err);
}
goto leave_rekey;
}
}
else
{
rc = SQLITE_ERROR;
sqlite3ErrorWithMsg(db, rc, "Rekeying failed. Pagesize cannot be changed for an encrypted database.");
goto leave_rekey;
}
}
else
{
sqlite3ErrorWithMsg(db, rc, "Rekeying failed. Setup of write cipher failed.");
goto leave_rekey;
}
}
rc = sqlite3BtreeBeginTrans(pBt, 1, 0);
if (!rc)
{
int pageSize = sqlite3BtreeGetPageSize(pBt);
Pgno nSkip = WX_PAGER_MJ_PGNO(pageSize);
DbPage *pPage;
Pgno n;
Pgno nPage;
int nPageCount = -1;
sqlite3PagerPagecount(pPager, &nPageCount);
nPage = nPageCount;
for (n = 1; rc == SQLITE_OK && n <= nPage; n++)
{
if (n == nSkip) continue;
rc = sqlite3PagerGet(pPager, n, &pPage, 0);
if (!rc)
{
rc = sqlite3PagerWrite(pPage);
sqlite3PagerUnref(pPage);
}
}
}
if (rc == SQLITE_OK)
{
rc = sqlite3BtreeCommit(pBt);
}
if (rc != SQLITE_OK)
{
sqlite3BtreeRollback(pBt, SQLITE_OK, 0);
}
leave_rekey:
sqlite3_mutex_leave(db->mutex);
if (rc == SQLITE_OK)
{
if (sqlite3mcHasWriteCipher(codec))
{
sqlite3mcCopyCipher(codec, 0);
sqlite3mcSetHasReadCipher(codec, 1);
}
else
{
sqlite3mcSetIsEncrypted(codec, 0);
}
db->aDb[dbIndex].hasCodec = pPager->hasCodec = libsql_db_has_codec(pPager->pVfs, pPager->zFilename);
}
else
{
if (sqlite3mcHasReadCipher(codec))
{
sqlite3mcCopyCipher(codec, 1);
}
else
{
sqlite3mcSetIsEncrypted(codec, 0);
}
}
sqlite3mcSetReadReserved(codec, -1);
sqlite3mcSetWriteReserved(codec, -1);
if (!sqlite3mcIsEncrypted(codec))
{
sqlite3mcSetCodec(db, zDbName, dbFileName, NULL);
}
return rc;
}
SQLITE_API int
sqlite3_rekey(sqlite3 *db, const void *zKey, int nKey)
{
return sqlite3_rekey_v2(db, "main", zKey, nKey);
}