mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 18:05:21 +00:00
Encrypt private media before BLE fragmentation (#1434)
Closes the last cleartext private-content path over BLE: private DM images/voice were sent as plaintext signed fileTransfer packets, TTL-relayed across the mesh, so every relay saw the full bytes. Now the complete BitchatFilePacket is encrypted as a single Noise AEAD message (inner type 0x20, matching Android) and the opaque ciphertext is fragmented. Adds an authenticated in-session capability proof (0x21 TLV: capabilities + Ed25519 key), TOFU-style downgrade pinning, a per-send consent dialog for the signed-cleartext fallback to legacy peers, and a cancellation/admission registry so cancel/delete cannot race a deferred cleartext send. Android wire constants (0x20 / 0x21 / capability bit 8) confirmed shipping. The 256-fragment preflight cap applies only to the directed fileTransfer migration fallback; encrypted media to capable peers uses the full receiver ceiling. Rebased over #1428/#1349: identity reads go through BLELocalIdentityStateStore; the session-bound authenticated signing-key check and the announce-path TOFU pin are kept as complementary checks. Full local suite green (1744+197 tests).
This commit is contained in:
@@ -14,6 +14,8 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol {
|
||||
private var blockedFingerprints: Set<String> = []
|
||||
private var blockedNostrPubkeys: Set<String> = []
|
||||
private var socialIdentities: [String: SocialIdentity] = [:]
|
||||
private var privateMediaCapableFingerprints: Set<String> = []
|
||||
private var authenticatedSigningKeys: [String: Data] = [:]
|
||||
|
||||
init(_: KeychainManagerProtocol) {}
|
||||
|
||||
@@ -87,7 +89,10 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol {
|
||||
|
||||
func registerEphemeralSession(peerID: PeerID, handshakeState: HandshakeState) {}
|
||||
|
||||
func clearAllIdentityData() {}
|
||||
func clearAllIdentityData() {
|
||||
privateMediaCapableFingerprints.removeAll()
|
||||
authenticatedSigningKeys.removeAll()
|
||||
}
|
||||
|
||||
func removeEphemeralSession(peerID: PeerID) {}
|
||||
|
||||
@@ -101,6 +106,22 @@ final class MockIdentityManager: SecureIdentityStateManagerProtocol {
|
||||
Set()
|
||||
}
|
||||
|
||||
func markPrivateMediaCapable(fingerprint: String) {
|
||||
privateMediaCapableFingerprints.insert(fingerprint)
|
||||
}
|
||||
|
||||
func hasObservedPrivateMediaCapability(fingerprint: String) -> Bool {
|
||||
privateMediaCapableFingerprints.contains(fingerprint)
|
||||
}
|
||||
|
||||
func bindAuthenticatedSigningPublicKey(_ signingPublicKey: Data, fingerprint: String) {
|
||||
authenticatedSigningKeys[fingerprint] = signingPublicKey
|
||||
}
|
||||
|
||||
func authenticatedSigningPublicKey(forFingerprint fingerprint: String) -> Data? {
|
||||
authenticatedSigningKeys[fingerprint]
|
||||
}
|
||||
|
||||
// MARK: Vouching (transitive verification)
|
||||
|
||||
private var vouchesByVouchee: [String: [VouchRecord]] = [:]
|
||||
|
||||
@@ -36,6 +36,7 @@ final class MockTransport: Transport {
|
||||
private(set) var sentFavoriteNotifications: [(peerID: PeerID, isFavorite: Bool)] = []
|
||||
private(set) var sentBroadcastFiles: [(packet: BitchatFilePacket, transferID: String)] = []
|
||||
private(set) var sentPrivateFiles: [(packet: BitchatFilePacket, peerID: PeerID, transferID: String)] = []
|
||||
private(set) var sentPrivateFileLegacyAllowances: [Bool] = []
|
||||
private(set) var cancelledTransfers: [String] = []
|
||||
private(set) var sentVerifyChallenges: [(peerID: PeerID, noiseKeyHex: String, nonceA: Data)] = []
|
||||
private(set) var sentVerifyResponses: [(peerID: PeerID, noiseKeyHex: String, nonceA: Data)] = []
|
||||
@@ -58,6 +59,7 @@ final class MockTransport: Transport {
|
||||
var peerNicknames: [PeerID: String] = [:]
|
||||
var peerFingerprints: [PeerID: String] = [:]
|
||||
var peerNoiseStates: [PeerID: LazyHandshakeState] = [:]
|
||||
var privateMediaPolicies: [PeerID: PrivateMediaSendPolicy] = [:]
|
||||
private let mockKeychain = MockKeychain()
|
||||
|
||||
// MARK: - Transport Protocol Implementation
|
||||
@@ -186,6 +188,29 @@ final class MockTransport: Transport {
|
||||
|
||||
func sendFilePrivate(_ packet: BitchatFilePacket, to peerID: PeerID, transferId: String) {
|
||||
sentPrivateFiles.append((packet, peerID, transferId))
|
||||
sentPrivateFileLegacyAllowances.append(false)
|
||||
}
|
||||
|
||||
func sendFilePrivate(
|
||||
_ packet: BitchatFilePacket,
|
||||
to peerID: PeerID,
|
||||
transferId: String,
|
||||
allowLegacyFallback: Bool
|
||||
) {
|
||||
sentPrivateFiles.append((packet, peerID, transferId))
|
||||
sentPrivateFileLegacyAllowances.append(allowLegacyFallback)
|
||||
}
|
||||
|
||||
func privateMediaSendPolicy(to peerID: PeerID) -> PrivateMediaSendPolicy {
|
||||
privateMediaPolicies[peerID] ?? .encrypted
|
||||
}
|
||||
|
||||
func resolvePrivateMediaSendPolicy(
|
||||
to peerID: PeerID,
|
||||
completion: @escaping @MainActor (PrivateMediaSendPolicy) -> Void
|
||||
) {
|
||||
let policy = privateMediaPolicies[peerID] ?? .encrypted
|
||||
Task { @MainActor in completion(policy) }
|
||||
}
|
||||
|
||||
func cancelTransfer(_ transferId: String) {
|
||||
|
||||
Reference in New Issue
Block a user