mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 02:25:21 +00:00
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:
@@ -145,6 +145,12 @@ open class EncryptionService(private val context: Context) {
|
|||||||
val prefs = context.getSharedPreferences("bitchat_crypto", Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences("bitchat_crypto", Context.MODE_PRIVATE)
|
||||||
prefs.edit().remove(ED25519_PRIVATE_KEY_PREF).apply()
|
prefs.edit().remove(ED25519_PRIVATE_KEY_PREF).apply()
|
||||||
Log.d(TAG, "🗑️ Cleared Ed25519 signing keys from preferences")
|
Log.d(TAG, "🗑️ Cleared Ed25519 signing keys from preferences")
|
||||||
|
|
||||||
|
// Generate new keys immediately
|
||||||
|
val keyPair = loadOrCreateEd25519KeyPair()
|
||||||
|
ed25519PrivateKey = keyPair.private as Ed25519PrivateKeyParameters
|
||||||
|
ed25519PublicKey = keyPair.public as Ed25519PublicKeyParameters
|
||||||
|
Log.d(TAG, "✅ Rotated Ed25519 signing keys in memory")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Failed to clear Ed25519 keys: ${e.message}")
|
Log.e(TAG, "❌ Failed to clear Ed25519 keys: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1375,6 +1375,9 @@ class BluetoothMeshService(private val context: Context) {
|
|||||||
fun clearAllInternalData() {
|
fun clearAllInternalData() {
|
||||||
Log.w(TAG, "🚨 Clearing all mesh service internal data")
|
Log.w(TAG, "🚨 Clearing all mesh service internal data")
|
||||||
try {
|
try {
|
||||||
|
// Stop services to cease broadcasting old ID immediately
|
||||||
|
stopServices()
|
||||||
|
|
||||||
// Clear all managers
|
// Clear all managers
|
||||||
fragmentManager.clearAllFragments()
|
fragmentManager.clearAllFragments()
|
||||||
storeForwardManager.clearAllCache()
|
storeForwardManager.clearAllCache()
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ class NoiseEncryptionService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static identity key (persistent across app restarts) - loaded from secure storage
|
// Static identity key (persistent across app restarts) - loaded from secure storage
|
||||||
private val staticIdentityPrivateKey: ByteArray
|
private var staticIdentityPrivateKey: ByteArray
|
||||||
private val staticIdentityPublicKey: ByteArray
|
private var staticIdentityPublicKey: ByteArray
|
||||||
|
|
||||||
// Ed25519 signing key (persistent across app restarts) - loaded from secure storage
|
// Ed25519 signing key (persistent across app restarts) - loaded from secure storage
|
||||||
private val signingPrivateKey: ByteArray
|
private var signingPrivateKey: ByteArray
|
||||||
private val signingPublicKey: ByteArray
|
private var signingPublicKey: ByteArray
|
||||||
|
|
||||||
// Session management
|
// Session management
|
||||||
private val sessionManager: NoiseSessionManager
|
private lateinit var sessionManager: NoiseSessionManager
|
||||||
|
|
||||||
// Channel encryption for password-protected channels
|
// Channel encryption for password-protected channels
|
||||||
private val channelEncryption = NoiseChannelEncryption()
|
private val channelEncryption = NoiseChannelEncryption()
|
||||||
@@ -56,6 +56,32 @@ class NoiseEncryptionService(private val context: Context) {
|
|||||||
// Initialize identity state manager for persistent storage
|
// Initialize identity state manager for persistent storage
|
||||||
identityStateManager = SecureIdentityStateManager(context)
|
identityStateManager = SecureIdentityStateManager(context)
|
||||||
|
|
||||||
|
// Load or create keys - temporary placeholders
|
||||||
|
staticIdentityPrivateKey = ByteArray(32)
|
||||||
|
staticIdentityPublicKey = ByteArray(32)
|
||||||
|
signingPrivateKey = ByteArray(32)
|
||||||
|
signingPublicKey = ByteArray(32)
|
||||||
|
|
||||||
|
loadOrGenerateKeys()
|
||||||
|
|
||||||
|
// Initialize session manager
|
||||||
|
initializeSessionManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeSessionManager() {
|
||||||
|
// Create new session manager with current keys
|
||||||
|
sessionManager = NoiseSessionManager(staticIdentityPrivateKey, staticIdentityPublicKey)
|
||||||
|
|
||||||
|
// Set up session callbacks
|
||||||
|
sessionManager.onSessionEstablished = { peerID, remoteStaticKey ->
|
||||||
|
handleSessionEstablished(peerID, remoteStaticKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure any other callbacks are wired if needed
|
||||||
|
// sessionManager.onSessionFailed could be wired if we exposed it
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadOrGenerateKeys() {
|
||||||
// Load or create static identity key (persistent across sessions)
|
// Load or create static identity key (persistent across sessions)
|
||||||
val loadedKeyPair = identityStateManager.loadStaticKey()
|
val loadedKeyPair = identityStateManager.loadStaticKey()
|
||||||
if (loadedKeyPair != null) {
|
if (loadedKeyPair != null) {
|
||||||
@@ -89,14 +115,6 @@ class NoiseEncryptionService(private val context: Context) {
|
|||||||
identityStateManager.saveSigningKey(signingPrivateKey, signingPublicKey)
|
identityStateManager.saveSigningKey(signingPrivateKey, signingPublicKey)
|
||||||
Log.d(TAG, "Generated and saved new Ed25519 signing key")
|
Log.d(TAG, "Generated and saved new Ed25519 signing key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize session manager
|
|
||||||
sessionManager = NoiseSessionManager(staticIdentityPrivateKey, staticIdentityPublicKey)
|
|
||||||
|
|
||||||
// Set up session callbacks
|
|
||||||
sessionManager.onSessionEstablished = { peerID, remoteStaticKey ->
|
|
||||||
handleSessionEstablished(peerID, remoteStaticKey)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public Interface
|
// MARK: - Public Interface
|
||||||
@@ -135,7 +153,23 @@ class NoiseEncryptionService(private val context: Context) {
|
|||||||
* Clear persistent identity (for panic mode)
|
* Clear persistent identity (for panic mode)
|
||||||
*/
|
*/
|
||||||
fun clearPersistentIdentity() {
|
fun clearPersistentIdentity() {
|
||||||
|
Log.w(TAG, "🚨 Panic Mode: Clearing persistent identity and rotating in-memory keys")
|
||||||
|
|
||||||
|
// 1. Clear storage
|
||||||
identityStateManager.clearIdentityData()
|
identityStateManager.clearIdentityData()
|
||||||
|
|
||||||
|
// 2. Clear all sessions immediately
|
||||||
|
if (::sessionManager.isInitialized) {
|
||||||
|
sessionManager.shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Regenerate keys immediately (in-memory rotation)
|
||||||
|
loadOrGenerateKeys()
|
||||||
|
|
||||||
|
// 4. Re-initialize SessionManager with new keys
|
||||||
|
initializeSessionManager()
|
||||||
|
|
||||||
|
Log.d(TAG, "✅ Identity cleared and keys rotated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Handshake Management
|
// MARK: - Handshake Management
|
||||||
@@ -478,7 +512,9 @@ class NoiseEncryptionService(private val context: Context) {
|
|||||||
* Clean shutdown
|
* Clean shutdown
|
||||||
*/
|
*/
|
||||||
fun shutdown() {
|
fun shutdown() {
|
||||||
|
if (::sessionManager.isInitialized) {
|
||||||
sessionManager.shutdown()
|
sessionManager.shutdown()
|
||||||
|
}
|
||||||
channelEncryption.clear()
|
channelEncryption.clear()
|
||||||
// No need to clear fingerprints here - they are managed centrally
|
// No need to clear fingerprints here - they are managed centrally
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.content.Intent
|
|||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.bitchat.android.MainActivity
|
import com.bitchat.android.MainActivity
|
||||||
@@ -112,7 +113,8 @@ class MeshForegroundService : Service() {
|
|||||||
|
|
||||||
private lateinit var notificationManager: NotificationManagerCompat
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
private var updateJob: Job? = null
|
private var updateJob: Job? = null
|
||||||
private var meshService: BluetoothMeshService? = null
|
private val meshService: BluetoothMeshService?
|
||||||
|
get() = MeshServiceHolder.meshService
|
||||||
private val serviceJob = Job()
|
private val serviceJob = Job()
|
||||||
private val scope = CoroutineScope(Dispatchers.Default + serviceJob)
|
private val scope = CoroutineScope(Dispatchers.Default + serviceJob)
|
||||||
private var isInForeground: Boolean = false
|
private var isInForeground: Boolean = false
|
||||||
@@ -123,15 +125,15 @@ class MeshForegroundService : Service() {
|
|||||||
notificationManager = NotificationManagerCompat.from(this)
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
createChannel()
|
createChannel()
|
||||||
|
|
||||||
// Adopt or create the mesh service
|
// Ensure mesh service exists in holder (create if needed)
|
||||||
val existing = MeshServiceHolder.meshService
|
val existing = MeshServiceHolder.meshService
|
||||||
meshService = existing ?: MeshServiceHolder.getOrCreate(applicationContext)
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
android.util.Log.d("MeshForegroundService", "Adopted existing BluetoothMeshService from holder")
|
Log.d("MeshForegroundService", "Using existing BluetoothMeshService from holder")
|
||||||
} else {
|
} else {
|
||||||
android.util.Log.i("MeshForegroundService", "Created/adopted new BluetoothMeshService via holder")
|
val created = MeshServiceHolder.getOrCreate(applicationContext)
|
||||||
|
Log.i("MeshForegroundService", "Created new BluetoothMeshService via holder")
|
||||||
|
MeshServiceHolder.attach(created)
|
||||||
}
|
}
|
||||||
MeshServiceHolder.attach(meshService!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
@@ -227,7 +229,8 @@ class MeshForegroundService : Service() {
|
|||||||
if (!hasBluetoothPermissions()) return
|
if (!hasBluetoothPermissions()) return
|
||||||
try {
|
try {
|
||||||
android.util.Log.d("MeshForegroundService", "Ensuring mesh service is started")
|
android.util.Log.d("MeshForegroundService", "Ensuring mesh service is started")
|
||||||
meshService?.startServices()
|
val service = MeshServiceHolder.getOrCreate(applicationContext)
|
||||||
|
service.startServices()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("MeshForegroundService", "Failed to start mesh service: ${e.message}")
|
android.util.Log.e("MeshForegroundService", "Failed to start mesh service: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ class SeenMessageStore private constructor(private val context: Context) {
|
|||||||
persist()
|
persist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized fun clear() {
|
||||||
|
delivered.clear()
|
||||||
|
read.clear()
|
||||||
|
persist()
|
||||||
|
}
|
||||||
|
|
||||||
private fun trim(set: LinkedHashSet<String>) {
|
private fun trim(set: LinkedHashSet<String>) {
|
||||||
if (set.size <= MAX_IDS) return
|
if (set.size <= MAX_IDS) return
|
||||||
val it = set.iterator()
|
val it = set.iterator()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import com.bitchat.android.mesh.BluetoothMeshDelegate
|
import com.bitchat.android.mesh.BluetoothMeshDelegate
|
||||||
import com.bitchat.android.mesh.BluetoothMeshService
|
import com.bitchat.android.mesh.BluetoothMeshService
|
||||||
|
import com.bitchat.android.service.MeshServiceHolder
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
import com.bitchat.android.model.BitchatMessageType
|
import com.bitchat.android.model.BitchatMessageType
|
||||||
import com.bitchat.android.nostr.NostrIdentityBridge
|
import com.bitchat.android.nostr.NostrIdentityBridge
|
||||||
@@ -37,8 +38,12 @@ import java.security.MessageDigest
|
|||||||
*/
|
*/
|
||||||
class ChatViewModel(
|
class ChatViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
val meshService: BluetoothMeshService
|
initialMeshService: BluetoothMeshService
|
||||||
) : AndroidViewModel(application), BluetoothMeshDelegate {
|
) : 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 } }
|
private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -104,7 +109,7 @@ class ChatViewModel(
|
|||||||
private val verificationHandler = VerificationHandler(
|
private val verificationHandler = VerificationHandler(
|
||||||
context = application.applicationContext,
|
context = application.applicationContext,
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
meshService = meshService,
|
getMeshService = { meshService },
|
||||||
identityManager = identityManager,
|
identityManager = identityManager,
|
||||||
state = state,
|
state = state,
|
||||||
notificationManager = notificationManager,
|
notificationManager = notificationManager,
|
||||||
@@ -113,7 +118,7 @@ class ChatViewModel(
|
|||||||
val verifiedFingerprints = verificationHandler.verifiedFingerprints
|
val verifiedFingerprints = verificationHandler.verifiedFingerprints
|
||||||
|
|
||||||
// Media file sending manager
|
// 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
|
// Delegate handler for mesh callbacks
|
||||||
private val meshDelegateHandler = MeshDelegateHandler(
|
private val meshDelegateHandler = MeshDelegateHandler(
|
||||||
@@ -908,6 +913,11 @@ class ChatViewModel(
|
|||||||
privateChatManager.clearAllPrivateChats()
|
privateChatManager.clearAllPrivateChats()
|
||||||
dataManager.clearAllData()
|
dataManager.clearAllData()
|
||||||
|
|
||||||
|
// Clear seen message store
|
||||||
|
try {
|
||||||
|
com.bitchat.android.services.SeenMessageStore.getInstance(getApplication()).clear()
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
|
||||||
// Clear all mesh service data
|
// Clear all mesh service data
|
||||||
clearAllMeshServiceData()
|
clearAllMeshServiceData()
|
||||||
|
|
||||||
@@ -935,10 +945,37 @@ class ChatViewModel(
|
|||||||
state.setNickname(newNickname)
|
state.setNickname(newNickname)
|
||||||
dataManager.saveNickname(newNickname)
|
dataManager.saveNickname(newNickname)
|
||||||
|
|
||||||
Log.w(TAG, "🚨 PANIC MODE COMPLETED - All sensitive data cleared")
|
// Recreate mesh service with fresh identity
|
||||||
|
recreateMeshServiceAfterPanic()
|
||||||
|
|
||||||
// Note: Mesh service restart is now handled by MainActivity
|
Log.w(TAG, "🚨 PANIC MODE COMPLETED - New identity: ${meshService.myPeerID}")
|
||||||
// This method now only clears data, not mesh service lifecycle
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 state: ChatState,
|
||||||
private val messageManager: MessageManager,
|
private val messageManager: MessageManager,
|
||||||
private val channelManager: ChannelManager,
|
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 {
|
companion object {
|
||||||
private const val TAG = "MediaSendingManager"
|
private const val TAG = "MediaSendingManager"
|
||||||
private const val MAX_FILE_SIZE = com.bitchat.android.util.AppConstants.Media.MAX_FILE_SIZE_BYTES // 50MB limit
|
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(
|
class VerificationHandler(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val meshService: BluetoothMeshService,
|
private val getMeshService: () -> BluetoothMeshService,
|
||||||
private val identityManager: SecureIdentityStateManager,
|
private val identityManager: SecureIdentityStateManager,
|
||||||
private val state: ChatState,
|
private val state: ChatState,
|
||||||
private val notificationManager: NotificationManager,
|
private val notificationManager: NotificationManager,
|
||||||
private val messageManager: MessageManager
|
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())
|
private val _verifiedFingerprints = MutableStateFlow<Set<String>>(emptySet())
|
||||||
val verifiedFingerprints: StateFlow<Set<String>> = _verifiedFingerprints.asStateFlow()
|
val verifiedFingerprints: StateFlow<Set<String>> = _verifiedFingerprints.asStateFlow()
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.bitchat.android.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import com.bitchat.android.noise.NoiseEncryptionService
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import java.util.Arrays
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
class EncryptionServiceTest {
|
||||||
|
|
||||||
|
private lateinit var context: Context
|
||||||
|
private lateinit var encryptionService: EncryptionService
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
context = ApplicationProvider.getApplicationContext()
|
||||||
|
encryptionService = EncryptionService(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test clearPersistentIdentity changes keys`() {
|
||||||
|
// 1. Get initial keys
|
||||||
|
val initialStaticKey = encryptionService.getStaticPublicKey()
|
||||||
|
val initialSigningKey = encryptionService.getSigningPublicKey()
|
||||||
|
val initialFingerprint = encryptionService.getIdentityFingerprint()
|
||||||
|
|
||||||
|
assertNotNull("Initial static key should not be null", initialStaticKey)
|
||||||
|
assertNotNull("Initial signing key should not be null", initialSigningKey)
|
||||||
|
|
||||||
|
// 2. Call clearPersistentIdentity (Panic Mode)
|
||||||
|
encryptionService.clearPersistentIdentity()
|
||||||
|
|
||||||
|
// 3. Get keys again.
|
||||||
|
val afterStaticKey = encryptionService.getStaticPublicKey()
|
||||||
|
val afterSigningKey = encryptionService.getSigningPublicKey()
|
||||||
|
val afterFingerprint = encryptionService.getIdentityFingerprint()
|
||||||
|
|
||||||
|
// 4. Verify keys are different (Panic Mode should clear/rotate in-memory keys)
|
||||||
|
// Note: We use string comparison for byte arrays to be safe in assertion messages
|
||||||
|
assertNotEquals("Static key should change after panic",
|
||||||
|
Arrays.toString(initialStaticKey), Arrays.toString(afterStaticKey))
|
||||||
|
|
||||||
|
assertNotEquals("Signing key should change after panic",
|
||||||
|
Arrays.toString(initialSigningKey), Arrays.toString(afterSigningKey))
|
||||||
|
|
||||||
|
assertNotEquals("Fingerprint should change after panic",
|
||||||
|
initialFingerprint, afterFingerprint)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user