Restore the Kyber trust boundary: storage node non-authoritative (revert #625's node gate) + preserve pinned AK on BLE prepare (ADR 0002) - #627
Merged
Conversation
…sting it" This reverts commit f0fe970.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
fce2a482includingf0fe9708("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)
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 againKept as a visible revert of
f0fe9708. The node returns to structural admission only: field presence, exact lengths (device_id32,genesis_hash32,kyber_public_key1184), size ceilings — it storeskyber_binding_sigas opaque bytes and serves it, and makes no identity-trust decision.KyberBindingDoesNotVerifyand 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_idcould 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-deadupdate_contact_public_keyDB 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✅ andcargo clippy --all-targets -- -D warnings✅ (workspace, incl.dsm_storage_noderebuilt clean post-revert)cargo test -p dsm_sdk— 1844 passed / 0 failed;cargo test -p dsm_storage_node— all suites pass / 0 failedci/production_safety_checks.sh— production clippy + TLA+ both greenFull CI matrix runs on this branch.