Skip to content

Protocol v2: receiver proves it opened the metadata (meta_ok key confirmation) - #71

Open
op-q wants to merge 2 commits into
fix/windows-receiverfrom
feat/meta-ok-confirmation
Open

op-q wants to merge 2 commits into
fix/windows-receiverfrom
feat/meta-ok-confirmation

Conversation

@op-q

@op-q op-q commented Sep 14, 2026

Copy link
Copy Markdown
Owner

meta_ok key confirmation plan, all phases, recorded as decision 18. Wire break, so it needs your review. Stacked on #70.

The gap

Decision 13 has the sender count failed guesses and ask a human before allowing another, so that being probed is visible. But the peer's "I opened it" was a bare meta_ok, sent by the very party being limited. A wrong guesser could send it anyway. The attempt counter never climbed and nobody was asked. No data leaked, but the whole point of the prompt was lost. The plan said to land this before the direct path shipped, and the direct path shipped in v0.2.0.

The fix

  • meta_ok carries confirmation: hex of a fourth HKDF output (drop/v1/confirm, 32 bytes). The sender checks it with SessionKeys::confirms, which does a length check and then subtle::ConstantTimeEq. The check lives in crypto/, so a later == can't slip in at a call site.
  • A missing, malformed, wrong-length or wrong confirmation counts as a failed attempt, the same as error, a timeout or a disconnect.
  • Both carriers. The relay now forwards meta_ok verbatim (bounded by MAX_OPAQUE_FIELD_BYTES) instead of failing the session on it. Over the relay, the sender reads past the relay's own status: sending and progress frames; on the direct path, any frame other than meta_ok is still a failure. A failed check over the relay ends the transfer, because the relay has already burned the session.
  • Version 2. ENVELOPE_VERSION and DROP_ALPN move together, and a test fails if they drift. The relay's mismatch error now names both versions.

Compatibility

0.4.0 won't interoperate with 0.3.0 peers or relays on either path. Each side refuses with a version message instead of a confusing wrong-code failure. The consent/cancel/status work comes next under the same unreleased version 2, so users take one break.

Tests (217, up from 210)

  • crypto: matching codes confirm each other; a wrong code can't; wrong length and flipped bytes are refused; the confirmation differs from the other derived secrets.
  • sender: a proven meta_ok passes; five unproven claims fail on both carriers; relay narration is read past on the relay but not on the direct path; the retry-with-human tests now use a peer running a real SPAKE2 handshake (new ScriptedTransport::responding).
  • relay: meta_ok is forwarded verbatim, and an oversized confirmation fails the session.
  • End to end: the existing relay and QUIC-loopback transfers pass with the new checkpoint in place.
  • Negative control: with the comparison forced to true, the claims test fails on the bare meta_ok.

Docs: protocol.md (key schedule, frame tables, checkpoint, version, plus two stale sections saying the direct path was unreachable), security.md, decision 18, plan and checklist.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G7Fy45hUvna94cd79WKG8S

Entry 13 has the sender count guesses and ask a human before allowing another,
so that being probed is visible. It rested on `meta_ok`, a bare claim made by
the party being limited. A guesser who could not open the metadata could say
`meta_ok` anyway, and the counter never climbed. Nothing readable ever leaked,
but the noticing did not hold.

`meta_ok` now carries a key confirmation, a fourth HKDF output only a peer
holding the same keys can produce. The sender compares it in constant time,
inside `crypto/` so a later `==` cannot creep in at a call site. A missing,
malformed or wrong one is a failed attempt like any other.

Both carriers run the checkpoint. The relay forwards `meta_ok` rather than
refusing it, and over the relay the sender reads past the relay's own `sending`
and `progress` narration, which would otherwise count as a failed guess. A
failure there ends the transfer, since the relay has already burned the
session.

`ENVELOPE_VERSION` and `DROP_ALPN` both become 2, and a test ties them
together. A 0.3.0 peer or relay is refused, and the relay's error now names
both versions. The direct path already shipped in v0.2.0, so this is a wire
break; the consent work lands under the same unreleased version.

Test peers now run a real handshake through `ScriptedTransport::responding`,
since no fixed script can prove it holds keys derived from the sender's fresh
half. With the comparison forced true, the claims test fails on a bare meta_ok.

Recorded as decisions entry 18.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G7Fy45hUvna94cd79WKG8S
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

This branch has not been deployed

No deployments
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.

1 participant