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:
jack
2025-07-22 15:40:13 +02:00
parent 49daa995cc
commit aaa7e2bf28
13 changed files with 269 additions and 112 deletions
@@ -1,5 +1,5 @@
//
// SecurityLogger.swift
// SecureLogger.swift
// bitchat
//
// This is free and unencumbered software released into the public domain.
@@ -11,7 +11,7 @@ import os.log
/// Centralized security-aware logging framework
/// Provides safe logging that filters sensitive data and security events
class SecurityLogger {
class SecureLogger {
// MARK: - Log Categories
@@ -23,6 +23,22 @@ class SecurityLogger {
static let session = OSLog(subsystem: subsystem, category: "session")
static let security = OSLog(subsystem: subsystem, category: "security")
// MARK: - Cached Regex Patterns
private static let fingerprintPattern = #/[a-fA-F0-9]{64}/#
private static let base64Pattern = #/[A-Za-z0-9+/]{40,}={0,2}/#
private static let passwordPattern = #/password["\s:=]+["']?[^"'\s]+["']?/#
private static let peerIDPattern = #/peerID: ([a-zA-Z0-9]{8})[a-zA-Z0-9]+/#
// MARK: - Sanitization Cache
private static let sanitizationCache: NSCache<NSString, NSString> = {
let cache = NSCache<NSString, NSString>()
cache.countLimit = 100 // Keep last 100 sanitized strings
return cache
}()
private static let cacheQueue = DispatchQueue(label: "chat.bitchat.securelogger.cache", attributes: .concurrent)
// MARK: - Log Levels
enum LogLevel {
@@ -80,18 +96,24 @@ class SecurityLogger {
// MARK: - Public Logging Methods
/// Log a security event
static func logSecurityEvent(_ event: SecurityEvent, level: LogLevel = .info) {
static func logSecurityEvent(_ event: SecurityEvent, level: LogLevel = .info,
file: String = #file, line: Int = #line, function: String = #function) {
let location = formatLocation(file: file, line: line, function: function)
let message = "\(location) \(event.message)"
#if DEBUG
os_log("%{public}@", log: security, type: level.osLogType, event.message)
os_log("%{public}@", log: security, type: level.osLogType, message)
#else
// In release, use private logging to prevent sensitive data exposure
os_log("%{private}@", log: security, type: level.osLogType, event.message)
os_log("%{private}@", log: security, type: level.osLogType, message)
#endif
}
/// Log general messages with automatic sensitive data filtering
static func log(_ message: String, category: OSLog = noise, level: LogLevel = .debug) {
let sanitized = sanitize(message)
static func log(_ message: String, category: OSLog = noise, level: LogLevel = .debug,
file: String = #file, line: Int = #line, function: String = #function) {
let location = formatLocation(file: file, line: line, function: function)
let sanitized = sanitize("\(location) \(message)")
#if DEBUG
os_log("%{public}@", log: category, type: level.osLogType, sanitized)
@@ -104,48 +126,70 @@ class SecurityLogger {
}
/// Log errors with context
static func logError(_ error: Error, context: String, category: OSLog = noise) {
static func logError(_ error: Error, context: String, category: OSLog = noise,
file: String = #file, line: Int = #line, function: String = #function) {
let location = formatLocation(file: file, line: line, function: function)
let sanitized = sanitize(context)
let errorDesc = sanitize(error.localizedDescription)
#if DEBUG
os_log("Error in %{public}@: %{public}@", log: category, type: .error, sanitized, errorDesc)
os_log("%{public}@ Error in %{public}@: %{public}@", log: category, type: .error, location, sanitized, errorDesc)
#else
os_log("Error in %{private}@: %{private}@", log: category, type: .error, sanitized, errorDesc)
os_log("%{private}@ Error in %{private}@: %{private}@", log: category, type: .error, location, sanitized, errorDesc)
#endif
}
// MARK: - Private Helpers
/// Format location information for logging
private static func formatLocation(file: String, line: Int, function: String) -> String {
let fileName = (file as NSString).lastPathComponent
return "[\(fileName):\(line) \(function)]"
}
/// Sanitize strings to remove potentially sensitive data
private static func sanitize(_ input: String) -> String {
let key = input as NSString
// Check cache first
var cachedValue: String?
cacheQueue.sync {
cachedValue = sanitizationCache.object(forKey: key) as String?
}
if let cached = cachedValue {
return cached
}
// Perform sanitization
var sanitized = input
// Remove full fingerprints (keep first 8 chars for debugging)
let fingerprintPattern = #/[a-fA-F0-9]{64}/#
sanitized = sanitized.replacing(fingerprintPattern) { match in
let fingerprint = String(match.output)
return String(fingerprint.prefix(8)) + "..."
}
// Remove base64 encoded data that might be keys
let base64Pattern = #/[A-Za-z0-9+/]{40,}={0,2}/#
sanitized = sanitized.replacing(base64Pattern) { _ in
"<base64-data>"
}
// Remove potential passwords (assuming they're in quotes or after "password:")
let passwordPattern = #/password["\s:=]+["']?[^"'\s]+["']?/#
sanitized = sanitized.replacing(passwordPattern) { _ in
"password: <redacted>"
}
// Truncate peer IDs to first 8 characters
let peerIDPattern = #/peerID: ([a-zA-Z0-9]{8})[a-zA-Z0-9]+/#
sanitized = sanitized.replacing(peerIDPattern) { match in
"peerID: \(match.1)..."
}
// Cache the result
cacheQueue.async(flags: .barrier) {
sanitizationCache.setObject(sanitized as NSString, forKey: key)
}
return sanitized
}
@@ -158,39 +202,45 @@ class SecurityLogger {
// MARK: - Convenience Extensions
extension SecurityLogger {
extension SecureLogger {
/// Log handshake events
static func logHandshake(_ phase: String, peerID: String, success: Bool = true) {
static func logHandshake(_ phase: String, peerID: String, success: Bool = true,
file: String = #file, line: Int = #line, function: String = #function) {
if success {
log("Handshake \(phase) with peer: \(peerID)", category: session, level: .info)
log("Handshake \(phase) with peer: \(peerID)", category: session, level: .info,
file: file, line: line, function: function)
} else {
log("Handshake \(phase) failed with peer: \(peerID)", category: session, level: .warning)
log("Handshake \(phase) failed with peer: \(peerID)", category: session, level: .warning,
file: file, line: line, function: function)
}
}
/// Log encryption operations
static func logEncryption(_ operation: String, success: Bool = true) {
static func logEncryption(_ operation: String, success: Bool = true,
file: String = #file, line: Int = #line, function: String = #function) {
let level: LogLevel = success ? .debug : .error
log("Encryption operation '\(operation)' \(success ? "succeeded" : "failed")",
category: encryption, level: level)
category: encryption, level: level, file: file, line: line, function: function)
}
/// Log key management operations
static func logKeyOperation(_ operation: String, keyType: String, success: Bool = true) {
static func logKeyOperation(_ operation: String, keyType: String, success: Bool = true,
file: String = #file, line: Int = #line, function: String = #function) {
let level: LogLevel = success ? .info : .error
log("Key operation '\(operation)' for \(keyType) \(success ? "succeeded" : "failed")",
category: keychain, level: level)
category: keychain, level: level, file: file, line: line, function: function)
}
}
// MARK: - Migration Helper
/// Helper to migrate from print statements to SecurityLogger
/// Helper to migrate from print statements to SecureLogger
/// Usage: Replace print(...) with secureLog(...)
func secureLog(_ items: Any..., separator: String = " ", terminator: String = "\n") {
func secureLog(_ items: Any..., separator: String = " ", terminator: String = "\n",
file: String = #file, line: Int = #line, function: String = #function) {
#if DEBUG
let message = items.map { String(describing: $0) }.joined(separator: separator)
SecurityLogger.log(message, level: .debug)
SecureLogger.log(message, level: .debug, file: file, line: line, function: function)
#endif
}