version bumps, MAX_RECV_SEEN patch, FIPS interop, doc updates
Some checks failed
CI / lint (push) Failing after 49s
CI / test-python (push) Has been cancelled
CI / test (push) Has been cancelled
CI / miri (push) Has been cancelled
CI / test-zig (push) Has been cancelled
CI / test-wasm (push) Has been cancelled
CI / build (push) Has been cancelled
CI / publish (push) Has been cancelled
CI / publish-wasm (push) Has been cancelled
CI / fuzz-regression (push) Has been cancelled
CI / publish-python (push) Has been cancelled
Some checks failed
CI / lint (push) Failing after 49s
CI / test-python (push) Has been cancelled
CI / test (push) Has been cancelled
CI / miri (push) Has been cancelled
CI / test-zig (push) Has been cancelled
CI / test-wasm (push) Has been cancelled
CI / build (push) Has been cancelled
CI / publish (push) Has been cancelled
CI / publish-wasm (push) Has been cancelled
CI / fuzz-regression (push) Has been cancelled
CI / publish-python (push) Has been cancelled
This commit is contained in:
parent
89f5be4f40
commit
e94b7e9ee7
17 changed files with 691 additions and 435 deletions
|
|
@ -1416,7 +1416,7 @@ function Decrypt(state, header, ciphertext, sender_fp, recipient_fp):
|
|||
if header.n in state.prev_recv_seen:
|
||||
restore_recv_state(state, snapshot)
|
||||
raise DuplicateMessage
|
||||
if |state.prev_recv_seen| >= MAX_RECV_SEEN:
|
||||
if |state.prev_recv_seen| + 1 >= MAX_RECV_SEEN:
|
||||
restore_recv_state(state, snapshot)
|
||||
raise ChainExhausted
|
||||
state.prev_recv_seen.add(header.n)
|
||||
|
|
@ -1434,7 +1434,7 @@ function Decrypt(state, header, ciphertext, sender_fp, recipient_fp):
|
|||
if header.n in state.recv_seen:
|
||||
restore_recv_state(state, snapshot)
|
||||
raise DuplicateMessage
|
||||
if |state.recv_seen| >= MAX_RECV_SEEN:
|
||||
if |state.recv_seen| + 1 >= MAX_RECV_SEEN:
|
||||
restore_recv_state(state, snapshot)
|
||||
raise ChainExhausted
|
||||
state.recv_seen.add(header.n)
|
||||
|
|
@ -1586,7 +1586,7 @@ Ratchet state is serialized for encrypted persistent storage. The caller MUST au
|
|||
|
||||
**Serialization buffer zeroization**: The serialization output buffer contains root keys, epoch keys, and ratchet secret keys — all secret material. Implementations SHOULD pre-allocate the buffer to its exact final size before writing any data. If a dynamic array (Vec, list, slice) reallocates during serialization, the abandoned allocation containing partial secret material is freed to the heap allocator without zeroization — only the final allocation is covered by zeroize-on-drop. In Rust, pre-allocation is the actual guarantee; a `debug_assert` on capacity detects underestimates during testing but is compiled out in release builds — if the pre-computed size is wrong, a release binary silently reallocates and the abandoned partial allocation is freed without zeroization. In C/Go/Python, pre-compute the buffer size and allocate once. The output buffer itself MUST be zeroized after the caller has finished with it (e.g., after passing it to storage encryption).
|
||||
|
||||
**MAX_RECV_SEEN cap at runtime**: When `recv_seen` or `prev_recv_seen` reaches `MAX_RECV_SEEN` (65536) entries during decrypt, subsequent messages in that epoch return `ChainExhausted`. This is transient — the cap resets on the next KEM ratchet step (which clears `recv_seen`). The cap prevents unbounded memory growth from an authenticated peer sending many messages in a single epoch.
|
||||
**MAX_RECV_SEEN cap at runtime**: When `recv_seen` or `prev_recv_seen` reaches `MAX_RECV_SEEN - 1` (65535) entries during decrypt, the next message in that epoch returns `ChainExhausted` — the runtime check rejects when a successful insert would push the set to `MAX_RECV_SEEN`, matching the serialization invariant (`|recv_seen| < MAX_RECV_SEEN`). This is transient — the cap resets on the next KEM ratchet step (which clears `recv_seen`). The cap prevents unbounded memory growth from an authenticated peer sending many messages in a single epoch.
|
||||
|
||||
**Which epoch paths can fire this cap**: The `recv_seen` saturation check (`ChainExhausted`) fires only on the **current-epoch path** (messages in the active receive epoch) and the **previous-epoch path** (messages in the prior epoch, checked against `prev_recv_seen`). The **new-epoch path** (which triggers a KEM ratchet step) is immune: it clears `recv_seen` to empty before the duplicate/cap check, so the cap check on that path is structurally unreachable — a new epoch always starts with an empty `recv_seen`. A reimplementer testing `ChainExhausted` from `recv_seen` saturation MUST use the current-epoch or previous-epoch path, not the new-epoch path.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue