diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index abc6b911..7b48f1b4 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -37,10 +37,8 @@ 04B6BA572E203D6C0090FE39 /* FingerprintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA532E203D6C0090FE39 /* FingerprintView.swift */; }; 04B6BA582E203D6C0090FE39 /* NoiseTestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */; }; 04B6BA5B2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; }; - 04B6BA5B2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */; }; 04B6BA5C2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */; }; 04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; }; - 04B6BA5D2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */; }; 04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */; }; 04B6BA672E21601B0090FE39 /* ChannelVerificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */; }; 04B6BA682E21601B0090FE39 /* NoiseKeyRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04B6BA622E21601B0090FE39 /* NoiseKeyRotationTests.swift */; }; @@ -166,7 +164,6 @@ 04B6BA542E203D6C0090FE39 /* NoiseTestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseTestingView.swift; sourceTree = ""; }; 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelKeyRotation.swift; sourceTree = ""; }; 04B6BA5A2E2041220090FE39 /* NoiseIdentityPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseIdentityPersistenceTests.swift; sourceTree = ""; }; - 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoisePostQuantum.swift; sourceTree = ""; }; 04B6BA5F2E21601B0090FE39 /* ChannelVerificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelVerificationTests.swift; sourceTree = ""; }; 04B6BA602E21601B0090FE39 /* KeychainIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainIntegrationTests.swift; sourceTree = ""; }; 04B6BA612E21601B0090FE39 /* NoiseChannelEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoiseChannelEncryptionTests.swift; sourceTree = ""; }; @@ -218,7 +215,6 @@ isa = PBXGroup; children = ( 04B6BA592E215FDA0090FE39 /* NoiseChannelKeyRotation.swift */, - 04B6BA5A2E215FDA0090FE39 /* NoisePostQuantum.swift */, 04B6BA402E2035530090FE39 /* NoiseChannelEncryption.swift */, 04B6BA412E2035530090FE39 /* NoiseProtocol.swift */, 04B6BA422E2035530090FE39 /* NoiseSecurityConsiderations.swift */, @@ -555,7 +551,6 @@ 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */, 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */, 04891CAA2E22971E0064A111 /* LRUCache.swift in Sources */, - 04B6BA5B2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */, 04B6BA5C2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */, 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */, 04B6BA452E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */, @@ -591,7 +586,6 @@ F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */, 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */, 04891CA92E22971E0064A111 /* LRUCache.swift in Sources */, - 04B6BA5D2E215FDA0090FE39 /* NoisePostQuantum.swift in Sources */, 04B6BA5E2E215FDA0090FE39 /* NoiseChannelKeyRotation.swift in Sources */, 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */, 04B6BA492E2035530090FE39 /* NoiseSecurityConsiderations.swift in Sources */, 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/Noise/NoiseSession.swift b/bitchat/Noise/NoiseSession.swift index 2f8b2eb7..1558b3eb 100644 --- a/bitchat/Noise/NoiseSession.swift +++ b/bitchat/Noise/NoiseSession.swift @@ -254,18 +254,18 @@ class NoiseSessionManager { } func removeSession(for peerID: String) { - _ = managerQueue.sync(flags: .barrier) { - sessions.removeValue(forKey: peerID) + managerQueue.sync(flags: .barrier) { + _ = sessions.removeValue(forKey: peerID) } } func migrateSession(from oldPeerID: String, to newPeerID: String) { - _ = managerQueue.sync(flags: .barrier) { + managerQueue.sync(flags: .barrier) { // Check if we have a session for the old peer ID if let session = sessions[oldPeerID] { // Move the session to the new peer ID sessions[newPeerID] = session - sessions.removeValue(forKey: oldPeerID) + _ = sessions.removeValue(forKey: oldPeerID) SecurityLogger.log("Migrated Noise session from \(oldPeerID) to \(newPeerID)", category: SecurityLogger.noise, level: .info) } @@ -290,7 +290,7 @@ class NoiseSessionManager { // Remove any existing non-established session if let existingSession = sessions[peerID], !existingSession.isEstablished() { - sessions.removeValue(forKey: peerID) + _ = sessions.removeValue(forKey: peerID) } // Create new initiator session @@ -306,7 +306,7 @@ class NoiseSessionManager { return handshakeData } catch { // Clean up failed session - sessions.removeValue(forKey: peerID) + _ = sessions.removeValue(forKey: peerID) throw error } } @@ -328,7 +328,7 @@ class NoiseSessionManager { // If we're in the middle of a handshake and receive a new initiation, // reset and start fresh (the other side may have restarted) if existing.getState() == .handshaking && message.count == 32 { - sessions.removeValue(forKey: peerID) + _ = sessions.removeValue(forKey: peerID) shouldCreateNew = true } else { existingSession = existing @@ -369,7 +369,7 @@ class NoiseSessionManager { return response } catch { // Reset the session on handshake failure so next attempt can start fresh - sessions.removeValue(forKey: peerID) + _ = sessions.removeValue(forKey: peerID) // Schedule callback outside the synchronized block to prevent deadlock DispatchQueue.global().async { [weak self] in 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..dc840555 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -79,7 +79,6 @@ class BluetoothMeshService: NSObject { private var activePeers: Set = [] // Track all active peers private var peerRSSI: [String: NSNumber] = [:] // Track RSSI values for peers private var peripheralRSSI: [String: NSNumber] = [:] // Track RSSI by peripheral ID during discovery - private var loggedCryptoErrors = Set() // Track which peers we've logged crypto errors for // MARK: - Peer Identity Rotation // Mappings between ephemeral peer IDs and permanent fingerprints @@ -884,8 +883,8 @@ class BluetoothMeshService: NSObject { private func notifyPeerIDChange(oldPeerID: String, newPeerID: String, fingerprint: String) { DispatchQueue.main.async { [weak self] in // Remove old peer ID from active peers and announcedPeers - _ = self?.collectionsQueue.sync(flags: .barrier) { - self?.activePeers.remove(oldPeerID) + self?.collectionsQueue.sync(flags: .barrier) { + _ = self?.activePeers.remove(oldPeerID) // Don't pre-insert the new peer ID - let the announce packet handle it // This ensures the connect message logic works properly } @@ -2043,7 +2042,7 @@ class BluetoothMeshService: NSObject { // IMPORTANT: Remove old peer ID from activePeers to prevent duplicates collectionsQueue.sync(flags: .barrier) { if self.activePeers.contains(tempID) { - self.activePeers.remove(tempID) + _ = self.activePeers.remove(tempID) } } @@ -2160,8 +2159,8 @@ class BluetoothMeshService: NSObject { if String(data: packet.payload, encoding: .utf8) != nil { // Remove from active peers with proper locking collectionsQueue.sync(flags: .barrier) { - self.activePeers.remove(senderID) - self.peerNicknames.removeValue(forKey: senderID) + _ = self.activePeers.remove(senderID) + _ = self.peerNicknames.removeValue(forKey: senderID) } announcedPeers.remove(senderID) @@ -2848,8 +2847,8 @@ extension BluetoothMeshService: CBCentralManagerDelegate { if removed { } - announcedPeers.remove(peerID) - announcedToPeers.remove(peerID) + _ = announcedPeers.remove(peerID) + _ = announcedToPeers.remove(peerID) } else { } @@ -3646,7 +3645,21 @@ 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 + collectionsQueue.sync(flags: .barrier) { + _ = self.activePeers.remove(peerID) + _ = self.peerNicknames.removeValue(forKey: peerID) + } + announcedPeers.remove(peerID) + + // Clean up any Noise session + noiseService.removePeer(peerID) + + // Notify delegate about incompatible peer disconnection + DispatchQueue.main.async { [weak self] in + self?.delegate?.didDisconnectFromPeer(peerID) + } } else { SecurityLogger.log("Version negotiation successful with \(peerID): agreed on version \(ack.agreedVersion)", category: SecurityLogger.session, level: .debug) @@ -3900,7 +3913,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { // Clean up old entries after 10 seconds DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in self?.collectionsQueue.sync(flags: .barrier) { - self?.recentlySentMessages.remove(sendKey) + _ = self?.recentlySentMessages.remove(sendKey) } } return false diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index 79f7190a..046a883f 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -466,6 +466,4 @@ struct NoiseMessage: Codable { enum NoiseEncryptionError: Error { case handshakeRequired case sessionNotEstablished - case invalidMessage - case handshakeFailed(Error) } \ No newline at end of file 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