mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 02:05:19 +00:00
Enhance logging framework and fix build issues
- Rename SecurityLogger to SecureLogger for better clarity - Add file:line:function tracking to all log entries - Optimize logging with pre-compiled regex patterns and NSCache - Add comprehensive logging for critical protocol flow points - Fix iOS entitlements to include Bluetooth permission - Fix VersionHello field name and optional chaining issues - Fix Package.swift resource warnings - Fix test compilation errors with proper type annotations
This commit is contained in:
@@ -58,7 +58,10 @@ class NoiseChannelEncryption {
|
||||
}
|
||||
|
||||
// Store in keychain
|
||||
_ = KeychainManager.shared.saveChannelPassword(password, for: channel)
|
||||
let saved = KeychainManager.shared.saveChannelPassword(password, for: channel)
|
||||
if saved {
|
||||
SecureLogger.logKeyOperation("set", keyType: "channel key for \(channel)", success: true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get channel key
|
||||
@@ -84,7 +87,10 @@ class NoiseChannelEncryption {
|
||||
self.channelKeys.removeValue(forKey: channel)
|
||||
}
|
||||
|
||||
_ = KeychainManager.shared.deleteChannelPassword(for: channel)
|
||||
let deleted = KeychainManager.shared.deleteChannelPassword(for: channel)
|
||||
if deleted {
|
||||
SecureLogger.logKeyOperation("remove", keyType: "channel key for \(channel)", success: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Replay Protection
|
||||
@@ -119,6 +125,7 @@ class NoiseChannelEncryption {
|
||||
/// Encrypt message for a channel
|
||||
func encryptChannelMessage(_ message: String, for channel: String) throws -> Data {
|
||||
guard let key = getChannelKey(for: channel) else {
|
||||
SecureLogger.log("Channel encryption failed - no key for channel: \(channel)", category: SecureLogger.encryption, level: .error)
|
||||
throw NoiseChannelError.noChannelKey
|
||||
}
|
||||
|
||||
@@ -137,6 +144,7 @@ class NoiseChannelEncryption {
|
||||
/// Decrypt channel message
|
||||
func decryptChannelMessage(_ encryptedData: Data, for channel: String) throws -> String {
|
||||
guard let key = getChannelKey(for: channel) else {
|
||||
SecureLogger.log("Channel decryption failed - no key for channel: \(channel)", category: SecureLogger.encryption, level: .error)
|
||||
throw NoiseChannelError.noChannelKey
|
||||
}
|
||||
|
||||
@@ -157,6 +165,7 @@ class NoiseChannelEncryption {
|
||||
let decryptedData = try ChaChaPoly.open(sealedBox, using: key)
|
||||
|
||||
guard let message = String(data: decryptedData, encoding: .utf8) else {
|
||||
SecureLogger.log("Channel decryption failed - invalid UTF8 for channel: \(channel)", category: SecureLogger.encryption, level: .error)
|
||||
throw NoiseChannelError.decryptionFailed
|
||||
}
|
||||
|
||||
@@ -192,12 +201,15 @@ class NoiseChannelEncryption {
|
||||
|
||||
// Verify timestamp is recent (within 5 minutes)
|
||||
let age = Date().timeIntervalSince(packet.timestamp)
|
||||
guard age < 300 else { return nil }
|
||||
guard age < 300 else {
|
||||
SecureLogger.log("Expired channel key packet for channel: \(packet.channel), age: \(age)s", category: SecureLogger.security, level: .warning)
|
||||
return nil
|
||||
}
|
||||
|
||||
return keyQueue.sync(flags: .barrier) {
|
||||
// Check for replay attack
|
||||
if receivedNonces.contains(packet.nonce) {
|
||||
SecurityLogger.logSecurityEvent(.replayAttackDetected(channel: packet.channel), level: .warning)
|
||||
SecureLogger.logSecurityEvent(.replayAttackDetected(channel: packet.channel), level: .warning)
|
||||
return nil // This nonce was already processed
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ class NoiseCipherState {
|
||||
nonce += 1
|
||||
|
||||
// Log high nonce values that might indicate issues
|
||||
if currentNonce > 100 {
|
||||
if currentNonce > 1000000 {
|
||||
SecureLogger.log("High nonce value detected: \(currentNonce) - consider rekeying", category: SecureLogger.encryption, level: .warning)
|
||||
}
|
||||
|
||||
return sealedBox.ciphertext + sealedBox.tag
|
||||
@@ -122,7 +123,8 @@ class NoiseCipherState {
|
||||
)
|
||||
|
||||
// Log high nonce values that might indicate issues
|
||||
if currentNonce > 100 {
|
||||
if currentNonce > 1000000 {
|
||||
SecureLogger.log("High nonce value detected: \(currentNonce) - consider rekeying", category: SecureLogger.encryption, level: .warning)
|
||||
}
|
||||
|
||||
do {
|
||||
@@ -131,6 +133,7 @@ class NoiseCipherState {
|
||||
return plaintext
|
||||
} catch {
|
||||
// Log authentication failures with nonce info
|
||||
SecureLogger.log("Decryption failed at nonce \(currentNonce)", category: SecureLogger.encryption, level: .error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -404,6 +407,7 @@ class NoiseHandshakeState {
|
||||
do {
|
||||
remoteEphemeralPublic = try NoiseHandshakeState.validatePublicKey(ephemeralData)
|
||||
} catch {
|
||||
SecureLogger.log("Invalid ephemeral public key received", category: SecureLogger.security, level: .warning)
|
||||
throw NoiseError.invalidMessage
|
||||
}
|
||||
symmetricState.mixHash(ephemeralData)
|
||||
@@ -420,6 +424,7 @@ class NoiseHandshakeState {
|
||||
let decrypted = try symmetricState.decryptAndHash(staticData)
|
||||
remoteStaticPublic = try NoiseHandshakeState.validatePublicKey(decrypted)
|
||||
} catch {
|
||||
SecureLogger.logSecurityEvent(.authenticationFailed(peerID: "Unknown - handshake"), level: .error)
|
||||
throw NoiseError.authenticationFailure
|
||||
}
|
||||
|
||||
@@ -604,7 +609,7 @@ extension NoiseHandshakeState {
|
||||
|
||||
// Check against known bad points
|
||||
if lowOrderPoints.contains(keyData) {
|
||||
SecurityLogger.logSecurityEvent(.invalidKey(reason: "Low-order point detected"), level: .warning)
|
||||
SecureLogger.logSecurityEvent(.invalidKey(reason: "Low-order point detected"), level: .warning)
|
||||
throw NoiseError.invalidPublicKey
|
||||
}
|
||||
|
||||
@@ -614,6 +619,7 @@ extension NoiseHandshakeState {
|
||||
return publicKey
|
||||
} catch {
|
||||
// If CryptoKit rejects it, it's invalid
|
||||
SecureLogger.logSecurityEvent(.invalidKey(reason: "CryptoKit validation failed"), level: .warning)
|
||||
throw NoiseError.invalidPublicKey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ class NoiseRateLimiter {
|
||||
// Check global rate limit first
|
||||
globalHandshakeTimestamps = globalHandshakeTimestamps.filter { $0 > oneMinuteAgo }
|
||||
if globalHandshakeTimestamps.count >= NoiseSecurityConstants.maxGlobalHandshakesPerMinute {
|
||||
SecureLogger.log("Global handshake rate limit exceeded: \(globalHandshakeTimestamps.count)/\(NoiseSecurityConstants.maxGlobalHandshakesPerMinute) per minute", category: SecureLogger.security, level: .warning)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -175,6 +176,7 @@ class NoiseRateLimiter {
|
||||
timestamps = timestamps.filter { $0 > oneMinuteAgo }
|
||||
|
||||
if timestamps.count >= NoiseSecurityConstants.maxHandshakesPerMinute {
|
||||
SecureLogger.log("Per-peer handshake rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxHandshakesPerMinute) per minute", category: SecureLogger.security, level: .warning)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -194,6 +196,7 @@ class NoiseRateLimiter {
|
||||
// Check global rate limit first
|
||||
globalMessageTimestamps = globalMessageTimestamps.filter { $0 > oneSecondAgo }
|
||||
if globalMessageTimestamps.count >= NoiseSecurityConstants.maxGlobalMessagesPerSecond {
|
||||
SecureLogger.log("Global message rate limit exceeded: \(globalMessageTimestamps.count)/\(NoiseSecurityConstants.maxGlobalMessagesPerSecond) per second", category: SecureLogger.security, level: .warning)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -202,6 +205,7 @@ class NoiseRateLimiter {
|
||||
timestamps = timestamps.filter { $0 > oneSecondAgo }
|
||||
|
||||
if timestamps.count >= NoiseSecurityConstants.maxMessagesPerSecond {
|
||||
SecureLogger.log("Per-peer message rate limit exceeded for \(peerID): \(timestamps.count)/\(NoiseSecurityConstants.maxMessagesPerSecond) per second", category: SecureLogger.security, level: .warning)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,8 @@ class NoiseSession {
|
||||
state = .established
|
||||
handshakeState = nil // Clear handshake state
|
||||
|
||||
SecureLogger.logSecurityEvent(.handshakeCompleted(peerID: peerID))
|
||||
|
||||
return nil
|
||||
} else {
|
||||
// Generate response
|
||||
@@ -148,6 +150,8 @@ class NoiseSession {
|
||||
|
||||
state = .established
|
||||
handshakeState = nil // Clear handshake state
|
||||
|
||||
SecureLogger.logSecurityEvent(.handshakeCompleted(peerID: peerID))
|
||||
}
|
||||
|
||||
return response
|
||||
@@ -208,12 +212,17 @@ class NoiseSession {
|
||||
|
||||
func reset() {
|
||||
sessionQueue.sync(flags: .barrier) {
|
||||
let wasEstablished = state == .established
|
||||
state = .uninitialized
|
||||
handshakeState = nil
|
||||
sendCipher = nil
|
||||
receiveCipher = nil
|
||||
sentHandshakeMessages.removeAll()
|
||||
handshakeHash = nil
|
||||
|
||||
if wasEstablished {
|
||||
SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,6 +264,9 @@ class NoiseSessionManager {
|
||||
|
||||
func removeSession(for peerID: String) {
|
||||
managerQueue.sync(flags: .barrier) {
|
||||
if let session = sessions[peerID], session.isEstablished() {
|
||||
SecureLogger.logSecurityEvent(.sessionExpired(peerID: peerID))
|
||||
}
|
||||
_ = sessions.removeValue(forKey: peerID)
|
||||
}
|
||||
}
|
||||
@@ -267,7 +279,7 @@ class NoiseSessionManager {
|
||||
sessions[newPeerID] = session
|
||||
_ = sessions.removeValue(forKey: oldPeerID)
|
||||
|
||||
SecurityLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecurityLogger.noise, level: .info)
|
||||
SecureLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecureLogger.noise, level: .info)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,6 +319,7 @@ class NoiseSessionManager {
|
||||
} catch {
|
||||
// Clean up failed session
|
||||
_ = sessions.removeValue(forKey: peerID)
|
||||
SecureLogger.logSecurityEvent(.handshakeFailed(peerID: peerID, error: error.localizedDescription), level: .error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -323,6 +336,7 @@ class NoiseSessionManager {
|
||||
if existing.isEstablished() {
|
||||
// Don't destroy our working session just because the other side is confused
|
||||
// They should detect the established session through successful message exchange
|
||||
SecureLogger.log("Rejecting handshake attempt - session already established with \(peerID)", category: SecureLogger.session, level: .debug)
|
||||
throw NoiseSessionError.alreadyEstablished
|
||||
} else {
|
||||
// If we're in the middle of a handshake and receive a new initiation,
|
||||
@@ -376,6 +390,7 @@ class NoiseSessionManager {
|
||||
self?.onSessionFailed?(peerID, error)
|
||||
}
|
||||
|
||||
SecureLogger.logSecurityEvent(.handshakeFailed(peerID: peerID, error: error.localizedDescription), level: .error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -428,6 +443,8 @@ class NoiseSessionManager {
|
||||
}
|
||||
|
||||
func initiateRekey(for peerID: String) throws {
|
||||
SecureLogger.logSecurityEvent(.keyRotation(channel: peerID))
|
||||
|
||||
// Remove old session
|
||||
removeSession(for: peerID)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user