mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 03:05:19 +00:00
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>