mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-15 07:06:11 +00:00
Two hardening fixes from the repo evaluation: - NoiseSessionManager.authenticatedRemoteKey returned true for peer IDs that are neither 16-hex wire IDs nor full Noise-key IDs — an accept-any-key fallback kept for test harnesses. It now fails closed; the Noise/integration/E2E tests that relied on it address peers by key-derived wire IDs instead (the pattern NoiseCoverageTests already used), and a new regression test pins the rejection. - Four SecRandomCopyBytes call sites discarded the return status. The two verification nonces now fail their operation on error, the Nostr device identity seed uses CryptoKit key generation (cannot fail, and can no longer silently persist an all-zero seed), and BIP-340 aux randomness throws on failure like the adjacent nonce path. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
134 lines
4.7 KiB
Swift
134 lines
4.7 KiB
Swift
//
|
|
// TestNetworkHelper.swift
|
|
// bitchatTests
|
|
//
|
|
// Extracted shared, mutable integration state for nodes and noise sessions.
|
|
// Keeps test containers nonmutating (Swift Testing-friendly).
|
|
//
|
|
|
|
import Foundation
|
|
import CryptoKit
|
|
import Testing
|
|
@testable import BitFoundation // to avoid unnecessary public's
|
|
@testable import bitchat
|
|
|
|
final class TestNetworkHelper {
|
|
// Public, read-only views for tests; mutation only through methods
|
|
var nodes: [String: MockBLEService] = [:]
|
|
var noiseManagers: [String: NoiseSessionManager] = [:]
|
|
let mockKeychain = MockKeychain()
|
|
private let bus = MockBLEBus(autoFloodEnabled: true)
|
|
|
|
// MARK: - Node/Manager management
|
|
|
|
@discardableResult
|
|
func createNode(_ name: String) -> MockBLEService {
|
|
let node = MockBLEService(bus: bus)
|
|
// Wire IDs must derive from the node's Noise static key: handshake
|
|
// completion fails closed on IDs the remote key can't vouch for.
|
|
let key = Curve25519.KeyAgreement.PrivateKey()
|
|
node.myPeerID = PeerID(publicKey: key.publicKey.rawRepresentation)
|
|
node.mockNickname = name
|
|
nodes[name] = node
|
|
|
|
// This synchronous helper directly drives all three XX messages and
|
|
// has no transport callback loop for delayed collision recovery.
|
|
noiseManagers[name] = NoiseSessionManager(
|
|
localStaticKey: key,
|
|
keychain: mockKeychain,
|
|
recentInitiatorCompletionGracePeriod: 0
|
|
)
|
|
return node
|
|
}
|
|
|
|
// MARK: - Topology
|
|
|
|
func connect(_ a: String, _ b: String) {
|
|
guard let n1 = nodes[a], let n2 = nodes[b] else { return }
|
|
n1.simulateConnectedPeer(n2.peerID)
|
|
n2.simulateConnectedPeer(n1.peerID)
|
|
}
|
|
|
|
func disconnect(_ a: String, _ b: String) {
|
|
guard let n1 = nodes[a], let n2 = nodes[b] else { return }
|
|
n1.simulateDisconnectedPeer(n2.peerID)
|
|
n2.simulateDisconnectedPeer(n1.peerID)
|
|
}
|
|
|
|
func connectFullMesh() {
|
|
let names = Array(nodes.keys)
|
|
for i in 0..<names.count {
|
|
for j in (i+1)..<names.count {
|
|
connect(names[i], names[j])
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Relay
|
|
|
|
func setupRelay(_ nodeName: String, nextHops: [String]) {
|
|
guard let node = nodes[nodeName] else { return }
|
|
node.packetDeliveryHandler = { [weak self] packet in
|
|
guard let self else { return }
|
|
guard packet.ttl > 1 else { return }
|
|
|
|
if let message = BitchatMessage(packet.payload) {
|
|
guard message.senderPeerID != node.peerID else { return }
|
|
|
|
let relayMessage = BitchatMessage(
|
|
id: message.id,
|
|
sender: message.sender,
|
|
content: message.content,
|
|
timestamp: message.timestamp,
|
|
isRelay: true,
|
|
originalSender: message.isRelay ? message.originalSender : message.sender,
|
|
isPrivate: message.isPrivate,
|
|
recipientNickname: message.recipientNickname,
|
|
senderPeerID: message.senderPeerID,
|
|
mentions: message.mentions
|
|
)
|
|
|
|
if let relayPayload = relayMessage.toBinaryPayload() {
|
|
let relayPacket = BitchatPacket(
|
|
type: packet.type,
|
|
senderID: packet.senderID,
|
|
recipientID: packet.recipientID,
|
|
timestamp: packet.timestamp,
|
|
payload: relayPayload,
|
|
signature: packet.signature,
|
|
ttl: packet.ttl - 1
|
|
)
|
|
|
|
for hop in nextHops {
|
|
self.nodes[hop]?.simulateIncomingPacket(relayPacket)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Noise sessions
|
|
|
|
func establishNoiseSession(_ node1: String, _ node2: String) throws {
|
|
guard let manager1 = noiseManagers[node1],
|
|
let manager2 = noiseManagers[node2],
|
|
let peer1ID = nodes[node1]?.peerID,
|
|
let peer2ID = nodes[node2]?.peerID else { return }
|
|
|
|
let msg1 = try manager1.initiateHandshake(with: peer2ID)
|
|
let msg2 = try #require(
|
|
try manager2.handleIncomingHandshake(
|
|
from: peer1ID,
|
|
message: msg1
|
|
)
|
|
)
|
|
let msg3 = try #require(
|
|
try manager1.handleIncomingHandshake(
|
|
from: peer2ID,
|
|
message: msg2
|
|
)
|
|
)
|
|
_ = try manager2.handleIncomingHandshake(from: peer1ID, message: msg3)
|
|
}
|
|
}
|