Skip to content

fix(crypto): add RatchetCache::clear() for explicit key eviction (#178)#469

Merged
intendednull merged 1 commit into
claude/friendly-maxwell-Oggvwfrom
auto-fix/issue-178-ratchet-cache-clear
Apr 28, 2026
Merged

fix(crypto): add RatchetCache::clear() for explicit key eviction (#178)#469
intendednull merged 1 commit into
claude/friendly-maxwell-Oggvwfrom
auto-fix/issue-178-ratchet-cache-clear

Conversation

@intendednull
Copy link
Copy Markdown
Owner

What

Add pub fn clear(&mut self) to RatchetCache. Empty cache map. Empty ratchet_states map. ChannelKey ZeroizeOnDrop, KeyRatchet ZeroizeOnDrop. BTreeMap::clear + HashMap::clear drop each value in place. Secret bytes zero before bucket release. Issue #178 want explicit eviction. Now exist.

Two unit tests added:

  • ratchet_cache_clear_drops_all_state — populate three epochs, call clear(), check both maps empty, check cache still usable for fresh derive.
  • ratchet_cache_clear_is_idempotent — clear empty cache no panic, repeat ok.

Brainstorm — wiring point

Task say "wire clear() into channel-leave / server-disconnect — adding method nobody call is theatre".

Look. RatchetCache not used outside crypto crate own tests. derive_or_cached zero call sites in client/web/messaging. Production decrypt path (open_content) not consult cache. Cache is API surface waiting integration.

Chosen wiring point: none today. Method exist + documented. Filed follow-up #468 so integrator wire clear() into leave_server + sign-out when cache lands in client decrypt path.

Runner-up rejected: Speculatively integrate RatchetCache into client decrypt flow now, then wire teardown. Rejected because (a) scope creep beyond #178, (b) ratchet caching is perf/correctness change distinct from this cleanup work, (c) task warn don't balloon to refactor.

Note on existing key map: HashMap<String, ChannelKey> in crates/client/src/state_actors.rs ServerEntry hold actual production channel keys. leave_server already remove ServerEntry — that drop the map — that drop each ChannelKey — ZeroizeOnDrop fire. Path safe today. No change needed.

Verification

  • cargo test -p willow-crypto: 64 passed
  • cargo clippy --workspace --all-targets -- -D warnings: clean
  • cargo check --target wasm32-unknown-unknown -p willow-crypto -p willow-client: clean
  • cargo fmt --check: clean
  • cargo test --workspace: passed except willow-actor::send_dead_actor_returns_send_error flaky pre-existing (unrelated, passes in isolation)

Tradeoffs

Skipped client-tier test asserting cache.len()==0 after sign-out: cache not yet owned by client, no fixture exist. Unit test cover the contract. Follow-up #468 cover integration test when wire-up land.

Refs #178


Generated by Claude Code

RatchetCache stored derived ChannelKey values in BTreeMap +
ratchet_states without any caller-driven way to wipe them. ChannelKey
already implements ZeroizeOnDrop, but secrets only zeroize when the
map drops the entry — so cached keys lingered until eviction pressure
hit max_entries or the cache itself was dropped.

Add `pub fn clear(&mut self)` that empties both maps. BTreeMap::clear
and HashMap::clear drop each value in place, triggering the per-key
ZeroizeOnDrop pass before the buckets are released, so secret material
is wiped synchronously at the call point.

Also add two unit tests:
- ratchet_cache_clear_drops_all_state: populate three epochs, call
  clear(), assert both `cache` and `ratchet_states` are empty + the
  cache is still usable for fresh derivations.
- ratchet_cache_clear_is_idempotent: clear-on-empty is a no-op.

Brainstorm — wiring point. RatchetCache is currently API surface only:
no client/web code calls `derive_or_cached`, so there is no live
production cache to wire into leave_server / sign-out today.
Speculatively integrating the cache into the client decrypt path was
considered (runner-up) and rejected: that's a perf/correctness change
distinct from this security cleanup, and the task explicitly cautions
against scope creep. Filing a follow-up so the wire-up is not
forgotten when the cache lands in the decrypt hot path.

The HashMap<String, ChannelKey> that actually holds production keys
(crates/client/src/state.rs, state_actors.rs) is already cleaned up
correctly: leave_server removes the ServerEntry, dropping its keys
map and zeroizing each ChannelKey via ZeroizeOnDrop.

Verification:
- cargo test -p willow-crypto: 64 passed
- cargo clippy --workspace --all-targets -- -D warnings: clean
- cargo check --target wasm32-unknown-unknown -p willow-crypto -p
  willow-client: clean
- cargo fmt --check: clean

Refs #178
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants