Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/chacha20poly1305.c
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,68 @@ int poly1305_tagcmp(const uint8_t tag1[16], const uint8_t tag2[16])
return (int) d;
}

/*
* Authenticated SQLite3MC pages without a plaintext prefix.
*
* pageSize includes the complete in-place buffer:
* [ encrypted payload | 16-byte nonce | 16-byte authentication tag ]
* The caller initializes the nonce and derives the 64-byte one-time key:
* otk[0..31] is the Poly1305 key; otk[32..63] is the ChaCha20 key.
* counter is the first payload block counter, not the key-derivation counter.
* The MAC covers the ciphertext and the complete 16-byte nonce. This is the
* SQLite3MC page construction, not the RFC 8439 AEAD message format.
*
* Sizes must include the 32 reserved bytes and be multiples of 16. This also
* permits future fused implementations without changing the calling contract.
* Page 1 (header/salt handling) and unauthenticated pages are handled by the
* caller. Key material must not overlap the page buffer.
*
* Invalid arguments leave the buffer unchanged. On authentication failure,
* callers must discard the page: this scalar implementation leaves ciphertext
* intact, but a future fused implementation may have overwritten it in place.
*/
enum
{
CHACHA20_POLY1305_OK = 0,
CHACHA20_POLY1305_INVALID_ARGUMENT = -1,
CHACHA20_POLY1305_AUTH_FAILED = 1
};

SQLITE_PRIVATE int
chacha20_poly1305_page_encrypt(void* buffer, size_t pageSize,
const uint8_t otk[64], uint32_t counter)
{
uint8_t* page = (uint8_t*) buffer;
size_t payloadSize;
if (page == NULL || otk == NULL || pageSize < 32 || (pageSize & 15) != 0)
return CHACHA20_POLY1305_INVALID_ARGUMENT;

payloadSize = pageSize - 32;
chacha20_xor(page, payloadSize, otk + 32, page + payloadSize, counter);
poly1305(page, payloadSize + 16, otk, page + payloadSize + 16);
return CHACHA20_POLY1305_OK;
}

SQLITE_PRIVATE int
chacha20_poly1305_page_decrypt(void* buffer, size_t pageSize,
const uint8_t otk[64], uint32_t counter)
{
uint8_t* page = (uint8_t*) buffer;
size_t payloadSize;
uint8_t tag[16];
if (page == NULL || otk == NULL || pageSize < 32 || (pageSize & 15) != 0)
return CHACHA20_POLY1305_INVALID_ARGUMENT;

payloadSize = pageSize - 32;
poly1305(page, payloadSize + 16, otk, tag);
if (poly1305_tagcmp(page + payloadSize + 16, tag) != 0)
return CHACHA20_POLY1305_AUTH_FAILED;

/* The scalar path verifies the MAC before exposing any plaintext. */
chacha20_xor(page, payloadSize, otk + 32, page + payloadSize, counter);
return CHACHA20_POLY1305_OK;
}

/*
* Platform-specific entropy functions for seeding RNG
*/
Expand Down
77 changes: 62 additions & 15 deletions src/cipher_chacha20.c
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,26 @@ EncryptPageChaCha20Cipher(void* cipher, int page, unsigned char* data, int len,
counter = LOAD32_LE(data + n + PAGE_NONCE_LEN_CHACHA20 - 4) ^ page;
chacha20_xor(otk, OTK_LEN_CHACHA20, chacha20Cipher->m_key, data + n, counter);

chacha20_xor(data + offset, n - offset, otk + 32, data + n, counter + 1);
if (page == 1 && usePlaintextHeader == 0)
if (page != 1)
{
memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
int result = chacha20_poly1305_page_encrypt(data, (size_t) len, otk, counter + 1);
if (result != CHACHA20_POLY1305_OK)
{
assert(result == CHACHA20_POLY1305_INVALID_ARGUMENT);
sqlite3mcSecureZeroMemory(otk, OTK_LEN_CHACHA20);
return SQLITE_MISUSE;
}
}
else
{
/* Page 1 authenticates the salt/header as well as the ciphertext. */
chacha20_xor(data + offset, n - offset, otk + 32, data + n, counter + 1);
if (usePlaintextHeader == 0)
{
memcpy(data, chacha20Cipher->m_salt, SALTLENGTH_CHACHA20);
}
poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, data + n + PAGE_NONCE_LEN_CHACHA20);
}
poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, data + n + PAGE_NONCE_LEN_CHACHA20);
}
else
{
Expand Down Expand Up @@ -316,31 +330,64 @@ DecryptPageChaCha20Cipher(void* cipher, int page, unsigned char* data, int len,
counter = LOAD32_LE(data + n + PAGE_NONCE_LEN_CHACHA20 - 4) ^ page;
chacha20_xor(otk, OTK_LEN_CHACHA20, chacha20Cipher->m_key, data + n, counter);

/* Only calculate and verify MAC if required */
if (hmacCheck != 0)
if (page != 1 && hmacCheck != 0)
{
poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, tag);

/* Verify the MAC */
if (poly1305_tagcmp(data + n + PAGE_NONCE_LEN_CHACHA20, tag))
int result = chacha20_poly1305_page_decrypt(data, (size_t) len, otk, counter + 1);
if (result != CHACHA20_POLY1305_OK)
{
if (result != CHACHA20_POLY1305_AUTH_FAILED)
{
assert(result == CHACHA20_POLY1305_INVALID_ARGUMENT);
sqlite3mcSecureZeroMemory(otk, OTK_LEN_CHACHA20);
return SQLITE_MISUSE;
}

SQLITE3MC_DEBUG_LOG("decrypt: codec=%p page=%d\n", chacha20Cipher, page);
SQLITE3MC_DEBUG_HEX("decrypt key:", chacha20Cipher->m_key, 32);
SQLITE3MC_DEBUG_HEX("decrypt otk:", otk, OTK_LEN_CHACHA20);
SQLITE3MC_DEBUG_HEX("decrypt data+00:", data, 16);
SQLITE3MC_DEBUG_HEX("decrypt data+24:", data + 24, 16);
SQLITE3MC_DEBUG_HEX("decrypt data+n:", data + n, 16);
#ifdef SQLITE3MC_DEBUG_DATA
poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, tag);
SQLITE3MC_DEBUG_HEX("decrypt tag r:", data + n + PAGE_NONCE_LEN_CHACHA20, PAGE_TAG_LEN_CHACHA20);
SQLITE3MC_DEBUG_HEX("decrypt tag c:", tag, PAGE_TAG_LEN_CHACHA20);

/* Bad MAC: Clean up and bail out BEFORE decrypting */
#endif

/* A future fused implementation may already have overwritten data. */
sqlite3mcSecureZeroMemory(otk, OTK_LEN_CHACHA20);
return (page == 1) ? SQLITE_NOTADB : SQLITE_CORRUPT;
sqlite3mcSecureZeroMemory(data, len);
return SQLITE_CORRUPT;
}
}
else
{
/* Preserve page-1 handling and the explicit MAC-check bypass. */
if (hmacCheck != 0)
{
poly1305(data, n + PAGE_NONCE_LEN_CHACHA20, otk, tag);

/* Verify the MAC */
if (poly1305_tagcmp(data + n + PAGE_NONCE_LEN_CHACHA20, tag))
{
SQLITE3MC_DEBUG_LOG("decrypt: codec=%p page=%d\n", chacha20Cipher, page);
SQLITE3MC_DEBUG_HEX("decrypt key:", chacha20Cipher->m_key, 32);
SQLITE3MC_DEBUG_HEX("decrypt otk:", otk, OTK_LEN_CHACHA20);
SQLITE3MC_DEBUG_HEX("decrypt data+00:", data, 16);
SQLITE3MC_DEBUG_HEX("decrypt data+24:", data + 24, 16);
SQLITE3MC_DEBUG_HEX("decrypt data+n:", data + n, 16);
SQLITE3MC_DEBUG_HEX("decrypt tag r:", data + n + PAGE_NONCE_LEN_CHACHA20, PAGE_TAG_LEN_CHACHA20);
SQLITE3MC_DEBUG_HEX("decrypt tag c:", tag, PAGE_TAG_LEN_CHACHA20);

/* Bad MAC: Clean up and bail out BEFORE decrypting */
sqlite3mcSecureZeroMemory(otk, OTK_LEN_CHACHA20);
return (page == 1) ? SQLITE_NOTADB : SQLITE_CORRUPT;
}
}

/* Decrypt only after MAC is verified (or if check is bypassed) */
chacha20_xor(data + offset, n - offset, otk + 32, data + n, counter + 1);
/* Decrypt only after MAC is verified (or if check is bypassed). */
chacha20_xor(data + offset, n - offset, otk + 32, data + n, counter + 1);
}

if (page == 1 && usePlaintextHeader == 0)
{
Expand Down
2 changes: 2 additions & 0 deletions src/sqlite3mc.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ SQLITE_PRIVATE int sqlite3mcGetMemorySecurity();
SQLITE_PRIVATE void chacha20_xor(void* data, size_t n, const uint8_t key[32], const uint8_t nonce[12], uint32_t counter);
SQLITE_PRIVATE void poly1305(const uint8_t* msg, size_t n, const uint8_t key[32], uint8_t tag[16]);
SQLITE_PRIVATE int poly1305_tagcmp(const uint8_t tag1[16], const uint8_t tag2[16]);
SQLITE_PRIVATE int chacha20_poly1305_page_encrypt(void* buffer, size_t pageSize, const uint8_t otk[64], uint32_t counter);
SQLITE_PRIVATE int chacha20_poly1305_page_decrypt(void* buffer, size_t pageSize, const uint8_t otk[64], uint32_t counter);
SQLITE_PRIVATE void chacha20_rng(void* out, size_t n);

#include "chacha20poly1305.c"
Expand Down
Loading