kdf: version Argon2id profiles (legacy stores still open) and raise the default to 64 MiB / t=3 #194
Loading…
Reference in a new issue
No description provided.
Delete branch "onionwire/t_fb6ffa87-strengthen-and-version-the-store-backup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
The store and identity backup shared one hard-coded Argon2id profile (m=19456 KiB, t=2, p=1) that was not recorded anywhere, so changing it would have bricked every existing store and backup. This adds a versioned profile and raises the default to m = 65_536 KiB (64 MiB), t = 3, p = 1, out = 32.
KdfProfile { Legacy = 0, Current = 1 }insrc/backup.rs;kdf()is now Current.seal()writesowbak1 || profile(1) || salt(16) || nonce(12) || ct. The pre-profile layout (exactlySEALED_BODY_LENbytes afterMAGIC) is read as Legacy. An unknown profile id is a clear error, never a fallback.MAGICandSALT_LENare unchanged.store_metagainskdf_profile INTEGER NOT NULL DEFAULT 0(additive migration). Legacy rows are re-wrapped once on a successful unlock — newkdf_salt, newwrapped_key,kdf_profile = 1in a single transaction. New stores insert profile 1 directly.docs/THREAT_MODEL.mdnow states the new params and that profiles are versioned.Spec note: the SQLCipher file key needs its own detection
store_meta.kdf_profilecannot say which profile keyed the SQLCipher file, becausestore_metais inside that file. The file key is still the rawkdf(passphrase, store_salt)(no second KDF — the Locks hold). A legacy store is therefore detected by trial-opening with the legacy profile, then rekeyed to the current profile withPRAGMA rekeyin one SQLCipher transaction (it rolls back on any page-write failure). WAL is checkpointed first so no old-key frames survive the rewrite. This is one-shot: after the rekey the next start takes the current path, and the legacy key no longer opens the file (asserted in the test).store_saltstays 16 bytes;SALT_LENstays 16.Tests (new)
src/backup.rs:wrong passphrase or corrupt backupunknown kdf profile 9, no fallbacksrc/store.rs(at_rest):store_metaflips to profile 1 with a changed salt and wrapped key; the file is rekeyed (current key opens, legacy key fails); a second unlock does not re-wrap againstore_meta.kdf_profileis a clean errorVerification
cargo test --locked --target x86_64-unknown-linux-gnu→EXITCODE=0; lib:168 passed; 0 failed; 2 ignored; all integration binaries green (incl. two_node, wallet, store, backup).cargo clippy --locked --all-targets --target x86_64-unknown-linux-gnu -- -D warnings→ rc=0.rustfmt --edition 2024 --check src/backup.rs src/store.rs→ clean.Release-build timings on this host (
--release --ignoredmeasurement tests):Well under the ~1.5 s ceiling, so the 64 MiB profile stays.
No dependency changes. Scope limited to
src/backup.rs,src/store.rs,docs/THREAT_MODEL.md.The store and identity backup shared one hard-coded Argon2id profile (m=19456 KiB, t=2, p=1) with nothing recorded on disk, so changing it would have bricked every existing store and backup. Record the profile with the data and raise the default: - KdfProfile { Legacy = 0, Current = 1 }; Current is m=65536 KiB, t=3, p=1, out=32. - Backups: seal() writes a profile byte after owbak1. Legacy blobs (exactly SEALED_BODY_LEN after MAGIC) still open; an unknown id is an error, never a fallback. - Stores: store_meta gains kdf_profile (additive, default 0). The SQLCipher file key has no place to record a profile before the db opens, so a legacy file is detected by trial-opening with the legacy profile, then rekeyed to Current in one SQLCipher transaction; store_meta is re-wrapped (salt + ciphertext + profile) in a single transaction on the first successful unlock. Both are one-shot: the next start takes the current path. - docs/THREAT_MODEL.md states the new params and the versioning. Measured in release on this host: kdf legacy 0.018s / current 0.095s; Store reopen current 0.354s, legacy upgrade path 0.528s.