// // MockKeychain.swift // bitchat // // This is free and unencumbered software released into the public domain. // For more information, see // import Foundation import BitFoundation // Kept local until the test-helper module is split out. 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 } func getIdentityKey(forKey key: String) -> Data? { storage[key] } func deleteIdentityKey(forKey key: String) -> Bool { storage.removeValue(forKey: key) return true } func deleteAllKeychainData() -> Bool { storage.removeAll() serviceStorage.removeAll() return true } func secureClear(_ data: inout Data) { data = Data() } func secureClear(_ string: inout String) { string = "" } func verifyIdentityKeyExists() -> Bool { 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?) { if serviceStorage[service] == nil { serviceStorage[service] = [:] } serviceStorage[service]?[key] = data } func load(key: String, service: String) -> Data? { serviceStorage[service]?[key] } func delete(key: String, service: String) { serviceStorage[service]?.removeValue(forKey: key) } func deleteAll(service: String) { serviceStorage.removeValue(forKey: service) } } /// Typealias for backwards compatibility with tests using MockKeychainHelper typealias MockKeychainHelper = MockKeychain /// Mock keychain that tracks secureClear calls for testing DH secret clearing final class TrackingMockKeychain: KeychainManagerProtocol { private var storage: [String: Data] = [:] private var serviceStorage: [String: [String: Data]] = [:] /// Thread-safe counter for secureClear calls private let lock = NSLock() 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() } return _secureClearDataCallCount } var secureClearStringCallCount: Int { lock.lock() defer { lock.unlock() } return _secureClearStringCallCount } var totalSecureClearCallCount: Int { return secureClearDataCallCount + secureClearStringCallCount } func resetCounts() { lock.lock() defer { lock.unlock() } _secureClearDataCallCount = 0 _secureClearStringCallCount = 0 } func saveIdentityKey(_ keyData: Data, forKey key: String) -> Bool { storage[key] = keyData return true } func getIdentityKey(forKey key: String) -> Data? { storage[key] } func deleteIdentityKey(forKey key: String) -> Bool { storage.removeValue(forKey: key) return true } func deleteAllKeychainData() -> Bool { storage.removeAll() serviceStorage.removeAll() return true } func secureClear(_ data: inout Data) { lock.lock() _secureClearDataCallCount += 1 lock.unlock() data = Data() } func secureClear(_ string: inout String) { lock.lock() _secureClearStringCallCount += 1 lock.unlock() string = "" } func verifyIdentityKeyExists() -> Bool { 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] = [:] } serviceStorage[service]?[key] = data } func load(key: String, service: String) -> Data? { serviceStorage[service]?[key] } func delete(key: String, service: String) { serviceStorage[service]?.removeValue(forKey: key) } func deleteAll(service: String) { serviceStorage.removeValue(forKey: service) } }