fix(security): Clear in-memory keys during panic mode (#596)

* fix(security): Clear in-memory keys during panic mode #588

* feat: Recreate mesh service after panic clear (#602)

* feat: Recreate mesh service after panic clear

This commit refactors the panic clear process to ensure a new mesh identity is immediately created and applied.

Previously, the `ChatViewModel` would clear sensitive data, but the recreation of the `BluetoothMeshService` was handled externally. This could lead to a delay or failure in adopting the new identity.

Key changes:
- Introduces `MeshServiceHolder` to manage the lifecycle of the `BluetoothMeshService` instance.
- Adds `recreateMeshServiceAfterPanic()` to `ChatViewModel`, which now explicitly clears the old service instance and creates a new one with a regenerated identity.
- The `meshService` property in `ChatViewModel` is now a `var` to allow it to be replaced with the fresh instance post-panic.
- The new service is started, and a broadcast announcement is sent immediately, ensuring the new peer ID is used on the network.

* fix: Ensure mesh service is properly managed in foreground service

* refactor: Decouple handlers from direct service reference

This commit updates the `VerificationHandler` and `MediaSendingManager` to receive the `meshService` via a lambda function (`getMeshService`) instead of a direct reference.

This change decouples the handlers from the service instance, preventing them from holding a stale reference if the service reconnects or changes. By invoking the lambda to get the current service instance when needed, it ensures they always interact with the active `meshService`.

* fix: restart bluetooth

---------

Co-authored-by: Moe Hamade <69801237+moehamade@users.noreply.github.com>
This commit is contained in:
callebtc
2026-01-15 15:11:10 +07:00
committed by GitHub
co-authored by Moe Hamade
parent 9dfebd73db
commit ae67fa4344
9 changed files with 183 additions and 31 deletions
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import com.bitchat.android.mesh.BluetoothMeshDelegate
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.service.MeshServiceHolder
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.BitchatMessageType
import com.bitchat.android.nostr.NostrIdentityBridge
@@ -37,8 +38,12 @@ import java.security.MessageDigest
*/
class ChatViewModel(
application: Application,
val meshService: BluetoothMeshService
initialMeshService: BluetoothMeshService
) : AndroidViewModel(application), BluetoothMeshDelegate {
// Made var to support mesh service replacement after panic clear
var meshService: BluetoothMeshService = initialMeshService
private set
private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } }
companion object {
@@ -104,7 +109,7 @@ class ChatViewModel(
private val verificationHandler = VerificationHandler(
context = application.applicationContext,
scope = viewModelScope,
meshService = meshService,
getMeshService = { meshService },
identityManager = identityManager,
state = state,
notificationManager = notificationManager,
@@ -113,7 +118,7 @@ class ChatViewModel(
val verifiedFingerprints = verificationHandler.verifiedFingerprints
// Media file sending manager
private val mediaSendingManager = MediaSendingManager(state, messageManager, channelManager, meshService)
private val mediaSendingManager = MediaSendingManager(state, messageManager, channelManager) { meshService }
// Delegate handler for mesh callbacks
private val meshDelegateHandler = MeshDelegateHandler(
@@ -908,6 +913,11 @@ class ChatViewModel(
privateChatManager.clearAllPrivateChats()
dataManager.clearAllData()
// Clear seen message store
try {
com.bitchat.android.services.SeenMessageStore.getInstance(getApplication()).clear()
} catch (_: Exception) { }
// Clear all mesh service data
clearAllMeshServiceData()
@@ -935,10 +945,37 @@ class ChatViewModel(
state.setNickname(newNickname)
dataManager.saveNickname(newNickname)
Log.w(TAG, "🚨 PANIC MODE COMPLETED - All sensitive data cleared")
// Note: Mesh service restart is now handled by MainActivity
// This method now only clears data, not mesh service lifecycle
// Recreate mesh service with fresh identity
recreateMeshServiceAfterPanic()
Log.w(TAG, "🚨 PANIC MODE COMPLETED - New identity: ${meshService.myPeerID}")
}
/**
* Recreate the mesh service with a fresh identity after panic clear.
* This ensures the new cryptographic keys are used for a new peer ID.
*/
private fun recreateMeshServiceAfterPanic() {
val oldPeerID = meshService.myPeerID
// Clear the holder so getOrCreate() returns a fresh instance
MeshServiceHolder.clear()
// Create fresh mesh service with new identity (keys were regenerated in clearAllCryptographicData)
val freshMeshService = MeshServiceHolder.getOrCreate(getApplication())
// Replace our reference and set up the new service
meshService = freshMeshService
meshService.delegate = this
// Restart mesh operations with new identity
meshService.startServices()
meshService.sendBroadcastAnnounce()
Log.d(
TAG,
"✅ Mesh service recreated. Old peerID: $oldPeerID, New peerID: ${meshService.myPeerID}"
)
}
/**
@@ -16,8 +16,11 @@ class MediaSendingManager(
private val state: ChatState,
private val messageManager: MessageManager,
private val channelManager: ChannelManager,
private val meshService: BluetoothMeshService
private val getMeshService: () -> BluetoothMeshService
) {
// Helper to get current mesh service (may change after panic clear)
private val meshService: BluetoothMeshService
get() = getMeshService()
companion object {
private const val TAG = "MediaSendingManager"
private const val MAX_FILE_SIZE = com.bitchat.android.util.AppConstants.Media.MAX_FILE_SIZE_BYTES // 50MB limit
@@ -26,12 +26,15 @@ import java.util.concurrent.ConcurrentHashMap
class VerificationHandler(
private val context: Context,
private val scope: CoroutineScope,
private val meshService: BluetoothMeshService,
private val getMeshService: () -> BluetoothMeshService,
private val identityManager: SecureIdentityStateManager,
private val state: ChatState,
private val notificationManager: NotificationManager,
private val messageManager: MessageManager
) {
// Helper to get current mesh service (may change after panic clear)
private val meshService: BluetoothMeshService
get() = getMeshService()
private val _verifiedFingerprints = MutableStateFlow<Set<String>>(emptySet())
val verifiedFingerprints: StateFlow<Set<String>> = _verifiedFingerprints.asStateFlow()