Refactor fingerprint manager (#185)

* centralized fingerprint manager

* centralized fingerprintmanager

* update fingerprint only if no collision
This commit is contained in:
callebtc
2025-07-25 15:43:19 +02:00
committed by GitHub
parent 9b7009137c
commit 534be7e613
10 changed files with 425 additions and 106 deletions
@@ -66,7 +66,7 @@ class BluetoothMeshService(private val context: Context) {
// Wire up PacketProcessor reference for recursive handling in MessageHandler // Wire up PacketProcessor reference for recursive handling in MessageHandler
messageHandler.packetProcessor = packetProcessor messageHandler.packetProcessor = packetProcessor
sendPeriodicBroadcastAnnounce() sendPeriodicBroadcastAnnounce()
//startPeriodicDebugLogging() startPeriodicDebugLogging()
} }
/** /**
@@ -126,9 +126,6 @@ class BluetoothMeshService(private val context: Context) {
// SecurityManager delegate for key exchange notifications // SecurityManager delegate for key exchange notifications
securityManager.delegate = object : SecurityManagerDelegate { securityManager.delegate = object : SecurityManagerDelegate {
override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) { override fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) {
// Notify delegate about key exchange completion so it can register peer fingerprint
delegate?.registerPeerPublicKey(peerID, peerPublicKeyData)
receivedAddress?.let { address -> receivedAddress?.let { address ->
connectionManager.addressPeerMap[address] = peerID connectionManager.addressPeerMap[address] = peerID
} }
@@ -259,19 +256,21 @@ class BluetoothMeshService(private val context: Context) {
} }
} }
override fun updatePeerIDBinding(newPeerID: String, fingerprint: String, nickname: String, override fun updatePeerIDBinding(newPeerID: String, nickname: String,
publicKey: ByteArray, previousPeerID: String?) { publicKey: ByteArray, previousPeerID: String?) {
Log.d(TAG, "Updating peer ID binding: $newPeerID (was: $previousPeerID) with nickname: $nickname and public key: ${publicKey.toHexString().take(16)}...")
// Update peer mapping in the PeerManager for peer ID rotation support // Update peer mapping in the PeerManager for peer ID rotation support
peerManager.addOrUpdatePeer(newPeerID, nickname) peerManager.addOrUpdatePeer(newPeerID, nickname)
// Store fingerprint for the peer via centralized fingerprint manager
val fingerprint = peerManager.storeFingerprintForPeer(newPeerID, publicKey)
// If there was a previous peer ID, remove it to avoid duplicates // If there was a previous peer ID, remove it to avoid duplicates
previousPeerID?.let { oldPeerID -> previousPeerID?.let { oldPeerID ->
peerManager.removePeer(oldPeerID) peerManager.removePeer(oldPeerID)
} }
// Register the public key with the delegate (ChatViewModel)
delegate?.registerPeerPublicKey(newPeerID, publicKey)
Log.d(TAG, "Updated peer ID binding: $newPeerID (was: $previousPeerID), fingerprint: ${fingerprint.take(16)}...") Log.d(TAG, "Updated peer ID binding: $newPeerID (was: $previousPeerID), fingerprint: ${fingerprint.take(16)}...")
} }
@@ -794,7 +793,7 @@ class BluetoothMeshService(private val context: Context) {
* Get peer fingerprint for identity management * Get peer fingerprint for identity management
*/ */
fun getPeerFingerprint(peerID: String): String? { fun getPeerFingerprint(peerID: String): String? {
return encryptionService.getPeerFingerprint(peerID) return peerManager.getFingerprintForPeer(peerID)
} }
/** /**
@@ -853,6 +852,8 @@ class BluetoothMeshService(private val context: Context) {
appendLine() appendLine()
appendLine(peerManager.getDebugInfo(connectionManager.addressPeerMap)) appendLine(peerManager.getDebugInfo(connectionManager.addressPeerMap))
appendLine() appendLine()
appendLine(peerManager.getFingerprintDebugInfo())
appendLine()
appendLine(fragmentManager.getDebugInfo()) appendLine(fragmentManager.getDebugInfo())
appendLine() appendLine()
appendLine(securityManager.getDebugInfo()) appendLine(securityManager.getDebugInfo())
@@ -910,5 +911,5 @@ interface BluetoothMeshDelegate {
fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String?
fun getNickname(): String? fun getNickname(): String?
fun isFavorite(peerID: String): Boolean fun isFavorite(peerID: String): Boolean
fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray) // registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
} }
@@ -138,7 +138,6 @@ class MessageHandler(private val myPeerID: String) {
// Update peer binding in the delegate (ChatViewModel/BluetoothMeshService) // Update peer binding in the delegate (ChatViewModel/BluetoothMeshService)
delegate?.updatePeerIDBinding( delegate?.updatePeerIDBinding(
newPeerID = announcement.peerID, newPeerID = announcement.peerID,
fingerprint = announcement.fingerprint ?: "",
nickname = announcement.nickname, nickname = announcement.nickname,
publicKey = announcement.publicKey, publicKey = announcement.publicKey,
previousPeerID = announcement.previousPeerID previousPeerID = announcement.previousPeerID
@@ -409,7 +408,7 @@ interface MessageHandlerDelegate {
// Noise protocol operations // Noise protocol operations
fun hasNoiseSession(peerID: String): Boolean fun hasNoiseSession(peerID: String): Boolean
fun initiateNoiseHandshake(peerID: String) fun initiateNoiseHandshake(peerID: String)
fun updatePeerIDBinding(newPeerID: String, fingerprint: String, nickname: String, fun updatePeerIDBinding(newPeerID: String, nickname: String,
publicKey: ByteArray, previousPeerID: String?) publicKey: ByteArray, previousPeerID: String?)
// Message operations // Message operations
@@ -0,0 +1,247 @@
package com.bitchat.android.mesh
import android.util.Log
import java.security.MessageDigest
import java.util.concurrent.ConcurrentHashMap
/**
* Centralized peer fingerprint management singleton
*
* This class manages all peer fingerprint storage and retrieval operations,
* providing a single source of truth for peer identity across the entire application.
*
* Fingerprints are SHA-256 hashes of peer static public keys and are only stored
* after successful Noise handshake session establishment.
*
* Key Design Principles:
* - Thread-safe operations using ConcurrentHashMap
* - Bidirectional mapping (peerID ↔ fingerprint)
* - Support for peer ID rotation while maintaining persistent identity
* - Centralized logging for debugging identity management
*/
class PeerFingerprintManager private constructor() {
companion object {
private const val TAG = "PeerFingerprintManager"
@Volatile
private var INSTANCE: PeerFingerprintManager? = null
/**
* Get the singleton instance
*/
fun getInstance(): PeerFingerprintManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: PeerFingerprintManager().also { INSTANCE = it }
}
}
}
// Bidirectional mapping for efficient lookups
private val peerIDToFingerprint = ConcurrentHashMap<String, String>() // peerID -> fingerprint
private val fingerprintToPeerID = ConcurrentHashMap<String, String>() // fingerprint -> current peerID
// MARK: - Fingerprint Storage (Only called after successful Noise handshake)
/**
* Store fingerprint mapping after successful Noise handshake session establishment
* This is the ONLY place where fingerprints should be stored
*
* @param peerID The peer's current ID
* @param publicKey The peer's static public key from Noise handshake
*/
fun storeFingerprintForPeer(peerID: String, publicKey: ByteArray): String {
// get existing fingerprint for this peer and compare
val existingFingerprint = getFingerprintForPeer(peerID)
val fingerprint = calculateFingerprint(publicKey)
if (existingFingerprint != null && existingFingerprint != fingerprint) {
Log.w(TAG, "Fingerprint mismatch for peer $peerID: $existingFingerprint != $fingerprint")
throw IllegalStateException("Fingerprint mismatch for peer $peerID: $existingFingerprint != $fingerprint")
}
// Store bidirectional mapping
peerIDToFingerprint[peerID] = fingerprint
fingerprintToPeerID[fingerprint] = peerID
Log.d(TAG, "Stored fingerprint for peer $peerID: ${fingerprint.take(16)}...")
return fingerprint
}
/**
* Update peer ID mapping while preserving fingerprint identity
* Used for peer ID rotation - when a peer changes their ID but maintains the same static key
*
* @param oldPeerID The previous peer ID (nullable if this is a fresh mapping)
* @param newPeerID The new peer ID
* @param fingerprint The persistent fingerprint (should match existing one)
*/
fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) {
if (newPeerID.isBlank()) {
Log.w(TAG, "Attempted to update mapping with blank newPeerID")
return
}
if (fingerprint.isBlank()) {
Log.w(TAG, "Attempted to update mapping with blank fingerprint")
return
}
// Remove old mapping if exists
oldPeerID?.takeIf { it.isNotBlank() }?.let { oldID ->
val removedFingerprint = peerIDToFingerprint.remove(oldID)
if (removedFingerprint != null && removedFingerprint == fingerprint) {
Log.d(TAG, "Removed old mapping: $oldID -> ${removedFingerprint.take(16)}...")
}
}
// Add new mapping
peerIDToFingerprint[newPeerID] = fingerprint
fingerprintToPeerID[fingerprint] = newPeerID
Log.d(TAG, "Updated peer ID mapping: $newPeerID (was: $oldPeerID), fingerprint: ${fingerprint.take(16)}...")
}
// MARK: - Fingerprint Retrieval
/**
* Get fingerprint for a specific peer ID
*
* @param peerID The peer ID to look up
* @return The fingerprint if found, null otherwise
*/
fun getFingerprintForPeer(peerID: String): String? {
if (peerID.isBlank()) return null
return peerIDToFingerprint[peerID]
}
/**
* Get current peer ID for a specific fingerprint
*
* @param fingerprint The fingerprint to look up
* @return The current peer ID if found, null otherwise
*/
fun getPeerIDForFingerprint(fingerprint: String): String? {
if (fingerprint.isBlank()) return null
return fingerprintToPeerID[fingerprint]
}
/**
* Check if we have a fingerprint for a specific peer
*
* @param peerID The peer ID to check
* @return True if we have a fingerprint for this peer, false otherwise
*/
fun hasFingerprintForPeer(peerID: String): Boolean {
return getFingerprintForPeer(peerID) != null
}
/**
* Get all current peer ID to fingerprint mappings
*
* @return Immutable copy of all mappings
*/
fun getAllPeerFingerprints(): Map<String, String> {
return peerIDToFingerprint.toMap()
}
/**
* Get all current fingerprint to peer ID mappings
*
* @return Immutable copy of all reverse mappings
*/
fun getAllFingerprintMappings(): Map<String, String> {
return fingerprintToPeerID.toMap()
}
// MARK: - Peer Management
/**
* Remove all mappings for a specific peer (called when peer disconnects)
*
* @param peerID The peer ID to remove
*/
fun removePeer(peerID: String) {
if (peerID.isBlank()) return
val fingerprint = peerIDToFingerprint.remove(peerID)
if (fingerprint != null) {
fingerprintToPeerID.remove(fingerprint)
Log.d(TAG, "Removed peer mappings for $peerID: ${fingerprint.take(16)}...")
}
}
/**
* Remove all mappings for a specific fingerprint
*
* @param fingerprint The fingerprint to remove
*/
fun removeFingerprint(fingerprint: String) {
if (fingerprint.isBlank()) return
val peerID = fingerprintToPeerID.remove(fingerprint)
if (peerID != null) {
peerIDToFingerprint.remove(peerID)
Log.d(TAG, "Removed fingerprint mappings for ${fingerprint.take(16)}...: $peerID")
}
}
/**
* Clear all fingerprint mappings (used for emergency clear/panic mode)
*/
fun clearAllFingerprints() {
val count = peerIDToFingerprint.size
peerIDToFingerprint.clear()
fingerprintToPeerID.clear()
Log.d(TAG, "Cleared all fingerprint mappings ($count entries)")
}
// MARK: - Utility Functions
/**
* Calculate SHA-256 fingerprint from public key
*
* @param publicKey The peer's static public key
* @return The hex-encoded SHA-256 hash
*/
private fun calculateFingerprint(publicKey: ByteArray): String {
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey)
return hash.joinToString("") { "%02x".format(it) }
}
/**
* Get debug information about current fingerprint mappings
*
* @return Debug string with mapping counts and summary
*/
fun getDebugInfo(): String {
return buildString {
appendLine("=== PeerFingerprintManager Debug Info ===")
appendLine("Total mappings: ${peerIDToFingerprint.size}")
if (peerIDToFingerprint.isNotEmpty()) {
appendLine("Peer ID -> Fingerprint mappings:")
peerIDToFingerprint.forEach { (peerID, fingerprint) ->
appendLine(" $peerID -> ${fingerprint.take(16)}...")
}
} else {
appendLine("No fingerprint mappings stored")
}
// Verify bidirectional mapping consistency
val inconsistentMappings = mutableListOf<String>()
peerIDToFingerprint.forEach { (peerID, fingerprint) ->
val reversePeerID = fingerprintToPeerID[fingerprint]
if (reversePeerID != peerID) {
inconsistentMappings.add("$peerID -> $fingerprint -> $reversePeerID")
}
}
if (inconsistentMappings.isNotEmpty()) {
appendLine("⚠️ INCONSISTENT MAPPINGS DETECTED:")
inconsistentMappings.forEach { appendLine(" $it") }
}
}
}
}
@@ -7,8 +7,10 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
/** /**
* Manages active peers, nicknames, and RSSI tracking * Manages active peers, nicknames, RSSI tracking, and peer fingerprints
* Extracted from BluetoothMeshService for better separation of concerns * Extracted from BluetoothMeshService for better separation of concerns
*
* Now includes centralized peer fingerprint management via PeerFingerprintManager singleton
*/ */
class PeerManager { class PeerManager {
@@ -25,6 +27,9 @@ class PeerManager {
private val announcedPeers = CopyOnWriteArrayList<String>() private val announcedPeers = CopyOnWriteArrayList<String>()
private val announcedToPeers = CopyOnWriteArrayList<String>() private val announcedToPeers = CopyOnWriteArrayList<String>()
// Centralized fingerprint management
private val fingerprintManager = PeerFingerprintManager.getInstance()
// Delegate for callbacks // Delegate for callbacks
var delegate: PeerManagerDelegate? = null var delegate: PeerManagerDelegate? = null
@@ -81,7 +86,7 @@ class PeerManager {
notifyPeerListUpdate() notifyPeerListUpdate()
return true return true
} }
Log.d(TAG, "Updated peer: $peerID ($nickname)")
return false return false
} }
@@ -95,6 +100,9 @@ class PeerManager {
announcedPeers.remove(peerID) announcedPeers.remove(peerID)
announcedToPeers.remove(peerID) announcedToPeers.remove(peerID)
// Also remove fingerprint mappings
fingerprintManager.removePeer(peerID)
if (notifyDelegate && nickname != null) { if (notifyDelegate && nickname != null) {
delegate?.onPeerDisconnected(nickname) delegate?.onPeerDisconnected(nickname)
notifyPeerListUpdate() notifyPeerListUpdate()
@@ -177,6 +185,10 @@ class PeerManager {
peerRSSI.clear() peerRSSI.clear()
announcedPeers.clear() announcedPeers.clear()
announcedToPeers.clear() announcedToPeers.clear()
// Also clear fingerprint mappings
fingerprintManager.clearAllFingerprints()
notifyPeerListUpdate() notifyPeerListUpdate()
} }
@@ -264,6 +276,83 @@ class PeerManager {
} }
} }
// MARK: - Fingerprint Management (Centralized)
/**
* Store fingerprint for a peer after successful Noise handshake
* This should only be called when a Noise session is established
*
* @param peerID The peer's ID
* @param publicKey The peer's static public key from Noise handshake
*/
fun storeFingerprintForPeer(peerID: String, publicKey: ByteArray): String {
return fingerprintManager.storeFingerprintForPeer(peerID, publicKey)
}
/**
* Update peer ID mapping for peer ID rotation
*
* @param oldPeerID The previous peer ID (nullable)
* @param newPeerID The new peer ID
* @param fingerprint The persistent fingerprint
*/
fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) {
fingerprintManager.updatePeerIDMapping(oldPeerID, newPeerID, fingerprint)
}
/**
* Get fingerprint for a specific peer
*
* @param peerID The peer ID to look up
* @return The fingerprint if found, null otherwise
*/
fun getFingerprintForPeer(peerID: String): String? {
return fingerprintManager.getFingerprintForPeer(peerID)
}
/**
* Get current peer ID for a specific fingerprint
*
* @param fingerprint The fingerprint to look up
* @return The current peer ID if found, null otherwise
*/
fun getPeerIDForFingerprint(fingerprint: String): String? {
return fingerprintManager.getPeerIDForFingerprint(fingerprint)
}
/**
* Check if we have a fingerprint for a specific peer
*
* @param peerID The peer ID to check
* @return True if we have a fingerprint for this peer, false otherwise
*/
fun hasFingerprintForPeer(peerID: String): Boolean {
return fingerprintManager.hasFingerprintForPeer(peerID)
}
/**
* Get all current peer ID to fingerprint mappings
*
* @return Immutable copy of all mappings
*/
fun getAllPeerFingerprints(): Map<String, String> {
return fingerprintManager.getAllPeerFingerprints()
}
/**
* Clear all fingerprint mappings (used for emergency clear)
*/
fun clearAllFingerprints() {
fingerprintManager.clearAllFingerprints()
}
/**
* Get fingerprint manager debug info
*/
fun getFingerprintDebugInfo(): String {
return fingerprintManager.getDebugInfo()
}
/** /**
* Shutdown the manager * Shutdown the manager
*/ */
@@ -146,46 +146,46 @@ class SecurityManager(private val encryptionService: EncryptionService, private
} }
} }
/** // /**
* Handle key exchange packet // * Handle key exchange packet
*/ // */
suspend fun handleKeyExchange(routed: RoutedPacket): Boolean { // suspend fun handleKeyExchange(routed: RoutedPacket): Boolean {
val packet = routed.packet // val packet = routed.packet
val peerID = routed.peerID ?: "unknown" // val peerID = routed.peerID ?: "unknown"
//
if (peerID == myPeerID) return false // if (peerID == myPeerID) return false
//
if (packet.payload.isEmpty()) { // if (packet.payload.isEmpty()) {
Log.w(TAG, "Key exchange packet has empty payload") // Log.w(TAG, "Key exchange packet has empty payload")
return false // return false
} // }
//
// Prevent duplicate key exchange processing // // Prevent duplicate key exchange processing
val exchangeKey = "$peerID-${packet.payload.sliceArray(0 until minOf(16, packet.payload.size)).contentHashCode()}" // val exchangeKey = "$peerID-${packet.payload.sliceArray(0 until minOf(16, packet.payload.size)).contentHashCode()}"
//
if (processedKeyExchanges.contains(exchangeKey)) { // if (processedKeyExchanges.contains(exchangeKey)) {
Log.d(TAG, "Already processed key exchange: $exchangeKey") // Log.d(TAG, "Already processed key exchange: $exchangeKey")
return false // return false
} // }
//
processedKeyExchanges.add(exchangeKey) // processedKeyExchanges.add(exchangeKey)
//
try { // try {
// Process the key exchange // // Process the key exchange
encryptionService.addPeerPublicKey(peerID, packet.payload) // encryptionService.addPeerPublicKey(peerID, packet.payload)
//
Log.d(TAG, "Successfully processed key exchange from $peerID") // Log.d(TAG, "Successfully processed key exchange from $peerID")
//
// Notify delegate // // Notify delegate
delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress) // delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress)
//
return true // return true
//
} catch (e: Exception) { // } catch (e: Exception) {
Log.e(TAG, "Failed to process key exchange from $peerID: ${e.message}") // Log.e(TAG, "Failed to process key exchange from $peerID: ${e.message}")
return false // return false
} // }
} // }
/** /**
* Verify packet signature * Verify packet signature
@@ -3,6 +3,7 @@ package com.bitchat.android.noise
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import com.bitchat.android.identity.SecureIdentityStateManager import com.bitchat.android.identity.SecureIdentityStateManager
import com.bitchat.android.mesh.PeerFingerprintManager
import com.bitchat.android.noise.southernstorm.protocol.Noise import com.bitchat.android.noise.southernstorm.protocol.Noise
import java.security.MessageDigest import java.security.MessageDigest
import java.security.SecureRandom import java.security.SecureRandom
@@ -40,9 +41,8 @@ class NoiseEncryptionService(private val context: Context) {
// Identity management for peer ID rotation support // Identity management for peer ID rotation support
private val identityStateManager: SecureIdentityStateManager private val identityStateManager: SecureIdentityStateManager
// Peer fingerprints mapping (peerID -> fingerprint and fingerprint -> peerID) // Centralized fingerprint management - NO LOCAL STORAGE
private val peerFingerprints = ConcurrentHashMap<String, String>() // peerID -> fingerprint private val fingerprintManager = PeerFingerprintManager.getInstance()
private val fingerprintToPeerID = ConcurrentHashMap<String, String>() // fingerprint -> current peerID
// Callbacks // Callbacks
var onPeerAuthenticated: ((String, String) -> Unit)? = null // (peerID, fingerprint) var onPeerAuthenticated: ((String, String) -> Unit)? = null // (peerID, fingerprint)
@@ -188,14 +188,14 @@ class NoiseEncryptionService(private val context: Context) {
* Get fingerprint for a peer (returns null if peer unknown) * Get fingerprint for a peer (returns null if peer unknown)
*/ */
fun getPeerFingerprint(peerID: String): String? { fun getPeerFingerprint(peerID: String): String? {
return peerFingerprints[peerID] return fingerprintManager.getFingerprintForPeer(peerID)
} }
/** /**
* Get current peer ID for a fingerprint (returns null if not currently online) * Get current peer ID for a fingerprint (returns null if not currently online)
*/ */
fun getPeerID(fingerprint: String): String? { fun getPeerID(fingerprint: String): String? {
return fingerprintToPeerID[fingerprint] return fingerprintManager.getPeerIDForFingerprint(fingerprint)
} }
/** /**
@@ -204,11 +204,8 @@ class NoiseEncryptionService(private val context: Context) {
fun removePeer(peerID: String) { fun removePeer(peerID: String) {
sessionManager.removeSession(peerID) sessionManager.removeSession(peerID)
// Clean up fingerprint mappings // Clean up fingerprint mappings via centralized manager
val fingerprint = peerFingerprints.remove(peerID) fingerprintManager.removePeer(peerID)
if (fingerprint != null) {
fingerprintToPeerID.remove(fingerprint)
}
} }
/** /**
@@ -216,14 +213,8 @@ class NoiseEncryptionService(private val context: Context) {
* This allows favorites/blocking to persist across peer ID changes * This allows favorites/blocking to persist across peer ID changes
*/ */
fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) { fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) {
// Remove old mapping if exists // Use centralized fingerprint manager for peer ID rotation
oldPeerID?.let { oldID -> fingerprintManager.updatePeerIDMapping(oldPeerID, newPeerID, fingerprint)
peerFingerprints.remove(oldID)
}
// Add new mapping
peerFingerprints[newPeerID] = fingerprint
fingerprintToPeerID[fingerprint] = newPeerID
} }
// MARK: - Channel Encryption // MARK: - Channel Encryption
@@ -318,12 +309,12 @@ class NoiseEncryptionService(private val context: Context) {
* Handle session establishment (called when Noise handshake completes) * Handle session establishment (called when Noise handshake completes)
*/ */
private fun handleSessionEstablished(peerID: String, remoteStaticKey: ByteArray) { private fun handleSessionEstablished(peerID: String, remoteStaticKey: ByteArray) {
// Calculate fingerprint from remote static key // Store fingerprint mapping via centralized manager
val fingerprint = calculateFingerprint(remoteStaticKey) // This is the ONLY place where fingerprints are stored - after successful Noise handshake
fingerprintManager.storeFingerprintForPeer(peerID, remoteStaticKey)
// Store fingerprint mapping // Calculate fingerprint for logging and callback
peerFingerprints[peerID] = fingerprint val fingerprint = calculateFingerprint(remoteStaticKey)
fingerprintToPeerID[fingerprint] = peerID
Log.d(TAG, "Session established with $peerID, fingerprint: ${fingerprint.take(16)}...") Log.d(TAG, "Session established with $peerID, fingerprint: ${fingerprint.take(16)}...")
@@ -346,8 +337,7 @@ class NoiseEncryptionService(private val context: Context) {
fun shutdown() { fun shutdown() {
sessionManager.shutdown() sessionManager.shutdown()
channelEncryption.clear() channelEncryption.clear()
peerFingerprints.clear() // No need to clear fingerprints here - they are managed centrally
fingerprintToPeerID.clear()
} }
} }
@@ -83,7 +83,7 @@ class ChatState {
private val _favoritePeers = MutableLiveData<Set<String>>(emptySet()) private val _favoritePeers = MutableLiveData<Set<String>>(emptySet())
val favoritePeers: LiveData<Set<String>> = _favoritePeers val favoritePeers: LiveData<Set<String>> = _favoritePeers
val peerIDToPublicKeyFingerprint = mutableMapOf<String, String>() // peerIDToPublicKeyFingerprint REMOVED - fingerprints now handled centrally in PeerManager
// Navigation state // Navigation state
private val _showAppInfo = MutableLiveData<Boolean>(false) private val _showAppInfo = MutableLiveData<Boolean>(false)
@@ -344,9 +344,7 @@ class ChatViewModel(
return meshDelegateHandler.isFavorite(peerID) return meshDelegateHandler.isFavorite(peerID)
} }
override fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray) { // registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
privateChatManager.registerPeerPublicKey(peerID, publicKeyData)
}
// MARK: - Emergency Clear // MARK: - Emergency Clear
@@ -153,7 +153,5 @@ class MeshDelegateHandler(
return privateChatManager.isFavorite(peerID) return privateChatManager.isFavorite(peerID)
} }
override fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray) { // registerPeerPublicKey REMOVED - fingerprints now handled centrally in PeerManager
privateChatManager.registerPeerPublicKey(peerID, publicKeyData)
}
} }
@@ -2,11 +2,13 @@ package com.bitchat.android.ui
import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryStatus import com.bitchat.android.model.DeliveryStatus
import com.bitchat.android.mesh.PeerFingerprintManager
import java.util.* import java.util.*
import android.util.Log import android.util.Log
/** /**
* Handles private chat functionality including peer management and blocking * Handles private chat functionality including peer management and blocking
* Now uses centralized PeerFingerprintManager for all fingerprint operations
*/ */
class PrivateChatManager( class PrivateChatManager(
private val state: ChatState, private val state: ChatState,
@@ -18,8 +20,8 @@ class PrivateChatManager(
private const val TAG = "PrivateChatManager" private const val TAG = "PrivateChatManager"
} }
// Peer identification mapping // Use centralized fingerprint management - NO LOCAL STORAGE
private val peerIDToPublicKeyFingerprint = mutableMapOf<String, String>() private val fingerprintManager = PeerFingerprintManager.getInstance()
// MARK: - Private Chat Lifecycle // MARK: - Private Chat Lifecycle
@@ -89,20 +91,13 @@ class PrivateChatManager(
// MARK: - Peer Management // MARK: - Peer Management
fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray) {
val md = java.security.MessageDigest.getInstance("SHA-256")
val hash = md.digest(publicKeyData)
val fingerprint = hash.take(8).joinToString("") { "%02x".format(it) }
peerIDToPublicKeyFingerprint[peerID] = fingerprint
}
fun isPeerBlocked(peerID: String): Boolean { fun isPeerBlocked(peerID: String): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
return fingerprint != null && dataManager.isUserBlocked(fingerprint) return fingerprint != null && dataManager.isUserBlocked(fingerprint)
} }
fun toggleFavorite(peerID: String) { fun toggleFavorite(peerID: String) {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] ?: return val fingerprint = fingerprintManager.getFingerprintForPeer(peerID) ?: return
Log.d(TAG, "toggleFavorite called for peerID: $peerID, fingerprint: $fingerprint") Log.d(TAG, "toggleFavorite called for peerID: $peerID, fingerprint: $fingerprint")
@@ -125,28 +120,28 @@ class PrivateChatManager(
state.setFavoritePeers(newFavorites) state.setFavoritePeers(newFavorites)
Log.d(TAG, "Force updated favorite peers state. New favorites: $newFavorites") Log.d(TAG, "Force updated favorite peers state. New favorites: $newFavorites")
Log.d(TAG, "All peer fingerprints: $peerIDToPublicKeyFingerprint") Log.d(TAG, "All peer fingerprints: ${fingerprintManager.getAllPeerFingerprints()}")
} }
fun isFavorite(peerID: String): Boolean { fun isFavorite(peerID: String): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] ?: return false val fingerprint = fingerprintManager.getFingerprintForPeer(peerID) ?: return false
val isFav = dataManager.isFavorite(fingerprint) val isFav = dataManager.isFavorite(fingerprint)
Log.d(TAG, "isFavorite check: peerID=$peerID, fingerprint=$fingerprint, result=$isFav") Log.d(TAG, "isFavorite check: peerID=$peerID, fingerprint=$fingerprint, result=$isFav")
return isFav return isFav
} }
fun getPeerFingerprint(peerID: String): String? { fun getPeerFingerprint(peerID: String): String? {
return peerIDToPublicKeyFingerprint[peerID] return fingerprintManager.getFingerprintForPeer(peerID)
} }
fun getPeerFingerprints(): Map<String, String> { fun getPeerFingerprints(): Map<String, String> {
return peerIDToPublicKeyFingerprint.toMap() return fingerprintManager.getAllPeerFingerprints()
} }
// MARK: - Block/Unblock Operations // MARK: - Block/Unblock Operations
fun blockPeer(peerID: String, meshService: Any): Boolean { fun blockPeer(peerID: String, meshService: Any): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null) { if (fingerprint != null) {
dataManager.addBlockedUser(fingerprint) dataManager.addBlockedUser(fingerprint)
@@ -170,7 +165,7 @@ class PrivateChatManager(
} }
fun unblockPeer(peerID: String, meshService: Any): Boolean { fun unblockPeer(peerID: String, meshService: Any): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) { if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
dataManager.removeBlockedUser(fingerprint) dataManager.removeBlockedUser(fingerprint)
@@ -208,7 +203,7 @@ class PrivateChatManager(
val peerID = getPeerIDForNickname(targetName, meshService) val peerID = getPeerIDForNickname(targetName, meshService)
if (peerID != null) { if (peerID != null) {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) { if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
return unblockPeer(peerID, meshService) return unblockPeer(peerID, meshService)
} else { } else {
@@ -288,12 +283,14 @@ class PrivateChatManager(
fun clearAllPrivateChats() { fun clearAllPrivateChats() {
state.setSelectedPrivateChatPeer(null) state.setSelectedPrivateChatPeer(null)
state.setUnreadPrivateMessages(emptySet()) state.setUnreadPrivateMessages(emptySet())
peerIDToPublicKeyFingerprint.clear()
// Clear fingerprints via centralized manager (only if needed for emergency clear)
// Note: This will be handled by the parent PeerManager.clearAllPeers()
} }
// MARK: - Public Getters // MARK: - Public Getters
fun getAllPeerFingerprints(): Map<String, String> { fun getAllPeerFingerprints(): Map<String, String> {
return peerIDToPublicKeyFingerprint.toMap() return fingerprintManager.getAllPeerFingerprints()
} }
} }