mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 03:45:21 +00:00
Refactor fingerprint manager (#185)
* centralized fingerprint manager * centralized fingerprintmanager * update fingerprint only if no collision
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user