Skip to content

Restore the Kyber trust boundary: storage node non-authoritative (revert #625's node gate) + preserve pinned AK on BLE prepare (ADR 0002) - #627

Merged
cryptskii merged 2 commits into
mainfrom
fix/restore-kyber-trust-boundary
Aug 7, 2026
Merged

Restore the Kyber trust boundary: storage node non-authoritative (revert #625's node gate) + preserve pinned AK on BLE prepare (ADR 0002)#627
cryptskii merged 2 commits into
mainfrom
fix/restore-kyber-trust-boundary

Conversation

@cryptskii

@cryptskii cryptskii commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Restores DSM's original trust boundary after #625 merged with a node-side crypto gate that should not exist.

Why this PR exists

#625 was merged at fce2a482 including f0fe9708 ("verify the ML-KEM identity binding before persisting it"), which made the storage node verify identity bindings. That puts identity authority in the storage layer — the opposite of DSM's model. This PR removes it and lands the client-side fix that was meant to accompany the drop.

The two changes restore one invariant: storage stays non-authoritative, and the client preserves the independently-established AK.

The invariant (ADR 0002)

A client may accept or cache peer Kyber (ML-KEM) material only when it can establish a cryptographic chain from that material to an independently authenticated peer AK, or to an equivalently authenticated pairing transcript. Storage-node acceptance is never part of that trust chain.

The test is a proof-chain to the pinned AK, not "does it call the verifier." Full client-path trust matrix (every row: authenticated-by-detached-binding / authenticated-by-signed-pairing-transcript / rejected — no fourth "trusted-because-stored" state) is in docs/adr/0002-storage-acceptance-is-not-cryptographic-endorsement.md.

Change 1 — revert f0fe9708: the storage node is non-authoritative again

Kept as a visible revert of f0fe9708. The node returns to structural admission only: field presence, exact lengths (device_id 32, genesis_hash 32, kyber_public_key 1184), size ceilings — it stores kyber_binding_sig as opaque bytes and serves it, and makes no identity-trust decision. KyberBindingDoesNotVerify and the node crypto test (kyber_binding_enforced.rs) are removed. The node can only verify a self-supplied AK anyway (internal consistency), which establishes no trust and does nothing for a contactless peer.

The client already is the trust boundary for node-served identity (verified against the pinned AK on the repair + hydrate paths — matrix rows 1–2, unchanged and confirmed SAFE by the read-side audit).

Change 2 — matrix rows 6/7: the BLE prepare handlers must not re-root the pinned AK

The bilateral BLE prepare-request/response handlers unconditionally overwrote a contact's pairing-established signing AK — the trust root — with the wire-supplied signing key. Anyone able to deliver a prepare frame naming a victim's device_id could re-root that contact's AK from unauthenticated wire bytes.

Fix (client-only, no wire change): the unconditional AK write is replaced with a first-write-wins bind_contact_public_key_if_absent. The AK is established only when the contact has none; a differing wire AK is reported as a substitution and rejected — the pinned AK stands. The in-memory manager is mirrored only when the wire AK equals the pinned trust root (which also routes the downstream σ_B verify to the pinned AK, not the just-overwritten one). The now-dead update_contact_public_key DB fn is deleted. Two comments claiming the binding is "verified at contact-add" are corrected (it is verified against the pinned AK at first online send / hydrate).

Unit-proven: bind_contact_public_key_if_absent_is_first_write_wins_and_rejects_substitution (all four outcomes + byte-for-byte pinned-AK preservation on substitution). The AK-guard mutation gate (ak_trust_root_tests) and the repair-path tests from #625 are retained (rows 1–2).

Explicitly NOT in this PR — matrix rows 8/9 (release-blocking BLE P0)

The BLE-delivered Kyber key is still in no signature verified against the pinned AK (prepare-req has no envelope signature; prepare-resp binds before σ_B, and σ_B excludes the Kyber field). Closing that is a wire-format change — bind the Kyber material into the signed BLE prepare transcript, sender signs, receiver verifies against the pinned AK before caching — that requires two-phone hardware validation and is the dedicated release-blocking BLE P0 branch. No proto/wire change appears in this PR.

Gates (local, green)

  • cargo fmt --all -- --check ✅ and cargo clippy --all-targets -- -D warnings ✅ (workspace, incl. dsm_storage_node rebuilt clean post-revert)
  • cargo test -p dsm_sdk1844 passed / 0 failed; cargo test -p dsm_storage_node — all suites pass / 0 failed
  • ci/production_safety_checks.sh — production clippy + TLA+ both green

Full CI matrix runs on this branch.

The bilateral BLE prepare-request/response handlers unconditionally overwrote a
contact's pairing-established signing AK — the trust root — with the wire-supplied
signing key (the old `update_contact_public_key` calls in bilateral_ble_handler).
Anyone able to deliver a prepare frame naming a victim's device_id could re-root
that contact's AK from unauthenticated wire bytes: the same substitution class the
online repair path already rejects. Matrix rows 6/7 in ADR 0002.

Fix (client-only, no wire change): replace the unconditional AK write with a
first-write-wins `bind_contact_public_key_if_absent`. The signing AK is established
only when the contact has none; a differing wire AK is reported as a substitution
and rejected — the pinned AK stands. The in-memory manager is mirrored only when
the wire AK equals the pinned trust root, which also makes the downstream sigma_B
verify against the pinned AK rather than the just-overwritten one. The now-dead
unconditional `update_contact_public_key` DB fn is deleted.

Adds ADR 0002 (storage acceptance is not cryptographic endorsement) with the
complete client-path trust matrix — every consumer of peer Kyber material is
authenticated by detached binding, authenticated by signed pairing transcript, or
rejected; storage-node acceptance is never part of the chain. Corrects two
comments that claimed the Kyber binding is "verified at contact-add" (it is
verified against the pinned AK at first online send / hydrate, not at QR-add).

NOT in this change: authenticating the BLE-delivered *Kyber* key against the
pinned AK (matrix rows 8/9). That is a BLE wire-format change — bind the Kyber
material into the signed prepare transcript, sender signs, receiver verifies
against the pinned AK before caching — requiring two-phone validation, and is the
dedicated release-blocking BLE P0 follow-up.
@cryptskii
cryptskii merged commit 3ae675f into main Aug 7, 2026
12 checks passed
@cryptskii
cryptskii deleted the fix/restore-kyber-trust-boundary branch August 7, 2026 23:26
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