diff --git a/bitchat/Noise/NoiseProtocol.swift b/bitchat/Noise/NoiseProtocol.swift index aa5f35be..b49baa0a 100644 --- a/bitchat/Noise/NoiseProtocol.swift +++ b/bitchat/Noise/NoiseProtocol.swift @@ -833,16 +833,20 @@ final class NoiseHandshakeState { return currentPattern >= messagePatterns.count } - func getTransportCiphers(useExtractedNonce: Bool) throws -> (send: NoiseCipherState, receive: NoiseCipherState) { + func getTransportCiphers(useExtractedNonce: Bool) throws -> (send: NoiseCipherState, receive: NoiseCipherState, handshakeHash: Data) { guard isHandshakeComplete() else { throw NoiseError.handshakeNotComplete } - + + // BCH-01-010: Capture handshake hash BEFORE split() clears symmetric state + let finalHandshakeHash = symmetricState.getHandshakeHash() + let (c1, c2) = symmetricState.split(useExtractedNonce: useExtractedNonce) - + // Initiator uses c1 for sending, c2 for receiving // Responder uses c2 for sending, c1 for receiving - return role == .initiator ? (c1, c2) : (c2, c1) + let ciphers = role == .initiator ? (c1, c2) : (c2, c1) + return (send: ciphers.0, receive: ciphers.1, handshakeHash: finalHandshakeHash) } func getRemoteStaticPublicKey() -> Curve25519.KeyAgreement.PublicKey? { diff --git a/bitchat/Noise/NoiseSession.swift b/bitchat/Noise/NoiseSession.swift index 8c84f85a..0b96a124 100644 --- a/bitchat/Noise/NoiseSession.swift +++ b/bitchat/Noise/NoiseSession.swift @@ -102,23 +102,23 @@ class NoiseSession { // Check if handshake is complete if handshake.isHandshakeComplete() { - // Get transport ciphers - let (send, receive) = try handshake.getTransportCiphers(useExtractedNonce: true) + // Get transport ciphers and handshake hash (hash captured before split clears state) + let (send, receive, hash) = try handshake.getTransportCiphers(useExtractedNonce: true) sendCipher = send receiveCipher = receive - + // Store remote static key remoteStaticPublicKey = handshake.getRemoteStaticPublicKey() - + // Store handshake hash for channel binding - handshakeHash = handshake.getHandshakeHash() - + handshakeHash = hash + state = .established handshakeState = nil // Clear handshake state - + SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete (no response needed), transitioning to established") SecureLogger.info(.handshakeCompleted(peerID: peerID.id)) - + return nil } else { // Generate response @@ -128,20 +128,20 @@ class NoiseSession { // Check if handshake is complete after writing if handshake.isHandshakeComplete() { - // Get transport ciphers - let (send, receive) = try handshake.getTransportCiphers(useExtractedNonce: true) + // Get transport ciphers and handshake hash (hash captured before split clears state) + let (send, receive, hash) = try handshake.getTransportCiphers(useExtractedNonce: true) sendCipher = send receiveCipher = receive - + // Store remote static key remoteStaticPublicKey = handshake.getRemoteStaticPublicKey() - + // Store handshake hash for channel binding - handshakeHash = handshake.getHandshakeHash() - + handshakeHash = hash + state = .established handshakeState = nil // Clear handshake state - + SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete after writing response, transitioning to established") SecureLogger.info(.handshakeCompleted(peerID: peerID.id)) }