Bug 1639552 - Integrate IPCStreamCipherStrategy into ObfuscatingVFS. r=dom-workers-and-storage-reviewers,janv

Differential Revision: https://phabricator.services.mozilla.com/D76141
This commit is contained in:
Simon Giesecke 2020-10-29 10:13:13 +00:00
Родитель 80cee3b558
Коммит 2cd9c3bd8e
1 изменённых файлов: 105 добавлений и 67 удалений

Просмотреть файл

@ -11,7 +11,7 @@
******************************************************************************
**
** This file implements a VFS shim that obfuscates database content
** written to disk by XOR-ing a hex pattern passed in as a query parameter.
** written to disk by applying a CipherStrategy.
**
** COMPILING
**
@ -108,6 +108,9 @@ SQLITE_EXTENSION_INIT1
#include <ctype.h>
#include <stdio.h> /* For debugging only */
#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
#include "mozilla/ScopeExit.h"
/*
** Forward declaration of objects used by this utility
*/
@ -127,24 +130,25 @@ typedef unsigned char u8;
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
#define ORIGFILE(p) ((sqlite3_file*)(((ObfsFile*)(p)) + 1))
/*
** Size of the obfuscation key
*/
#define OBFS_KEYSZ 32
/*
** Database page size for obfuscated databases
*/
#define OBFS_PGSZ 8192
using namespace mozilla;
using namespace mozilla::dom::quota;
/* An open file */
struct ObfsFile {
sqlite3_file base; /* IO methods */
const char* zFName; /* Original name of the file */
char inCkpt; /* Currently doing a checkpoint */
ObfsFile* pPartner; /* Ptr from WAL to main-db, or from main-db to WAL */
void* pTemp; /* Temporary storage for encoded pages */
u8 aKey[OBFS_KEYSZ]; /* Obfuscation key */
sqlite3_file base; /* IO methods */
const char* zFName; /* Original name of the file */
char inCkpt; /* Currently doing a checkpoint */
ObfsFile* pPartner; /* Ptr from WAL to main-db, or from main-db to WAL */
void* pTemp; /* Temporary storage for encoded pages */
IPCStreamCipherStrategy*
encryptCipherStrategy; /* CipherStrategy for encryption */
IPCStreamCipherStrategy*
decryptCipherStrategy; /* CipherStrategy for decryption */
};
/*
@ -236,28 +240,36 @@ static const sqlite3_io_methods obfs_io_methods = {
obfsUnfetch /* xUnfetch */
};
/* Obfuscate a page using the key in p->aKey[].
static constexpr int kKeyBytes = 32;
static constexpr int kIvBytes = IPCStreamCipherStrategy::BlockPrefixLength;
static constexpr int kClearTextPrefixBytesOnFirstPage = 32;
static constexpr int kReservedBytes = 32;
static constexpr int kBasicBlockSize = IPCStreamCipherStrategy::BasicBlockSize;
static_assert(kClearTextPrefixBytesOnFirstPage % kBasicBlockSize == 0);
static_assert(kReservedBytes % kBasicBlockSize == 0);
/* Obfuscate a page using p->encryptCipherStrategy.
**
** A new random nonce is created and stored in the last 32 bytes
** of the page. All other bytes of the page are XOR-ed against both
** the key and the nonce. Except, for page-1 (including the SQLite
** of the page. All other bytes of the page are obfuscasted using the
** CipherStrategy. Except, for page-1 (including the SQLite
** database header) the first 32 bytes are not obfuscated
**
** Return a pointer to the obfuscated content, which is held in the
** p->aTemp buffer. Or return a NULL pointer if something goes wrong.
** p->pTemp buffer. Or return a NULL pointer if something goes wrong.
** Errors are reported using sqlite3_log().
*/
static void* obfsEncode(
ObfsFile* p, /* File containing page to be obfuscated */
u8* a, /* database page to be obfuscated */
int nByte /* Bytes of content in a[]. Must be a multiple of OBFS_KEYSZ. */
static void* obfsEncode(ObfsFile* p, /* File containing page to be obfuscated */
u8* a, /* database page to be obfuscated */
int nByte /* Bytes of content in a[]. Must be a multiple
of kBasicBlockSize. */
) {
u8 aKey[OBFS_KEYSZ];
u8 aIv[kIvBytes];
u8* pOut;
int i;
assert((OBFS_KEYSZ & (OBFS_KEYSZ - 1)) == 0);
sqlite3_randomness(OBFS_KEYSZ, aKey);
static_assert((kIvBytes & (kIvBytes - 1)) == 0);
sqlite3_randomness(kIvBytes, aIv);
pOut = (u8*)p->pTemp;
if (pOut == 0) {
pOut = static_cast<u8*>(sqlite3_malloc64(nByte));
@ -268,60 +280,58 @@ static void* obfsEncode(
p->zFName);
return 0;
}
}
memcpy(pOut + nByte - OBFS_KEYSZ, aKey, OBFS_KEYSZ);
for (i = 0; i < OBFS_KEYSZ; i++) {
aKey[i] ^= p->aKey[i];
p->pTemp = pOut;
}
if (memcmp(a, "SQLite format 3", 16) == 0) {
i = OBFS_KEYSZ;
if (a[20] != OBFS_KEYSZ) {
i = kClearTextPrefixBytesOnFirstPage;
if (a[20] != kReservedBytes) {
sqlite3_log(SQLITE_IOERR,
"obfuscated database must have reserve-bytes"
"obfuscated database must have reserved-bytes"
" set to %d",
OBFS_KEYSZ);
return 0;
kReservedBytes);
return nullptr;
}
memcpy(pOut, a, OBFS_KEYSZ);
memcpy(pOut, a, kClearTextPrefixBytesOnFirstPage);
} else {
i = 0;
}
while (i < nByte - OBFS_KEYSZ) {
pOut[i] = aKey[i & (OBFS_KEYSZ - 1)] ^ a[i];
i++;
}
const int payloadLength = nByte - kReservedBytes - i;
MOZ_ASSERT(payloadLength > 0);
// XXX I guess this can be done in-place as well, then we don't need the
// temporary page at all, I guess?
p->encryptCipherStrategy->Cipher(
Span{aIv}, Span{a + i, static_cast<unsigned>(payloadLength)},
Span{pOut + i, static_cast<unsigned>(payloadLength)});
memcpy(pOut + nByte - kReservedBytes, aIv, kIvBytes);
return pOut;
}
/* De-obfuscate a page using the key in p->aKey[].
/* De-obfuscate a page using p->decryptCipherStrategy.
**
** Deobfuscation consists of XORing all bytes against both the key
** and the nonce stored in the last 32-bytes of the page. The
** deobfuscation is done in-place.
** The deobfuscation is done in-place.
**
** For pages that begin with the SQLite header text, the first
** 32 bytes are not deobfuscated.
*/
static void obfsDecode(
ObfsFile* p, /* File containing page to be obfuscated */
u8* a, /* database page to be obfuscated */
int nByte /* Bytes of content in a[]. Must be a multiple of OBFS_KEYSZ. */
static void obfsDecode(ObfsFile* p, /* File containing page to be obfuscated */
u8* a, /* database page to be obfuscated */
int nByte /* Bytes of content in a[]. Must be a multiple
of kBasicBlockSize. */
) {
u8 aKey[OBFS_KEYSZ];
int i;
for (i = 0; i < OBFS_KEYSZ; i++) {
aKey[i] = p->aKey[i] ^ a[i + nByte - OBFS_KEYSZ];
}
if (memcmp(a, "SQLite format 3", 16) == 0) {
i = OBFS_KEYSZ;
i = kClearTextPrefixBytesOnFirstPage;
} else {
i = 0;
}
while (i < nByte - OBFS_KEYSZ) {
a[i] ^= aKey[i & (OBFS_KEYSZ - 1)];
i++;
}
const int payloadLength = nByte - kReservedBytes - i;
MOZ_ASSERT(payloadLength > 0);
p->decryptCipherStrategy->Cipher(
Span{a + nByte - kReservedBytes, kIvBytes},
Span{a + i, static_cast<unsigned>(payloadLength)},
Span{a + i, static_cast<unsigned>(payloadLength)});
}
/*
@ -335,6 +345,10 @@ static int obfsClose(sqlite3_file* pFile) {
p->pPartner = 0;
}
sqlite3_free(p->pTemp);
delete p->decryptCipherStrategy;
delete p->encryptCipherStrategy;
pFile = ORIGFILE(pFile);
return pFile->pMethods->xClose(pFile);
}
@ -345,7 +359,7 @@ static int obfsClose(sqlite3_file* pFile) {
** If the file is less than one full page in length, then return
** a substitute "prototype" page-1. This prototype page one
** specifies a database in WAL mode with an 8192-byte page size
** and a 32-byte reserve-byte value. Those settings are necessary
** and a 32-byte reserved-bytes value. Those settings are necessary
** for obfuscation to function correctly.
*/
static int obfsRead(sqlite3_file* pFile, void* zBuf, int iAmt,
@ -360,9 +374,10 @@ static int obfsRead(sqlite3_file* pFile, void* zBuf, int iAmt,
}
} else if (SQLITE_IOERR_SHORT_READ && iOfst == 0 && iAmt >= 100) {
static const unsigned char aEmptyDb[] = {
0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d,
0x61, 0x74, 0x20, 0x33, 0x00, 0x20, 0x00, 0x02, 0x02, 0x20, 0x40,
0x20, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01};
0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00,
0x20, 0x00, 0x02, 0x02, kReservedBytes, 0x40, 0x20, 0x20,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01};
memcpy(zBuf, aEmptyDb, sizeof(aEmptyDb));
memset(((u8*)zBuf) + sizeof(aEmptyDb), 0, iAmt - sizeof(aEmptyDb));
rc = SQLITE_OK;
@ -546,7 +561,7 @@ static int obfsOpen(sqlite3_vfs* pVfs, const char* zName, sqlite3_file* pFile,
sqlite3_vfs* pSubVfs;
int rc, i;
const char* zKey;
u8 aKey[OBFS_KEYSZ];
u8 aKey[kKeyBytes];
pSubVfs = ORIGVFS(pVfs);
if (flags &
(SQLITE_OPEN_MAIN_DB | SQLITE_OPEN_WAL | SQLITE_OPEN_MAIN_JOURNAL)) {
@ -558,22 +573,43 @@ static int obfsOpen(sqlite3_vfs* pVfs, const char* zName, sqlite3_file* pFile,
return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
}
for (i = 0;
i < OBFS_KEYSZ && isxdigit(zKey[i * 2]) && isxdigit(zKey[i * 2 + 1]);
i < kKeyBytes && isxdigit(zKey[i * 2]) && isxdigit(zKey[i * 2 + 1]);
i++) {
aKey[i] = (obfsHexToInt(zKey[i * 2]) << 8) | obfsHexToInt(zKey[i * 2 + 1]);
aKey[i] = (obfsHexToInt(zKey[i * 2]) << 4) | obfsHexToInt(zKey[i * 2 + 1]);
}
if (i != OBFS_KEYSZ) {
if (i != kKeyBytes) {
sqlite3_log(SQLITE_CANTOPEN, "invalid query parameter on %s: key=%s", zName,
zKey);
return SQLITE_CANTOPEN;
}
p = (ObfsFile*)pFile;
memset(p, 0, sizeof(*p));
memcpy(p->aKey, aKey, sizeof(aKey));
auto encryptCipherStrategy = MakeUnique<IPCStreamCipherStrategy>();
auto decryptCipherStrategy = MakeUnique<IPCStreamCipherStrategy>();
auto resetMethods = MakeScopeExit([pFile] { pFile->pMethods = nullptr; });
if (NS_WARN_IF(NS_FAILED(encryptCipherStrategy->Init(
CipherMode::Encrypt, Span{aKey, sizeof(aKey)},
IPCStreamCipherStrategy::MakeBlockPrefix())))) {
return SQLITE_ERROR;
}
if (NS_WARN_IF(NS_FAILED(decryptCipherStrategy->Init(
CipherMode::Decrypt, Span{aKey, sizeof(aKey)})))) {
return SQLITE_ERROR;
}
pSubFile = ORIGFILE(pFile);
p->base.pMethods = &obfs_io_methods;
rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
if (rc) goto obfs_open_done;
if (rc) {
return rc;
}
resetMethods.release();
if (flags & (SQLITE_OPEN_WAL | SQLITE_OPEN_MAIN_JOURNAL)) {
sqlite3_file* pDb = sqlite3_database_file_object(zName);
p->pPartner = (ObfsFile*)pDb;
@ -581,9 +617,11 @@ static int obfsOpen(sqlite3_vfs* pVfs, const char* zName, sqlite3_file* pFile,
p->pPartner->pPartner = p;
}
p->zFName = zName;
obfs_open_done:
if (rc) pFile->pMethods = 0;
return rc;
p->encryptCipherStrategy = encryptCipherStrategy.release();
p->decryptCipherStrategy = decryptCipherStrategy.release();
return SQLITE_OK;
}
/*