mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 15:05:20 +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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user