diff --git a/bitchat/Noise/NoiseChannelKeyRotation.swift b/bitchat/Noise/NoiseChannelKeyRotation.swift index 316cb42c..6af75836 100644 --- a/bitchat/Noise/NoiseChannelKeyRotation.swift +++ b/bitchat/Noise/NoiseChannelKeyRotation.swift @@ -293,37 +293,4 @@ class NoiseChannelKeyRotation { _ = KeychainManager.shared.deleteChannelPassword(for: epochKey) } } -} - -// MARK: - Future Double Ratchet Support - -/// Placeholder for full Double Ratchet implementation -/// This would handle per-message key derivation and ratcheting -protocol DoubleRatchetProtocol { - /// Initialize a new ratchet session - func initializeRatchet(sharedSecret: Data, isInitiator: Bool) throws - - /// Ratchet forward and get next message key - func ratchetEncrypt(_ plaintext: Data) throws -> (ciphertext: Data, header: Data) - - /// Ratchet forward using received header and decrypt - func ratchetDecrypt(_ ciphertext: Data, header: Data) throws -> Data -} - -/// Message header for Double Ratchet (future use) -struct RatchetHeader: Codable { - let publicKey: Data // Ephemeral public key - let previousChainLength: UInt32 - let messageNumber: UInt32 -} - -/// Placeholder for full implementation -class ChannelDoubleRatchet { - // This would implement the full Double Ratchet algorithm - // adapted for multi-party channels - // Challenges: - // - Sender keys for multi-party - // - Out-of-order delivery - // - State synchronization - // - Performance with many members } \ No newline at end of file diff --git a/bitchat/Noise/NoisePostQuantum.swift b/bitchat/Noise/NoisePostQuantum.swift deleted file mode 100644 index b53add9f..00000000 --- a/bitchat/Noise/NoisePostQuantum.swift +++ /dev/null @@ -1,285 +0,0 @@ -// -// NoisePostQuantum.swift -// bitchat -// -// This is free and unencumbered software released into the public domain. -// For more information, see -// - -import Foundation -import CryptoKit - -// MARK: - Post-Quantum Cryptography Framework - -/// Framework for integrating post-quantum algorithms with Noise Protocol -/// Currently a placeholder until PQ libraries are available in Swift/iOS -protocol PostQuantumKeyExchange { - associatedtype PublicKey - associatedtype PrivateKey - associatedtype SharedSecret - - /// Generate a new keypair - static func generateKeyPair() throws -> (publicKey: PublicKey, privateKey: PrivateKey) - - /// Derive shared secret (for initiator) - static func encapsulate(remotePublicKey: PublicKey) throws -> (sharedSecret: SharedSecret, ciphertext: Data) - - /// Derive shared secret (for responder) - static func decapsulate(ciphertext: Data, privateKey: PrivateKey) throws -> SharedSecret - - /// Get size requirements - static var publicKeySize: Int { get } - static var privateKeySize: Int { get } - static var ciphertextSize: Int { get } - static var sharedSecretSize: Int { get } -} - -// MARK: - Hybrid Key Exchange - -/// Combines classical (Curve25519) with post-quantum algorithms -class HybridNoiseKeyExchange { - - enum Algorithm { - case classicalOnly // Current: Curve25519 only - case hybridKyber768 // Future: Curve25519 + Kyber768 - case hybridKyber1024 // Future: Curve25519 + Kyber1024 - - var name: String { - switch self { - case .classicalOnly: - return "25519" - case .hybridKyber768: - return "25519+Kyber768" - case .hybridKyber1024: - return "25519+Kyber1024" - } - } - - var isPostQuantum: Bool { - switch self { - case .classicalOnly: - return false - case .hybridKyber768, .hybridKyber1024: - return true - } - } - } - - struct HybridPublicKey { - let classical: Curve25519.KeyAgreement.PublicKey - let postQuantum: Data? // Future: actual PQ public key - - var serialized: Data { - var data = classical.rawRepresentation - if let pq = postQuantum { - data.append(pq) - } - return data - } - } - - struct HybridPrivateKey { - let classical: Curve25519.KeyAgreement.PrivateKey - let postQuantum: Data? // Future: actual PQ private key - } - - struct HybridSharedSecret { - let classical: SharedSecret - let postQuantum: Data? // Future: actual PQ shared secret - - /// Combine both secrets using KDF - func combinedSecret() -> SymmetricKey { - var combinedData = classical.withUnsafeBytes { Data($0) } - - if let pq = postQuantum { - combinedData.append(pq) - } - - // Use HKDF to combine secrets - let hash = SHA256.hash(data: combinedData) - return SymmetricKey(data: Data(hash)) - } - } - - // MARK: - Key Generation - - static func generateKeyPair(algorithm: Algorithm) throws -> (publicKey: HybridPublicKey, privateKey: HybridPrivateKey) { - // Generate classical keypair - let classicalPrivate = Curve25519.KeyAgreement.PrivateKey() - let classicalPublic = classicalPrivate.publicKey - - // Generate PQ keypair when available - let pqPublic: Data? = nil - let pqPrivate: Data? = nil - - switch algorithm { - case .classicalOnly: - break // No PQ component - - case .hybridKyber768, .hybridKyber1024: - // Future: Generate Kyber keypair - // let (pqPub, pqPriv) = try KyberKeyExchange.generateKeyPair() - // pqPublic = pqPub.serialized - // pqPrivate = pqPriv.serialized - break - } - - return ( - HybridPublicKey(classical: classicalPublic, postQuantum: pqPublic), - HybridPrivateKey(classical: classicalPrivate, postQuantum: pqPrivate) - ) - } - - // MARK: - Key Agreement - - static func performKeyAgreement( - localPrivate: HybridPrivateKey, - remotePublic: HybridPublicKey, - algorithm: Algorithm - ) throws -> HybridSharedSecret { - // Perform classical ECDH - let classicalShared = try localPrivate.classical.sharedSecretFromKeyAgreement( - with: remotePublic.classical - ) - - // Perform PQ key agreement when available - let pqShared: Data? = nil - - switch algorithm { - case .classicalOnly: - break // No PQ component - - case .hybridKyber768, .hybridKyber1024: - // Future: Perform Kyber decapsulation - // if let pqCiphertext = remotePublic.postQuantum, - // let pqPrivateKey = localPrivate.postQuantum { - // pqShared = try KyberKeyExchange.decapsulate( - // ciphertext: pqCiphertext, - // privateKey: pqPrivateKey - // ) - // } - break - } - - return HybridSharedSecret(classical: classicalShared, postQuantum: pqShared) - } -} - -// MARK: - Modified Noise Pattern for PQ - -/// Extended Noise handshake pattern for post-quantum -/// Based on Noise PQ patterns: https://github.com/noiseprotocol/noise_pq_spec -struct NoisePQHandshakePattern { - // Pattern modifiers for PQ - enum Modifier { - case pq1 // First message includes PQ KEM - case pq2 // Second message includes PQ KEM - case pq3 // Third message includes PQ KEM - } - - // Example: XXpq1 pattern (XX with PQ in first message) - // -> e, epq - // <- e, ee, eepq, s, es - // -> s, se - - // This would modify the Noise XX pattern to include - // post-quantum key encapsulation material -} - -// MARK: - Migration Support - -/// Helps transition from classical to post-quantum crypto -class NoiseProtocolMigration { - - enum MigrationPhase { - case classicalOnly // Current state - case hybridOptional // Support both, prefer hybrid - case hybridRequired // Require hybrid mode - case postQuantumOnly // Future: PQ only - } - - struct MigrationConfig { - let currentPhase: MigrationPhase - let preferredAlgorithm: HybridNoiseKeyExchange.Algorithm - let acceptedAlgorithms: Set - let migrationDeadline: Date? - } - - /// Check if a peer supports post-quantum - static func checkPQSupport(peerVersion: String) -> Bool { - // Future: Parse peer capabilities - // For now, assume no PQ support - return false - } - - /// Get migration configuration - static func getMigrationConfig() -> MigrationConfig { - // Current configuration: classical only - return MigrationConfig( - currentPhase: .classicalOnly, - preferredAlgorithm: .classicalOnly, - acceptedAlgorithms: [.classicalOnly], - migrationDeadline: nil - ) - } -} - -// MARK: - Future Implementation Notes - -/* - Post-Quantum Integration Plan: - - 1. Wait for stable Swift PQ libraries (e.g., SwiftOQS) - 2. Implement Kyber768/1024 wrapper conforming to PostQuantumKeyExchange - 3. Update Noise handshake to support hybrid mode - 4. Add capability negotiation in protocol - 5. Implement gradual rollout with fallback - - Challenges: - - Increased message sizes (Kyber768 public key ~1184 bytes) - - Performance impact on mobile devices - - Battery usage considerations - - Backward compatibility - - Library availability and maintenance - - Timeline estimate: - - PQ libraries stable in Swift: 2025-2026 - - Initial hybrid implementation: 2026 - - Full deployment: 2027+ - */ - -// MARK: - Testing Support - -#if DEBUG -/// Mock PQ implementation for testing -class MockPostQuantumKeyExchange: PostQuantumKeyExchange { - typealias PublicKey = Data - typealias PrivateKey = Data - typealias SharedSecret = Data - - static func generateKeyPair() throws -> (publicKey: Data, privateKey: Data) { - // Generate mock keys for testing - let privateKey = Data((0..<32).map { _ in UInt8.random(in: 0...255) }) - let publicKey = Data((0..<800).map { _ in UInt8.random(in: 0...255) }) // Simulate larger PQ key - return (publicKey, privateKey) - } - - static func encapsulate(remotePublicKey: Data) throws -> (sharedSecret: Data, ciphertext: Data) { - let sharedSecret = Data((0..<32).map { _ in UInt8.random(in: 0...255) }) - let ciphertext = Data((0..<1088).map { _ in UInt8.random(in: 0...255) }) // Simulate Kyber ciphertext - return (sharedSecret, ciphertext) - } - - static func decapsulate(ciphertext: Data, privateKey: Data) throws -> Data { - // Return deterministic secret based on inputs for testing - let combined = ciphertext + privateKey - let hash = SHA256.hash(data: combined) - return Data(hash) - } - - static var publicKeySize: Int { 800 } - static var privateKeySize: Int { 1632 } - static var ciphertextSize: Int { 1088 } - static var sharedSecretSize: Int { 32 } -} -#endif \ No newline at end of file diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index f9272489..ec421a3b 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -77,7 +77,6 @@ struct MessagePadding { enum MessageType: UInt8 { case announce = 0x01 - // 0x02 was legacy keyExchange - removed case leave = 0x03 case message = 0x04 // All user messages (private and broadcast) case fragmentStart = 0x05 diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index 56b4d641..79635a65 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -3646,7 +3646,16 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { SecurityLogger.log("Version negotiation rejected by \(peerID): \(ack.reason ?? "Unknown reason")", category: SecurityLogger.session, level: .error) versionNegotiationState[peerID] = .failed(reason: ack.reason ?? "Version rejected") - // TODO: Handle disconnection + + // Clean up state for incompatible peer + handlePeerDisconnection(peerID) + + // Notify delegate about incompatible peer + DispatchQueue.main.async { [weak self] in + if let delegate = self?.delegate as? ChatViewModel { + delegate.showSystemMessage("Unable to connect to \(self?.peerNicknames[peerID] ?? peerID): \(ack.reason ?? "Incompatible protocol version")") + } + } } else { SecurityLogger.log("Version negotiation successful with \(peerID): agreed on version \(ack.agreedVersion)", category: SecurityLogger.session, level: .debug) diff --git a/bitchatTests/NoiseKeyRotationTests.swift b/bitchatTests/NoiseKeyRotationTests.swift index da6030b2..159bac32 100644 --- a/bitchatTests/NoiseKeyRotationTests.swift +++ b/bitchatTests/NoiseKeyRotationTests.swift @@ -307,80 +307,4 @@ class NoiseKeyRotationTests: XCTestCase { XCTAssertTrue(decrypted, "Failed to decrypt with any valid key") } -} - -// MARK: - Post-Quantum Framework Tests - -class NoisePostQuantumTests: XCTestCase { - - func testHybridKeyGeneration() throws { - let (publicKey, privateKey) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly) - - XCTAssertNotNil(publicKey.classical) - XCTAssertNil(publicKey.postQuantum) // No PQ component yet - XCTAssertEqual(publicKey.serialized.count, 32) // Just Curve25519 - - XCTAssertNotNil(privateKey.classical) - XCTAssertNil(privateKey.postQuantum) - } - - func testHybridKeyAgreement() throws { - // Generate two keypairs - let (alicePub, alicePriv) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly) - let (bobPub, bobPriv) = try HybridNoiseKeyExchange.generateKeyPair(algorithm: .classicalOnly) - - // Perform key agreement - let aliceShared = try HybridNoiseKeyExchange.performKeyAgreement( - localPrivate: alicePriv, - remotePublic: bobPub, - algorithm: .classicalOnly - ) - - let bobShared = try HybridNoiseKeyExchange.performKeyAgreement( - localPrivate: bobPriv, - remotePublic: alicePub, - algorithm: .classicalOnly - ) - - // Shared secrets should match - XCTAssertEqual( - aliceShared.combinedSecret().withUnsafeBytes { Data($0) }, - bobShared.combinedSecret().withUnsafeBytes { Data($0) } - ) - } - - func testMigrationConfig() { - let config = NoiseProtocolMigration.getMigrationConfig() - - XCTAssertEqual(config.currentPhase, .classicalOnly) - XCTAssertEqual(config.preferredAlgorithm, .classicalOnly) - XCTAssertTrue(config.acceptedAlgorithms.contains(.classicalOnly)) - XCTAssertNil(config.migrationDeadline) - } - - #if DEBUG - func testMockPostQuantum() throws { - // Test mock PQ implementation - let (publicKey, privateKey) = try MockPostQuantumKeyExchange.generateKeyPair() - - XCTAssertEqual(publicKey.count, MockPostQuantumKeyExchange.publicKeySize) - XCTAssertEqual(privateKey.count, 32) // Mock uses smaller private key - - let (sharedSecret, ciphertext) = try MockPostQuantumKeyExchange.encapsulate( - remotePublicKey: publicKey - ) - - XCTAssertEqual(ciphertext.count, MockPostQuantumKeyExchange.ciphertextSize) - XCTAssertEqual(sharedSecret.count, MockPostQuantumKeyExchange.sharedSecretSize) - - let decapsulatedSecret = try MockPostQuantumKeyExchange.decapsulate( - ciphertext: ciphertext, - privateKey: privateKey - ) - - // In real implementation, these would match - // Mock just returns deterministic values - XCTAssertEqual(decapsulatedSecret.count, 32) - } - #endif } \ No newline at end of file