Implement Noise Protocol Framework and peer ID rotation for enhanced security and privacy

This major update replaces the basic encryption with the Noise Protocol Framework
and adds ephemeral peer ID rotation for enhanced privacy.

Key Changes:

Security Infrastructure:
- Implemented Noise Protocol Framework (XX handshake pattern)
- End-to-end encryption with forward secrecy and identity hiding
- Session management with automatic rekey support
- Channel encryption with password-derived keys

Privacy Enhancements:
- Ephemeral peer ID rotation (5-15 minute random intervals)
- Persistent identity through public key fingerprints
- Favorites and verification persist across ID rotations
- Block list based on fingerprints, not ephemeral IDs

Core Components Added:
- NoiseEncryptionService: Main encryption service
- NoiseSession: Individual peer session management
- NoiseChannelEncryption: Password-protected channel support
- SecureIdentityStateManager: Persistent identity storage
- FingerprintView: Visual fingerprint verification UI

Bug Fixes:
- Fixed handshake storm with tie-breaker mechanism
- Fixed missing connect messages during peer rotation
- Fixed delivery ACK compression issues
- Fixed race conditions in message queue
- Fixed nickname resolution for rotated peer IDs

Testing:
- Comprehensive test suite for Noise implementation
- Security validator tests
- Channel encryption tests
- Identity persistence tests
- Rate limiter tests

Documentation:
- BRING_THE_NOISE.md: Technical implementation details
- Updated WHITEPAPER.md: Simplified and focused on core innovations
- Removed temporary debug documentation

The implementation maintains backward compatibility while significantly
improving security and privacy. All existing features (channels, private
messages, favorites, blocking) work seamlessly with the new system.
This commit is contained in:
jack
2025-07-15 13:15:31 +02:00
parent 6d39222ea0
commit 3070a4d307
42 changed files with 10952 additions and 2233 deletions
@@ -40,11 +40,9 @@ class ShareViewController: SLComposeServiceViewController {
return
}
print("ShareExtension: Processing share with \(extensionItem.attachments?.count ?? 0) attachments")
// Get the page title from the compose view or extension item
let pageTitle = self.contentText ?? extensionItem.attributedContentText?.string ?? extensionItem.attributedTitle?.string
print("ShareExtension: Page title: \(pageTitle ?? "none")")
var foundURL: URL? = nil
let group = DispatchGroup()
@@ -52,20 +50,17 @@ class ShareViewController: SLComposeServiceViewController {
// IMPORTANT: Check if the NSExtensionItem itself has a URL
// Safari often provides the URL as an attributedString with a link
if let attributedText = extensionItem.attributedContentText {
print("ShareExtension: Checking attributed text for URLs")
let text = attributedText.string
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector?.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
if let firstMatch = matches?.first, let url = firstMatch.url {
print("ShareExtension: Found URL in attributed text: \(url.absoluteString)")
foundURL = url
}
}
// Only check attachments if we haven't found a URL yet
if foundURL == nil {
for (index, itemProvider) in (extensionItem.attachments ?? []).enumerated() {
print("ShareExtension: Attachment \(index) types: \(itemProvider.registeredTypeIdentifiers)")
for (_, itemProvider) in (extensionItem.attachments ?? []).enumerated() {
// Try multiple URL type identifiers that Safari might use
let urlTypes = [
@@ -81,16 +76,13 @@ class ShareViewController: SLComposeServiceViewController {
defer { group.leave() }
if let url = item as? URL {
print("ShareExtension: Found URL: \(url.absoluteString)")
foundURL = url
} else if let data = item as? Data,
let urlString = String(data: data, encoding: .utf8),
let url = URL(string: urlString) {
print("ShareExtension: Found URL from data: \(url.absoluteString)")
foundURL = url
} else if let string = item as? String,
let url = URL(string: string) {
print("ShareExtension: Found URL from string: \(url.absoluteString)")
foundURL = url
}
}
@@ -108,7 +100,6 @@ class ShareViewController: SLComposeServiceViewController {
// Check if the text is actually a URL
if let url = URL(string: text),
(url.scheme == "http" || url.scheme == "https") {
print("ShareExtension: Found URL in plain text: \(url.absoluteString)")
foundURL = url
}
}
@@ -126,7 +117,6 @@ class ShareViewController: SLComposeServiceViewController {
"title": pageTitle ?? url.host ?? "Shared Link"
]
print("ShareExtension: Saving URL share - url: \(url.absoluteString), title: \(urlData["title"] ?? "")")
if let jsonData = try? JSONSerialization.data(withJSONObject: urlData),
let jsonString = String(data: jsonData, encoding: .utf8) {
@@ -134,7 +124,6 @@ class ShareViewController: SLComposeServiceViewController {
}
} else if let title = pageTitle, !title.isEmpty {
// No URL found, just share the text
print("ShareExtension: No URL found, sharing as text: \(title)")
self?.saveToSharedDefaults(content: title, type: "text")
}
@@ -190,7 +179,6 @@ class ShareViewController: SLComposeServiceViewController {
private func saveToSharedDefaults(content: String, type: String) {
// Use app groups to share data between extension and main app
guard let userDefaults = UserDefaults(suiteName: "group.chat.bitchat") else {
print("ShareExtension: Failed to access app group UserDefaults")
return
}
@@ -199,8 +187,6 @@ class ShareViewController: SLComposeServiceViewController {
userDefaults.set(Date(), forKey: "sharedContentDate")
userDefaults.synchronize()
print("ShareExtension: Saved content of type \(type) to shared defaults")
print("ShareExtension: Content: \(content)")
// Force open the main app
self.openMainApp()