mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 17:25:22 +00:00
Discard deferred Noise ciphertext during panic
This commit is contained in:
@@ -184,6 +184,16 @@ final class BLENoisePacketHandler {
|
|||||||
drainDeferredCiphertextsIfReady(for: peerID)
|
drainDeferredCiphertextsIfReady(for: peerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Synchronously discards ciphertext retained for a pre-panic Noise
|
||||||
|
/// generation. The handler survives the service's identity replacement,
|
||||||
|
/// so keeping this queue would replay old bytes after post-panic auth.
|
||||||
|
func resetForPanic() {
|
||||||
|
deferredLock.lock()
|
||||||
|
deferredCiphertexts.removeAll(keepingCapacity: false)
|
||||||
|
deferredCiphertextBytes = 0
|
||||||
|
deferredLock.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
private func handleEncrypted(
|
private func handleEncrypted(
|
||||||
_ packet: BitchatPacket,
|
_ packet: BitchatPacket,
|
||||||
from peerID: PeerID,
|
from peerID: PeerID,
|
||||||
|
|||||||
@@ -718,6 +718,7 @@ final class BLEService: NSObject {
|
|||||||
/// or advertising while the full panic transaction is incomplete.
|
/// or advertising while the full panic transaction is incomplete.
|
||||||
func suspendForPanicReset() {
|
func suspendForPanicReset() {
|
||||||
setPanicSuspended(true)
|
setPanicSuspended(true)
|
||||||
|
noisePacketHandler.resetForPanic()
|
||||||
gossipSyncManager?.stop()
|
gossipSyncManager?.stop()
|
||||||
gossipSyncManager = nil
|
gossipSyncManager = nil
|
||||||
// Stop the radio and drain CoreBluetooth's delegate queue first. A
|
// Stop the radio and drain CoreBluetooth's delegate queue first. A
|
||||||
@@ -728,7 +729,11 @@ final class BLEService: NSObject {
|
|||||||
// Drain every receive/send submitted by callbacks that finished ahead
|
// Drain every receive/send submitted by callbacks that finished ahead
|
||||||
// of the radio stop. Later callbacks observe the closed lifecycle, and
|
// of the radio stop. Later callbacks observe the closed lifecycle, and
|
||||||
// generation-bound handoffs that raced this barrier reject themselves.
|
// generation-bound handoffs that raced this barrier reject themselves.
|
||||||
messageQueue.sync(flags: .barrier) {}
|
// Clear the old identity's bounded early-ciphertext queue again after
|
||||||
|
// those callbacks drain so none can repopulate it after the first wipe.
|
||||||
|
messageQueue.sync(flags: .barrier) {
|
||||||
|
noisePacketHandler.resetForPanic()
|
||||||
|
}
|
||||||
clearEmergencySessionState()
|
clearEmergencySessionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,6 +751,11 @@ final class BLEService: NSObject {
|
|||||||
) {
|
) {
|
||||||
gossipSyncManager?.stop()
|
gossipSyncManager?.stop()
|
||||||
gossipSyncManager = nil
|
gossipSyncManager = nil
|
||||||
|
// Discard deferred pre-panic ciphertext behind any in-flight receive
|
||||||
|
// handlers so none can repopulate the handler's bounded queue.
|
||||||
|
messageQueue.sync(flags: .barrier) {
|
||||||
|
noisePacketHandler.resetForPanic()
|
||||||
|
}
|
||||||
// pendingNoiseSessionQueues is owned by collectionsQueue everywhere
|
// pendingNoiseSessionQueues is owned by collectionsQueue everywhere
|
||||||
// else, so clear it there too rather than on messageQueue.
|
// else, so clear it there too rather than on messageQueue.
|
||||||
collectionsQueue.sync(flags: .barrier) {
|
collectionsQueue.sync(flags: .barrier) {
|
||||||
|
|||||||
@@ -521,6 +521,60 @@ struct BLENoisePacketHandlerTests {
|
|||||||
#expect(recorder.initiatedHandshakes.isEmpty)
|
#expect(recorder.initiatedHandshakes.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func panicResetDiscardsDeferredCiphertextBeforeFutureAuthentication() {
|
||||||
|
let recorder = Recorder()
|
||||||
|
recorder.hasSession = true
|
||||||
|
recorder.awaitingResponderHandshake = true
|
||||||
|
recorder.decryptResult = .failure(
|
||||||
|
CryptoKitError.authenticationFailure
|
||||||
|
)
|
||||||
|
let handler = makeHandler(recorder: recorder)
|
||||||
|
let prePanicCiphertext = makeEncryptedPacket(
|
||||||
|
recipientID: Data(hexString: localPeerID.id),
|
||||||
|
payload: Data(
|
||||||
|
count: NoiseSecurityConstants.maxMessageSize
|
||||||
|
+ NoiseSecurityConstants.transportCiphertextOverhead
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
handler.handleEncrypted(prePanicCiphertext, from: remotePeerID)
|
||||||
|
#expect(recorder.decryptCalls.count == 1)
|
||||||
|
|
||||||
|
handler.resetForPanic()
|
||||||
|
|
||||||
|
// Three maximum-sized packets fit only when reset also zeroed the
|
||||||
|
// global byte accounting. They model ciphertext received under the
|
||||||
|
// replacement identity before that responder handshake completes.
|
||||||
|
for index in 0..<3 {
|
||||||
|
handler.handleEncrypted(
|
||||||
|
makeEncryptedPacket(
|
||||||
|
recipientID: Data(hexString: localPeerID.id),
|
||||||
|
timestamp: UInt64(901_000 + index),
|
||||||
|
payload: Data(
|
||||||
|
count: NoiseSecurityConstants.maxMessageSize
|
||||||
|
+ NoiseSecurityConstants.transportCiphertextOverhead
|
||||||
|
)
|
||||||
|
),
|
||||||
|
from: remotePeerID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#expect(recorder.decryptCalls.count == 4)
|
||||||
|
|
||||||
|
recorder.awaitingResponderHandshake = false
|
||||||
|
recorder.decryptResult = .success(
|
||||||
|
Data([NoisePayloadType.privateMessage.rawValue, 0xCA, 0xFE])
|
||||||
|
)
|
||||||
|
handler.handleSessionAuthenticated(remotePeerID)
|
||||||
|
|
||||||
|
// Only the three post-reset packets replay; the pre-panic packet does
|
||||||
|
// not survive into the replacement session.
|
||||||
|
#expect(recorder.decryptCalls.count == 7)
|
||||||
|
#expect(recorder.deliveries.count == 3)
|
||||||
|
#expect(recorder.clearedSessions.isEmpty)
|
||||||
|
#expect(recorder.initiatedHandshakes.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func ciphertextQueuedAheadOfEstablishmentCallbackDoesNotConsumeNonce()
|
func ciphertextQueuedAheadOfEstablishmentCallbackDoesNotConsumeNonce()
|
||||||
throws {
|
throws {
|
||||||
|
|||||||
Reference in New Issue
Block a user