This document describes BitChat's secure storage architecture, focusing on the KeychainManager service and its integration with iOS/macOS Keychain for persisting cryptographic keys and sensitive data. This covers key generation, error handling, retry logic, and Secure Enclave integration.
For information about the cryptographic identities themselves and trust management, see Identity Management and Trust System (4.3) For details about the Noise Protocol encryption that uses these keys, see Noise Protocol Framework (4.2)
BitChat's secure storage layer provides a unified interface for persisting cryptographic keys, identity mappings, and sensitive configuration data. The architecture is built around four core components:
KeychainManagerProtocol for secure handshake operations and key retrieval bitchat/Noise/NoiseSession.swift14-17All persistent cryptographic material is stored in the system Keychain. To ensure background operation (e.g., mesh networking while the device is locked), items use kSecAttrAccessibleAfterFirstUnlock bitchat/Services/KeychainManager.swift50
Title: Secure Storage Architecture and Key Flow
Sources: bitchat/Services/KeychainManager.swift14-50 bitchat/Services/NoiseEncryptionService.swift153-160 bitchat/Identity/SecureIdentityStateManager.swift148-151 bitchat/Noise/NoiseSession.swift17 bitchat/Nostr/NostrIdentityBridge.swift71-87
The KeychainManager class implements KeychainManagerProtocol bitchat/Services/KeychainManager.swift14 and provides the primary interface for all secure storage operations. It manages identity keys, generic data, and secure clearing.
The KeychainManagerProtocol defines methods for each operation to support detailed error reporting:
| Method | Return Type | Purpose |
|---|---|---|
saveIdentityKey(_:forKey:) | Bool | Basic save with success/failure boolean bitchat/Services/KeychainManager.swift90 |
saveIdentityKeyWithResult(_:forKey:) | KeychainSaveResult | Save with detailed error classification bitchat/Services/KeychainManager.swift118 |
getIdentityKey(forKey:) | Data? | Basic read returning nil on failure bitchat/Services/KeychainManager.swift97 |
getIdentityKeyWithResult(forKey:) | KeychainReadResult | Read with detailed error classification bitchat/Services/KeychainManager.swift112 |
deleteAllKeychainData() | Bool | Panic mode: wipe all BitChat data bitchat/Services/KeychainManager.swift189 |
The keychain uses BitchatApp.bundleID as the service identifier bitchat/Services/KeychainManager.swift42 and group. + bundle ID for shared access between the main app and extensions bitchat/Services/KeychainManager.swift43 On iOS, queries attempt to use the access group first, falling back if error -34018 (missing entitlement) occurs bitchat/Services/KeychainManager.swift148-151
Title: Keychain Query Construction and Platform Logic
Sources: bitchat/Services/KeychainManager.swift41-51 bitchat/Services/KeychainManager.swift124-154
BitChat implements a classification system to distinguish expected states from critical failures (BCH-01-009).
success(Data), itemNotFound (expected), accessDenied (critical), deviceLocked (recoverable), and authenticationFailed (recoverable) bitchat/Services/KeychainManager.swift112success, duplicateItem (retryable), accessDenied, deviceLocked, and storageFull bitchat/Services/KeychainManager.swift118The saveDataWithResult method implements a retry mechanism for transient errors. It attempts the operation up to 3 times (initial + 2 retries) with a delay of 100ms and 200ms between retries using usleep bitchat/Services/KeychainManager.swift124-187
Title: Keychain Read Error Classification Flow
Sources: bitchat/Services/KeychainManager.swift112-187
The SecureIdentityStateManager provides encryption-at-rest for the identity cache, which includes social metadata and favorite relationships.
SymmetricKey named identityCacheEncryptionKey bitchat/Identity/SecureIdentityStateManager.swift151-170bitchat.identityCache.v2 bitchat/Identity/SecureIdentityStateManager.swift150Sources: bitchat/Identity/SecureIdentityStateManager.swift148-182 bitchat/Identity/IdentityModels.swift161-183
BitChat provides utilities to zero out sensitive memory (like private keys or shared secrets) before deallocation to protect against memory forensics.
secureClear(_ data: inout Data): Zeroes the underlying buffer of a Data object bitchat/Services/KeychainManager.swift221secureClear(_ string: inout String): Clears string contents securely bitchat/Services/KeychainManager.swift229In the Noise Protocol implementation, ephemeral keys and intermediate secrets are cleared as soon as the handshake transitions or the session is destroyed. The NoiseEncryptionService ensures identity keys are persisted only in the Keychain, not in plain application state bitchat/Services/NoiseEncryptionService.swift35-37 The NoiseCipherState also explicitly calls clearSensitiveData upon deinitialization bitchat/Noise/NoiseProtocol.swift154-156
Sources: bitchat/Services/KeychainManager.swift221-231 bitchat/Noise/NoiseProtocol.swift154-156 bitchat/Services/NoiseEncryptionService.swift27-39
The NostrIdentityBridge manages a stable device seed used to derive unlinkable per-geohash identities. This seed is stored with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to ensure it remains stable even if the device is locked during background mesh operations bitchat/Nostr/NostrIdentityBridge.swift71-87
HMAC-SHA256(deviceSeed, geohash) bitchat/Nostr/NostrIdentityBridge.swift119derivedIdentityCache to avoid repeated cryptographic operations and Keychain I/O during UI rendering bitchat/Nostr/NostrIdentityBridge.swift100-105Sources: bitchat/Nostr/NostrIdentityBridge.swift67-145
The deleteAllKeychainData() method implements a comprehensive wipe of all application data bitchat/Services/KeychainManager.swift189
chat.bitchat.nostr, chat.bitchat.favorites) to delete all associated items bitchat/Services/KeychainManager.swift190-201 bitchat/Nostr/NostrIdentityBridge.swift51-55 bitchat/Services/FavoritesPersistenceService.swift29identity_ using deleteIdentityKey bitchat/Services/KeychainManager.swift102-106SecureIdentityStateManager.clearAllIdentityData() wipes the in-memory cache and associated keychain entries bitchat/Identity/SecureIdentityStateManager.swift126NostrIdentityBridge.clearAllAssociations() clears the derivedIdentityCache and deviceSeedCache to ensure post-panic messages do not use pre-panic keys bitchat/Nostr/NostrIdentityBridge.swift51-65Sources: bitchat/Services/KeychainManager.swift189-219 bitchat/Identity/SecureIdentityStateManager.swift126 bitchat/Services/KeychainManager.swift102-106 bitchat/Nostr/NostrIdentityBridge.swift51-65
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.