mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
Fix: Capture handshake hash before split() clears symmetric state
Addresses Codex review feedback on PR #948: the getTransportCiphers() method now returns the handshake hash as part of its return tuple, capturing it BEFORE split() calls clearSensitiveData(). Previously, calling getHandshakeHash() after getTransportCiphers() would return an all-zero value since split() clears the symmetric state. This broke channel binding functionality. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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? {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user