fix: set SQLCipher cipher_page_size BEFORE first database access
Some checks are pending
Test / rust-fmt-check (push) Waiting to run
Test / rust-clippy (push) Waiting to run
Test / rust-tests (push) Waiting to run
Test / frontend-typecheck (push) Waiting to run
Test / frontend-tests (push) Waiting to run

Previously cipher_page_size was set AFTER the verification SELECT,
making it a no-op (SQLCipher locks page size on first access). This
caused two bugs:

1. Databases were created with the default page size regardless of the
   setting, then flushed on close using misaligned 4KB mmap pages on
   16KB kernel → corrupted file → SQLITE_NOTADB on reopen.

2. Reopening an existing DB used default (potentially wrong) page size
   for the initial read → decryption failure.

Fix: batch all cipher settings (key, page_size, kdf_iter, algorithms)
into a single execute_batch call BEFORE the first SELECT. This ensures:
- New databases are created with 16KB pages (aligned to Asahi kernel)
- Existing 16KB-page databases are reopened with the correct page size
- Close/flush operations use properly aligned mmap → no corruption

Note: existing 4KB-page databases (from v0.1.0) remain incompatible
and must be deleted once on upgrade.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Shaun Arman 2026-03-29 21:25:43 -05:00
parent 9c3f924f27
commit 66de8f71cb

View File

@ -3,17 +3,20 @@ use std::path::Path;
pub fn open_encrypted_db(path: &Path, key: &str) -> anyhow::Result<Connection> {
let conn = Connection::open(path)?;
// Set SQLCipher encryption key
conn.execute_batch(&format!("PRAGMA key = '{}';", key.replace('\'', "''")))?;
// Verify the key works by running a simple query
conn.execute_batch("SELECT count(*) FROM sqlite_master;")?;
// Set SQLCipher settings for AES-256
conn.execute_batch(
"PRAGMA cipher_page_size = 16384; \
PRAGMA kdf_iter = 256000; \
PRAGMA cipher_hmac_algorithm = HMAC_SHA512; \
// ALL cipher settings MUST be set before the first database access.
// cipher_page_size in particular must precede any read/write so it takes
// effect for both creation (new DB) and reopening (existing DB).
// 16384 matches 16KB kernel page size (Asahi Linux / Apple Silicon aarch64).
conn.execute_batch(&format!(
"PRAGMA key = '{}';\
PRAGMA cipher_page_size = 16384;\
PRAGMA kdf_iter = 256000;\
PRAGMA cipher_hmac_algorithm = HMAC_SHA512;\
PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512;",
)?;
key.replace('\'', "''")
))?;
// Verify the key and settings work
conn.execute_batch("SELECT count(*) FROM sqlite_master;")?;
Ok(conn)
}