mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
fix(security): improve keychain error handling [BCH-01-009]
Add proper error classification to distinguish expected states (item not found) from critical failures (access denied, storage full) and recoverable errors (device locked, auth failed). Changes: - Add KeychainReadResult and KeychainSaveResult enums with error types - Add getIdentityKeyWithResult/saveIdentityKeyWithResult protocol methods - Implement retry logic with exponential backoff for transient errors - Add consistent SecureLogger logging for all keychain operations - Update NoiseEncryptionService identity loading to handle errors gracefully - Update MockKeychain and TrackingMockKeychain with error simulation - Add comprehensive KeychainErrorHandlingTests (18 new tests) Security: Applications can now properly detect and respond to critical keychain failures, improving resilience during device lock states and providing actionable diagnostics for access issues. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,10 @@ final class MockKeychain: KeychainManagerProtocol {
|
||||
private var storage: [String: Data] = [:]
|
||||
private var serviceStorage: [String: [String: Data]] = [:]
|
||||
|
||||
// BCH-01-009: Configurable error simulation for testing
|
||||
var simulatedReadError: KeychainReadResult?
|
||||
var simulatedSaveError: KeychainSaveResult?
|
||||
|
||||
func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool {
|
||||
storage[key] = keyData
|
||||
return true
|
||||
@@ -45,6 +49,25 @@ final class MockKeychain: KeychainManagerProtocol {
|
||||
storage["identity_noiseStaticKey"] != nil
|
||||
}
|
||||
|
||||
// BCH-01-009: New methods with proper error classification
|
||||
func getIdentityKeyWithResult(forKey key: String) -> KeychainReadResult {
|
||||
if let simulated = simulatedReadError {
|
||||
return simulated
|
||||
}
|
||||
if let data = storage[key] {
|
||||
return .success(data)
|
||||
}
|
||||
return .itemNotFound
|
||||
}
|
||||
|
||||
func saveIdentityKeyWithResult(_ keyData: Data, forKey key: String) -> KeychainSaveResult {
|
||||
if let simulated = simulatedSaveError {
|
||||
return simulated
|
||||
}
|
||||
storage[key] = keyData
|
||||
return .success
|
||||
}
|
||||
|
||||
// MARK: - Generic Data Storage (consolidated from KeychainHelper)
|
||||
|
||||
func save(key: String, data: Data, service: String, accessible: CFString?) {
|
||||
@@ -76,6 +99,10 @@ final class TrackingMockKeychain: KeychainManagerProtocol {
|
||||
private var _secureClearDataCallCount = 0
|
||||
private var _secureClearStringCallCount = 0
|
||||
|
||||
// BCH-01-009: Configurable error simulation for testing
|
||||
var simulatedReadError: KeychainReadResult?
|
||||
var simulatedSaveError: KeychainSaveResult?
|
||||
|
||||
var secureClearDataCallCount: Int {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
@@ -137,6 +164,25 @@ final class TrackingMockKeychain: KeychainManagerProtocol {
|
||||
storage["identity_noiseStaticKey"] != nil
|
||||
}
|
||||
|
||||
// BCH-01-009: New methods with proper error classification
|
||||
func getIdentityKeyWithResult(forKey key: String) -> KeychainReadResult {
|
||||
if let simulated = simulatedReadError {
|
||||
return simulated
|
||||
}
|
||||
if let data = storage[key] {
|
||||
return .success(data)
|
||||
}
|
||||
return .itemNotFound
|
||||
}
|
||||
|
||||
func saveIdentityKeyWithResult(_ keyData: Data, forKey key: String) -> KeychainSaveResult {
|
||||
if let simulated = simulatedSaveError {
|
||||
return simulated
|
||||
}
|
||||
storage[key] = keyData
|
||||
return .success
|
||||
}
|
||||
|
||||
func save(key: String, data: Data, service: String, accessible: CFString?) {
|
||||
if serviceStorage[service] == nil {
|
||||
serviceStorage[service] = [:]
|
||||
|
||||
Reference in New Issue
Block a user