mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 18:05:19 +00:00
fix: clear DH shared secrets after Noise handshake operations
Added secureClear() calls for all 6 DH operations in NoiseProtocol.swift to properly clear sensitive shared secrets from memory after use: - writeMessage(): .es initiator/responder, .se initiator/responder - performDHOperation(): .ee and .ss operations This fixes a forward secrecy vulnerability where shared secrets could persist in memory after handshake completion. Also adds: - TrackingMockKeychain to count secureClear calls in tests - 3 new tests verifying secureClear is called during handshake 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,3 +65,90 @@ final class MockKeychain: KeychainManagerProtocol {
|
||||
|
||||
/// 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user