BLE announce trust verified the packet signature against the Ed25519
signing key carried inside the same announce, and the trust policy only
rejected on noise-key mismatch. Since peerIDs derive from the broadcast
(public) noise key, an on-mesh attacker could replay a victim's
peerID+noiseKey with their own signing key, nickname, and a valid
self-signature; BLEPeerRegistry.upsertVerifiedAnnounce then overwrote
the victim's entry unconditionally. That enabled mesh nickname spoofing
and, via the persisted identity, forged attribution of signed
public/broadcast messages.
Fix: TOFU-pin the signing key per peer (noise-key-derived peerID).
- BLEAnnounceTrustPolicy now rejects announces whose signing key
differs from the one already recorded for the peer
(.signingKeyMismatch) and logs a security event.
- BLEPeerRegistry.upsertVerifiedAnnounce refuses to replace a pinned
signing key (returns nil), closing the race where the pre-barrier
trust check reads the registry outside the collections barrier. It
also never drops a pinned key when an announce omits one.
- BLEAnnounceHandler skips registry upsert, topology updates, and
identity persistence for rejected announces.
No wire-format change: the packet signature already covers senderID,
timestamp, and the full announce payload (noise key, signing key,
nickname), so mixed-version meshes are unaffected. First contact for an
unknown peer behaves exactly as before (trust on first use); the pin is
scoped to the registry entry lifetime, so a legitimately re-keyed
identity recovers after normal peer eviction.
Tests: trust-policy signing-key mismatch/match cases, registry pinning
(attacker upsert refused, legitimate re-announce accepted, omitted key
keeps pin), handler-level pinned-key rejection, and an end-to-end test
with real Ed25519 keys showing a fully self-consistent attacker
announce cannot displace the victim's pinned identity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>