diff --git a/bitchat/Protocols/BitchatProtocol.swift b/bitchat/Protocols/BitchatProtocol.swift index 677584d8..f9272489 100644 --- a/bitchat/Protocols/BitchatProtocol.swift +++ b/bitchat/Protocols/BitchatProtocol.swift @@ -347,16 +347,18 @@ struct ChannelMetadata: Codable { struct NoiseIdentityAnnouncement: Codable { let peerID: String // Current ephemeral peer ID let publicKey: Data // Noise static public key + let signingPublicKey: Data // Ed25519 signing public key let nickname: String // Current nickname let timestamp: Date // When this binding was created let previousPeerID: String? // Previous peer ID (for smooth transition) let signature: Data // Signature proving ownership - init(peerID: String, publicKey: Data, nickname: String, previousPeerID: String? = nil, signature: Data) { + init(peerID: String, publicKey: Data, signingPublicKey: Data, nickname: String, timestamp: Date, previousPeerID: String? = nil, signature: Data) { self.peerID = peerID self.publicKey = publicKey + self.signingPublicKey = signingPublicKey self.nickname = nickname - self.timestamp = Date() + self.timestamp = timestamp self.previousPeerID = previousPeerID self.signature = signature } @@ -366,7 +368,7 @@ struct NoiseIdentityAnnouncement: Codable { } static func decode(from data: Data) -> NoiseIdentityAnnouncement? { - try? JSONDecoder().decode(NoiseIdentityAnnouncement.self, from: data) + return try? JSONDecoder().decode(NoiseIdentityAnnouncement.self, from: data) } } @@ -375,14 +377,22 @@ struct PeerIdentityBinding { let currentPeerID: String // Current ephemeral ID let fingerprint: String // Permanent cryptographic identity let publicKey: Data // Noise static public key + let signingPublicKey: Data // Ed25519 signing public key let nickname: String // Last known nickname let bindingTimestamp: Date // When this binding was created let signature: Data // Cryptographic proof of binding // Verify the binding signature func verify() -> Bool { - // TODO: Implement signature verification - return true + let bindingData = currentPeerID.data(using: .utf8)! + publicKey + + String(Int64(bindingTimestamp.timeIntervalSince1970 * 1000)).data(using: .utf8)! + + do { + let signingKey = try Curve25519.Signing.PublicKey(rawRepresentation: signingPublicKey) + return signingKey.isValidSignature(signature, for: bindingData) + } catch { + return false + } } } diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index 25dc39c0..2663a2bd 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -2303,11 +2303,12 @@ class BluetoothMeshService: NSObject { return } - // Verify the signature (currently always returns true for compatibility) - let bindingData = announcement.peerID.data(using: .utf8)! + announcement.publicKey + announcement.timestamp.timeIntervalSince1970.data - if !noiseService.verifySignature(announcement.signature, for: bindingData, publicKey: announcement.publicKey) { - // Log but don't reject - signature verification is temporarily disabled - SecurityLogger.log("Signature verification skipped for \(senderID)", category: SecurityLogger.noise, level: .debug) + // Verify the signature using the signing public key + let timestampData = String(Int64(announcement.timestamp.timeIntervalSince1970 * 1000)).data(using: .utf8)! + let bindingData = announcement.peerID.data(using: .utf8)! + announcement.publicKey + timestampData + if !noiseService.verifySignature(announcement.signature, for: bindingData, publicKey: announcement.signingPublicKey) { + SecurityLogger.log("Signature verification failed for \(senderID)", category: SecurityLogger.noise, level: .warning) + return // Reject announcements with invalid signatures } // Calculate fingerprint from public key @@ -2319,6 +2320,7 @@ class BluetoothMeshService: NSObject { currentPeerID: announcement.peerID, fingerprint: fingerprint, publicKey: announcement.publicKey, + signingPublicKey: announcement.signingPublicKey, nickname: announcement.nickname, bindingTimestamp: announcement.timestamp, signature: announcement.signature @@ -3800,23 +3802,27 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { lastIdentityAnnounceTimes["*broadcast*"] = now } - // Get our Noise static public key + // Get our Noise static public key and signing public key let staticKey = noiseService.getStaticPublicKeyData() + let signingKey = noiseService.getSigningPublicKeyData() // Get nickname from delegate let nickname = (delegate as? ChatViewModel)?.nickname ?? "Anonymous" - // Create the binding data to sign - let bindingData = myPeerID.data(using: .utf8)! + staticKey + now.timeIntervalSince1970.data + // Create the binding data to sign (peerID + publicKey + timestamp) + let timestampData = String(Int64(now.timeIntervalSince1970 * 1000)).data(using: .utf8)! + let bindingData = myPeerID.data(using: .utf8)! + staticKey + timestampData - // Sign the binding with our private key + // Sign the binding with our Ed25519 signing key let signature = noiseService.signData(bindingData) ?? Data() // Create the identity announcement let announcement = NoiseIdentityAnnouncement( peerID: myPeerID, publicKey: staticKey, + signingPublicKey: signingKey, nickname: nickname, + timestamp: now, previousPeerID: previousPeerID, signature: signature ) diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index 3bfd4983..99994558 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -17,6 +17,10 @@ class NoiseEncryptionService { private let staticIdentityKey: Curve25519.KeyAgreement.PrivateKey public let staticIdentityPublicKey: Curve25519.KeyAgreement.PublicKey + // Ed25519 signing key (persistent across sessions) + private let signingKey: Curve25519.Signing.PrivateKey + public let signingPublicKey: Curve25519.Signing.PublicKey + // Session manager private let sessionManager: NoiseSessionManager @@ -63,6 +67,27 @@ class NoiseEncryptionService { self.staticIdentityKey = loadedKey self.staticIdentityPublicKey = staticIdentityKey.publicKey + // Load or create signing key pair + let loadedSigningKey: Curve25519.Signing.PrivateKey + + // Try to load from keychain + if let signingData = KeychainManager.shared.getIdentityKey(forKey: "ed25519SigningKey"), + let key = try? Curve25519.Signing.PrivateKey(rawRepresentation: signingData) { + loadedSigningKey = key + } + // If no signing key exists, create new one + else { + loadedSigningKey = Curve25519.Signing.PrivateKey() + let keyData = loadedSigningKey.rawRepresentation + + // Save to keychain + _ = KeychainManager.shared.saveIdentityKey(keyData, forKey: "ed25519SigningKey") + } + + // Now assign the signing keys + self.signingKey = loadedSigningKey + self.signingPublicKey = signingKey.publicKey + // Initialize session manager self.sessionManager = NoiseSessionManager(localStaticKey: staticIdentityKey) @@ -82,6 +107,11 @@ class NoiseEncryptionService { return staticIdentityPublicKey.rawRepresentation } + /// Get our signing public key for sharing + func getSigningPublicKeyData() -> Data { + return signingPublicKey.rawRepresentation + } + /// Get our identity fingerprint func getIdentityFingerprint() -> String { let hash = SHA256.hash(data: staticIdentityPublicKey.rawRepresentation) @@ -97,25 +127,31 @@ class NoiseEncryptionService { func clearPersistentIdentity() { // Clear from keychain _ = KeychainManager.shared.deleteIdentityKey(forKey: "noiseStaticKey") + _ = KeychainManager.shared.deleteIdentityKey(forKey: "ed25519SigningKey") // Stop rekey timer stopRekeyTimer() } - /// Sign data with our static identity key + /// Sign data with our Ed25519 signing key func signData(_ data: Data) -> Data? { - // For now, use HMAC with the private key as a simple signature - // This is not cryptographically ideal but works for identity binding - let key = SymmetricKey(data: staticIdentityKey.rawRepresentation) - let signature = HMAC.authenticationCode(for: data, using: key) - return Data(signature) + do { + let signature = try signingKey.signature(for: data) + return signature + } catch { + SecurityLogger.logError(error, context: "Failed to sign data", category: SecurityLogger.noise) + return nil + } } - /// Verify signature with a peer's public key + /// Verify signature with a peer's Ed25519 public key func verifySignature(_ signature: Data, for data: Data, publicKey: Data) -> Bool { - // For verification, we can't use the same HMAC approach since we don't have the private key - // For now, we'll skip signature verification but maintain the protocol structure - // In production, this should use proper Ed25519 signatures - return true // Temporarily accept all signatures to fix the immediate issue + do { + let signingPublicKey = try Curve25519.Signing.PublicKey(rawRepresentation: publicKey) + return signingPublicKey.isValidSignature(signature, for: data) + } catch { + SecurityLogger.logError(error, context: "Failed to verify signature", category: SecurityLogger.noise) + return false + } } // MARK: - Handshake Management