mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 19:25:20 +00:00
14ab04d2bbf4beda32ea365329b2d94ba49d3c16
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbe11641ad |
Extend signing-key pin to persisted identity so it survives restart/eviction
The TOFU signing-key pin lived only in the in-memory peerRegistry. When a previously seen peer was no longer in the registry (app restart, or reconcileConnectivity pruning an offline peer), the announce trust check saw no pinned key, treated the announce as first contact, and persistIdentity overwrote the cached signing key/nickname — so an attacker could replay a victim's noiseKey/peerID with their own signing key and bypass the spoofing protection for returning/offline peers. Fixes: - BLEAnnounceHandler now falls back to the persisted cryptographic identity (via a new persistedSigningPublicKey environment closure) when the registry has no signing key for the peer, so the pin remains effective across registry eviction and app restarts. BLEService wires it to SecureIdentityStateManager.getCryptoIdentitiesByPeerIDPrefix, the same synchronous identity-manager read already used on the packet path by signedSenderDisplayName. - SecureIdentityStateManager.upsertCryptographicIdentity refuses to replace a persisted signing key with a different one (security-logged, nickname update included in the refusal), mirroring the registry policy. First-writer-wins persistence also removes any race where a concurrent announce could poison the stored identity. - CryptographicIdentity entries (incl. the signing-key pin) are now part of the encrypted IdentityCache, so they actually persist across app restarts; previously they were in-memory only. Old caches without the new field still decode (decodeIfPresent), and clearAllIdentityData / panic wipe clears the pins as before. Legitimate re-keying: presenting a different signing key for the same noise key is exactly the attack being blocked, so refusal is correct and permanent until the peer adopts a new noise identity (new peerID) or the user explicitly clears identity data. Repeated rejected announces follow the existing unverified-announce path (log + ignore); no retry loops or crashes. Tests: handler-level persisted-pin mismatch/match/precedence cases, an end-to-end restart+eviction test with real Ed25519 keys and a real SecureIdentityStateManager showing the attacker replay is rejected and the persisted identity is untouched while the victim re-announce is accepted, and identity-manager tests for the pinned-key refusal and the keychain round-trip of the pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
385d32063f |
Pin announce signing keys to stop mesh identity spoofing
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> |
||
|
|
ca63893197 |
Fix security audit findings: 3 critical, 7 high
A broad audit surfaced ten critical/high issues across the crypto, transport, identity, and panic-wipe layers. This fixes all ten. Critical: - Nostr DMs were unauthenticated. The NIP-17 seal was signed with a throwaway ephemeral key and the receiver never verified it, so anyone who knows a recipient's npub could forge messages (and delivery/read receipts) into an existing trusted conversation. The seal is now signed with the sender's real identity key, and the receiver verifies the seal signature and that seal.pubkey == rumor.pubkey. NOTE: this is a breaking wire-protocol change (see PR). - Public BLE messages trusted registry membership instead of the packet signature. Since senderID is attacker-controlled, any verified peer could be impersonated in public chat. A valid signature from the claimed sender is now required before any registry identity is used. - Unverified announces still persisted the announced identity, letting a replayed noisePublicKey overwrite a victim's stored signing key and nickname. persistIdentity is now gated on verification. High: - Noise decrypt trapped on a 16-19 byte ciphertext (negative prefix length after nonce extraction) — a remote crash. Now validated. - Identity-cache debounce save used Timer.scheduledTimer on a GCD queue with no run loop, so it never fired; block/verify/favorite changes only persisted on explicit forceSave. Replaced with a DispatchSourceTimer on the queue; forceSave is now serialized. - Identity-cache key load couldn't tell "missing" from a transient keychain failure and would regenerate (deleting) the key, orphaning the cache. Now uses getIdentityKeyWithResult and falls back to a session-only ephemeral key without clobbering the persisted key/cache. - BLE receive-dedup key lacked a payload digest, so post-handshake flushes (queued msgs + delivery/read acks in the same ms) were dropped as duplicates. Digest added, matching the ingress registry. - Maintenance timer was created only in init and never recreated after a panic stop/start, silently degrading the mesh until app restart. Now recreated in startServices. - Panic wipe left persisted location state (selected channel, teleport set, bookmarks) and cached per-geohash Nostr private keys behind. Both are now cleared. - Panic spawned an orphan NostrRelayManager instead of reusing .shared, splitting relay state from every other component. Now reuses .shared. Tests updated to assert the fixed behavior (announce no longer persists unverified identities; public messages require a signature; receive dedup ID includes the payload digest). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3a995e20b6 |
Extract BLE packet handlers and migrate coordinators to narrow contexts
BLEService's per-packet-type orchestration moves into owned, tested components (BLEAnnounceHandler, BLEPublicMessageHandler, BLENoisePacketHandler, BLEFileTransferHandler, BLEFragmentHandler), each taking an environment struct of closures so every queue hop stays in BLEService and the handlers are synchronously testable. Behavior is preserved verbatim, including Noise session recovery on decrypt failure and single-block UI event ordering. handleLeave/handleRequestSync stay in place as already-thin delegations. BLEService drops to 3393 lines. Four coordinators (delivery, private conversation, Nostr, public conversation) drop their unowned/weak ChatViewModel back-references for narrow @MainActor context protocols, with ChatViewModel conformances as single shared witnesses for overlapping members. Their true coupling is now an explicit, reviewable surface, and each gains a mock-context test suite covering flows previously testable only through the full view model. Delivery/read acks now also clear the router's retained-send outbox via the delivery context. New LargeTopologyTests exercise production-shaped meshes with the in-memory harness: an 8-peer relay chain with per-hop TTL decay, a 14-peer cyclic mesh with exactly-once delivery, partition/heal, and topology churn. App-layer runtime/model files updated alongside. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |