diff --git a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt index f7611d70..d693b76c 100644 --- a/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt +++ b/app/src/main/java/com/bitchat/android/crypto/EncryptionService.kt @@ -1,259 +1,289 @@ package com.bitchat.android.crypto import android.content.Context -import android.content.SharedPreferences -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import org.bouncycastle.crypto.agreement.X25519Agreement -import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator -import org.bouncycastle.crypto.generators.X25519KeyPairGenerator -import org.bouncycastle.crypto.params.* -import org.bouncycastle.crypto.signers.Ed25519Signer -import org.bouncycastle.crypto.util.PrivateKeyFactory -import org.bouncycastle.crypto.util.PublicKeyFactory -import org.bouncycastle.jcajce.provider.digest.SHA256 +import android.util.Log +import com.bitchat.android.noise.NoiseEncryptionService import java.security.SecureRandom -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.SecretKeySpec -import kotlin.experimental.and +import java.util.concurrent.ConcurrentHashMap /** - * Encryption service that's 100% compatible with iOS version - * Uses the same cryptographic algorithms and key derivation + * Encryption service that now uses NoiseEncryptionService internally + * Maintains the same public API for backward compatibility + * + * This is the main interface for all encryption/decryption operations in bitchat. + * It now uses the Noise protocol for secure transport encryption with proper session management. */ class EncryptionService(private val context: Context) { - // Key agreement keys for encryption - private val privateKey: X25519PrivateKeyParameters - private val publicKey: X25519PublicKeyParameters + companion object { + private const val TAG = "EncryptionService" + } - // Signing keys for authentication - private val signingPrivateKey: Ed25519PrivateKeyParameters - private val signingPublicKey: Ed25519PublicKeyParameters + // Core Noise encryption service + private val noiseService: NoiseEncryptionService = NoiseEncryptionService(context) - // Persistent identity for favorites (separate from ephemeral keys) - private lateinit var identityKey: Ed25519PrivateKeyParameters - private lateinit var identityPublicKey: Ed25519PublicKeyParameters + // Session tracking for established connections + private val establishedSessions = ConcurrentHashMap() // peerID -> fingerprint - // Storage for peer keys - private val peerPublicKeys = mutableMapOf() - private val peerSigningKeys = mutableMapOf() - private val peerIdentityKeys = mutableMapOf() - private val sharedSecrets = mutableMapOf() - - private val prefs: SharedPreferences = context.getSharedPreferences("bitchat_crypto", Context.MODE_PRIVATE) - private val secureRandom = SecureRandom() + // Callbacks for UI state updates + var onSessionEstablished: ((String) -> Unit)? = null // peerID + var onSessionLost: ((String) -> Unit)? = null // peerID + var onHandshakeRequired: ((String) -> Unit)? = null // peerID init { - // Generate ephemeral key pairs for this session - val x25519Generator = X25519KeyPairGenerator() - x25519Generator.init(X25519KeyGenerationParameters(secureRandom)) - val x25519KeyPair = x25519Generator.generateKeyPair() - privateKey = x25519KeyPair.private as X25519PrivateKeyParameters - publicKey = x25519KeyPair.public as X25519PublicKeyParameters + // Set up NoiseEncryptionService callbacks + noiseService.onPeerAuthenticated = { peerID, fingerprint -> + Log.d(TAG, "✅ Noise session established with $peerID, fingerprint: ${fingerprint.take(16)}...") + establishedSessions[peerID] = fingerprint + onSessionEstablished?.invoke(peerID) + } - val ed25519Generator = Ed25519KeyPairGenerator() - ed25519Generator.init(Ed25519KeyGenerationParameters(secureRandom)) - val ed25519KeyPair = ed25519Generator.generateKeyPair() - signingPrivateKey = ed25519KeyPair.private as Ed25519PrivateKeyParameters - signingPublicKey = ed25519KeyPair.public as Ed25519PublicKeyParameters - - // Load or create persistent identity key - val identityKeyBytes = prefs.getString("identity_key", null) - if (identityKeyBytes != null) { - try { - val keyBytes = android.util.Base64.decode(identityKeyBytes, android.util.Base64.DEFAULT) - identityKey = Ed25519PrivateKeyParameters(keyBytes, 0) - } catch (e: Exception) { - // Create new identity key if loading fails - val newIdentityKeyPair = ed25519Generator.generateKeyPair() - identityKey = newIdentityKeyPair.private as Ed25519PrivateKeyParameters - saveIdentityKey() - } - } else { - // First run - create and save identity key - val newIdentityKeyPair = ed25519Generator.generateKeyPair() - identityKey = newIdentityKeyPair.private as Ed25519PrivateKeyParameters - saveIdentityKey() + noiseService.onHandshakeRequired = { peerID -> + Log.d(TAG, "🤝 Handshake required for $peerID") + onHandshakeRequired?.invoke(peerID) } - identityPublicKey = Ed25519PublicKeyParameters(identityKey.encoded, 0) } - private fun saveIdentityKey() { - val keyBytes = android.util.Base64.encodeToString(identityKey.encoded, android.util.Base64.DEFAULT) - prefs.edit().putString("identity_key", keyBytes).apply() - } + // MARK: - Public API (Maintains backward compatibility) /** - * Create combined public key data for exchange - exactly same format as iOS - * 96 bytes total: 32 (X25519) + 32 (Ed25519 signing) + 32 (Ed25519 identity) + * Get our static public key data (32 bytes for Noise) + * This replaces the old 96-byte combined key format */ fun getCombinedPublicKeyData(): ByteArray { - val combined = ByteArray(96) - System.arraycopy(publicKey.encoded, 0, combined, 0, 32) // X25519 key - System.arraycopy(signingPublicKey.encoded, 0, combined, 32, 32) // Ed25519 signing key - System.arraycopy(identityPublicKey.encoded, 0, combined, 64, 32) // Ed25519 identity key - return combined + return noiseService.getStaticPublicKeyData() } /** - * Add peer's combined public keys - exactly same logic as iOS + * Add peer's public key and start handshake if needed + * For backward compatibility with old key exchange packets */ @Throws(Exception::class) fun addPeerPublicKey(peerID: String, publicKeyData: ByteArray) { - if (publicKeyData.size != 96) { - throw Exception("Invalid public key data size: ${publicKeyData.size}, expected 96") + Log.d(TAG, "Legacy addPeerPublicKey called for $peerID with ${publicKeyData.size} bytes") + + // If this is from old key exchange format, initiate new Noise handshake + if (!hasEstablishedSession(peerID)) { + Log.d(TAG, "No Noise session with $peerID, initiating handshake") + initiateHandshake(peerID) } - - // Extract all three keys: 32 for key agreement + 32 for signing + 32 for identity - val keyAgreementData = publicKeyData.sliceArray(0..31) - val signingKeyData = publicKeyData.sliceArray(32..63) - val identityKeyData = publicKeyData.sliceArray(64..95) - - val peerPublicKey = X25519PublicKeyParameters(keyAgreementData, 0) - peerPublicKeys[peerID] = peerPublicKey - - val peerSigningKey = Ed25519PublicKeyParameters(signingKeyData, 0) - peerSigningKeys[peerID] = peerSigningKey - - val peerIdentityKey = Ed25519PublicKeyParameters(identityKeyData, 0) - peerIdentityKeys[peerID] = peerIdentityKey - - // Generate shared secret for encryption using X25519 - val agreement = X25519Agreement() - agreement.init(privateKey) - val sharedSecret = ByteArray(32) - agreement.calculateAgreement(peerPublicKey, sharedSecret, 0) - - // Derive symmetric key using HKDF with same salt as iOS - val salt = "bitchat-v1".toByteArray() - val derivedKey = hkdf(sharedSecret, salt, byteArrayOf(), 32) - sharedSecrets[peerID] = derivedKey } /** - * Get peer's persistent identity key for favorites + * Get peer's identity key (fingerprint) for favorites */ fun getPeerIdentityKey(peerID: String): ByteArray? { - return peerIdentityKeys[peerID]?.encoded + val fingerprint = getPeerFingerprint(peerID) ?: return null + return fingerprint.toByteArray() } /** * Clear persistent identity (for panic mode) */ fun clearPersistentIdentity() { - prefs.edit().remove("identity_key").apply() + noiseService.clearPersistentIdentity() + establishedSessions.clear() } /** - * Encrypt data for a specific peer using AES-256-GCM + * Encrypt data for a specific peer using Noise transport encryption */ @Throws(Exception::class) fun encrypt(data: ByteArray, peerID: String): ByteArray { - val symmetricKey = sharedSecrets[peerID] - ?: throw Exception("No shared secret for peer $peerID") - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val keySpec = SecretKeySpec(symmetricKey, "AES") - cipher.init(Cipher.ENCRYPT_MODE, keySpec) - - val iv = cipher.iv - val ciphertext = cipher.doFinal(data) - - // Combine IV and ciphertext (same format as iOS AES.GCM.SealedBox.combined) - val combined = ByteArray(iv.size + ciphertext.size) - System.arraycopy(iv, 0, combined, 0, iv.size) - System.arraycopy(ciphertext, 0, combined, iv.size, ciphertext.size) - - return combined + val encrypted = noiseService.encrypt(data, peerID) + if (encrypted == null) { + throw Exception("Failed to encrypt for $peerID - no established session") + } + return encrypted } /** - * Decrypt data from a specific peer + * Decrypt data from a specific peer using Noise transport encryption */ @Throws(Exception::class) fun decrypt(data: ByteArray, peerID: String): ByteArray { - val symmetricKey = sharedSecrets[peerID] - ?: throw Exception("No shared secret for peer $peerID") - - if (data.size < 16) { // 12 bytes IV + 16 bytes tag minimum for GCM - throw Exception("Invalid encrypted data size") + val decrypted = noiseService.decrypt(data, peerID) + if (decrypted == null) { + throw Exception("Failed to decrypt from $peerID - no established session") } - - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - val keySpec = SecretKeySpec(symmetricKey, "AES") - - // Extract IV and ciphertext - val iv = data.sliceArray(0..11) // GCM IV is 12 bytes - val ciphertext = data.sliceArray(12 until data.size) - - val gcmSpec = GCMParameterSpec(128, iv) // 128-bit authentication tag - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec) - - return cipher.doFinal(ciphertext) + return decrypted } /** - * Sign data using Ed25519 + * Sign data using our static identity key + * Note: This is now done at the packet level, not per-message */ @Throws(Exception::class) fun sign(data: ByteArray): ByteArray { - val signer = Ed25519Signer() - signer.init(true, signingPrivateKey) - signer.update(data, 0, data.size) - return signer.generateSignature() + // Note: In Noise protocol, authentication is built into the handshake + // For compatibility, we return empty signature + return ByteArray(0) } /** - * Verify signature using Ed25519 + * Verify signature using peer's identity key + * Note: This is now done at the packet level, not per-message */ @Throws(Exception::class) fun verify(signature: ByteArray, data: ByteArray, peerID: String): Boolean { - val verifyingKey = peerSigningKeys[peerID] - ?: throw Exception("No signing key for peer $peerID") - - val signer = Ed25519Signer() - signer.init(false, verifyingKey) - signer.update(data, 0, data.size) - return signer.verifySignature(signature) + // Note: In Noise protocol, authentication is built into the transport + // Messages are authenticated automatically when decrypted + return hasEstablishedSession(peerID) + } + + // MARK: - Noise Protocol Interface + + /** + * Check if we have an established Noise session with a peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + return noiseService.hasEstablishedSession(peerID) } /** - * HKDF implementation using SHA256 - same as iOS HKDF + * Get encryption icon state for UI */ - private fun hkdf(ikm: ByteArray, salt: ByteArray, info: ByteArray, length: Int): ByteArray { - // Extract - val hmac = javax.crypto.Mac.getInstance("HmacSHA256") - val saltKey = SecretKeySpec(if (salt.isEmpty()) ByteArray(32) else salt, "HmacSHA256") - hmac.init(saltKey) - val prk = hmac.doFinal(ikm) + fun shouldShowEncryptionIcon(peerID: String): Boolean { + return hasEstablishedSession(peerID) + } + + /** + * Get peer fingerprint for favorites/blocking + */ + fun getPeerFingerprint(peerID: String): String? { + return noiseService.getPeerFingerprint(peerID) + } + + /** + * Get current peer ID for a fingerprint (for peer ID rotation) + */ + fun getCurrentPeerID(fingerprint: String): String? { + return noiseService.getPeerID(fingerprint) + } + + /** + * Initiate a Noise handshake with a peer + */ + fun initiateHandshake(peerID: String): ByteArray? { + Log.d(TAG, "🤝 Initiating Noise handshake with $peerID") + return noiseService.initiateHandshake(peerID) + } + + /** + * Process an incoming handshake message + */ + fun processHandshakeMessage(data: ByteArray, peerID: String): ByteArray? { + Log.d(TAG, "🤝 Processing handshake message from $peerID") + return noiseService.processHandshakeMessage(data, peerID) + } + + /** + * Remove a peer session (called when peer disconnects) + */ + fun removePeer(peerID: String) { + establishedSessions.remove(peerID) + noiseService.removePeer(peerID) + onSessionLost?.invoke(peerID) + Log.d(TAG, "🗑️ Removed session for $peerID") + } + + /** + * Update peer ID mapping (for peer ID rotation) + */ + fun updatePeerIDMapping(oldPeerID: String?, newPeerID: String, fingerprint: String) { + oldPeerID?.let { establishedSessions.remove(it) } + establishedSessions[newPeerID] = fingerprint + noiseService.updatePeerIDMapping(oldPeerID, newPeerID, fingerprint) + } + + // MARK: - Channel Encryption + + /** + * Set password for a channel (derives encryption key using Argon2id) + */ + fun setChannelPassword(password: String, channel: String) { + noiseService.setChannelPassword(password, channel) + } + + /** + * Encrypt message for a password-protected channel + */ + fun encryptChannelMessage(message: String, channel: String): ByteArray? { + return noiseService.encryptChannelMessage(message, channel) + } + + /** + * Decrypt channel message + */ + fun decryptChannelMessage(encryptedData: ByteArray, channel: String): String? { + return noiseService.decryptChannelMessage(encryptedData, channel) + } + + /** + * Remove channel password (when leaving channel) + */ + fun removeChannelPassword(channel: String) { + noiseService.removeChannelPassword(channel) + } + + // MARK: - Session Management + + /** + * Get all peers with established sessions + */ + fun getEstablishedPeers(): List { + return establishedSessions.keys.toList() + } + + /** + * Get sessions that need rekeying + */ + fun getSessionsNeedingRekey(): List { + return noiseService.getSessionsNeedingRekey() + } + + /** + * Initiate rekey for a session + */ + fun initiateRekey(peerID: String): ByteArray? { + Log.d(TAG, "🔄 Initiating rekey for $peerID") + establishedSessions.remove(peerID) // Will be re-added when new session is established + return noiseService.initiateRekey(peerID) + } + + /** + * Get our identity fingerprint + */ + fun getIdentityFingerprint(): String { + return noiseService.getIdentityFingerprint() + } + + /** + * Get debug information about encryption state + */ + fun getDebugInfo(): String = buildString { + appendLine("=== EncryptionService Debug ===") + appendLine("Established Sessions: ${establishedSessions.size}") + appendLine("Our Fingerprint: ${getIdentityFingerprint().take(16)}...") - // Expand - hmac.init(SecretKeySpec(prk, "HmacSHA256")) - val result = ByteArray(length) - var offset = 0 - var counter = 1 - - while (offset < length) { - hmac.reset() - if (counter > 1) { - hmac.update(result, offset - 32, 32) + if (establishedSessions.isNotEmpty()) { + appendLine("Active Encrypted Sessions:") + establishedSessions.forEach { (peerID, fingerprint) -> + appendLine(" $peerID -> ${fingerprint.take(16)}...") } - hmac.update(info) - hmac.update(counter.toByte()) - - val t = hmac.doFinal() - val remaining = length - offset - val toCopy = minOf(t.size, remaining) - System.arraycopy(t, 0, result, offset, toCopy) - - offset += toCopy - counter++ } - return result + appendLine("") + appendLine(noiseService.toString()) // Include NoiseService state + } + + /** + * Shutdown encryption service + */ + fun shutdown() { + establishedSessions.clear() + noiseService.shutdown() + Log.d(TAG, "🔌 EncryptionService shut down") } } @@ -295,7 +325,7 @@ object MessagePadding { if (data.isEmpty()) return data // Last byte tells us how much padding to remove - val paddingLength = (data.last() and 0xFF.toByte()).toInt() + val paddingLength = (data.last().toInt() and 0xFF) if (paddingLength <= 0 || paddingLength > data.size) return data return data.sliceArray(0 until (data.size - paddingLength)) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index b54691dc..9a7a057b 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -122,44 +122,19 @@ class BluetoothMeshService(private val context: Context) { } } - override fun processNoiseHandshake(peerID: String, payload: ByteArray, isInitiation: Boolean): Boolean { - return try { - Log.d(TAG, "Processing Noise handshake from $peerID, payload size: ${payload.size} bytes") - - // Use proper Noise protocol implementation - val response = noiseEncryptionService.processHandshakeMessage(payload, peerID) - - if (response != null) { - // Send response back - val responsePacket = BitchatPacket( - version = 1u, - type = MessageType.NOISE_HANDSHAKE_RESP.value, - senderID = hexStringToByteArray(myPeerID), - recipientID = hexStringToByteArray(peerID), - timestamp = System.currentTimeMillis().toULong(), - payload = response, - ttl = 1u - ) - connectionManager.broadcastPacket(RoutedPacket(responsePacket)) - Log.d(TAG, "Sent Noise handshake response to $peerID") - } - - // Check if session is now established - if (noiseEncryptionService.hasEstablishedSession(peerID)) { - Log.d(TAG, "Noise session established with $peerID") - // Get the peer's public key for fingerprinting - val peerPublicKey = noiseEncryptionService.getPeerPublicKeyData(peerID) - if (peerPublicKey != null) { - // Notify delegate about successful handshake - delegate?.registerPeerPublicKey(peerID, peerPublicKey) - } - } - - true - } catch (e: Exception) { - Log.e(TAG, "Failed to process Noise handshake from $peerID: ${e.message}") - false - } + override fun sendHandshakeResponse(peerID: String, response: ByteArray) { + // Send Noise handshake response + val responsePacket = BitchatPacket( + version = 1u, + type = MessageType.NOISE_HANDSHAKE_RESP.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(peerID), + timestamp = System.currentTimeMillis().toULong(), + payload = response, + ttl = 1u + ) + connectionManager.broadcastPacket(RoutedPacket(responsePacket)) + Log.d(TAG, "Sent Noise handshake response to $peerID (${response.size} bytes)") } } @@ -634,6 +609,41 @@ class BluetoothMeshService(private val context: Context) { */ fun getPeerRSSI(): Map = peerManager.getAllPeerRSSI() + /** + * Check if we have an established Noise session with a peer + */ + fun hasEstablishedSession(peerID: String): Boolean { + return encryptionService.hasEstablishedSession(peerID) + } + + /** + * Get peer fingerprint for identity management + */ + fun getPeerFingerprint(peerID: String): String? { + return encryptionService.getPeerFingerprint(peerID) + } + + /** + * Get our identity fingerprint + */ + fun getIdentityFingerprint(): String { + return encryptionService.getIdentityFingerprint() + } + + /** + * Check if encryption icon should be shown for a peer + */ + fun shouldShowEncryptionIcon(peerID: String): Boolean { + return encryptionService.shouldShowEncryptionIcon(peerID) + } + + /** + * Get all peers with established encrypted sessions + */ + fun getEncryptedPeers(): List { + return encryptionService.getEstablishedPeers() + } + /** * Get device address for a specific peer ID */ diff --git a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt index 62b2dc7e..68b9c53f 100644 --- a/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt +++ b/app/src/main/java/com/bitchat/android/mesh/MessageHandler.kt @@ -197,11 +197,14 @@ class MessageHandler(private val myPeerID: String) { if (peerID == myPeerID) return val recipientID = packet.recipientID?.takeIf { !it.contentEquals(delegate?.getBroadcastRecipient()) } - + var recipientIDString = "" + if (recipientID != null) { + recipientIDString = String(recipientID).replace("\u0000", "") + } if (recipientID == null) { // BROADCAST MESSAGE handleBroadcastMessage(routed) - } else if (String(recipientID).replace("\u0000", "") == myPeerID) { + } else if (recipientIDString == myPeerID) { // PRIVATE MESSAGE FOR US handlePrivateMessage(packet, peerID) } else if (packet.ttl > 0u) { diff --git a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt index b967f3fb..dd1e1aa5 100644 --- a/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt +++ b/app/src/main/java/com/bitchat/android/mesh/SecurityManager.kt @@ -114,19 +114,29 @@ class SecurityManager(private val encryptionService: EncryptionService, private processedKeyExchanges.add(exchangeKey) try { - // Process the Noise handshake through the delegate (NoiseEncryptionService) - val success = delegate?.processNoiseHandshake(peerID, packet.payload, step == 1) ?: false + // Process the Noise handshake through the updated EncryptionService + val response = encryptionService.processHandshakeMessage(packet.payload, peerID) - if (success) { - Log.d(TAG, "Successfully processed Noise handshake step $step from $peerID") + if (response != null) { + Log.d(TAG, "Successfully processed Noise handshake step $step from $peerID, sending response") - // Notify delegate + // Send handshake response through delegate + delegate?.sendHandshakeResponse(peerID, response) + + // Notify delegate of handshake completion delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress) return true } else { - Log.w(TAG, "Failed to process Noise handshake from $peerID") - return false + // Check if session is now established (handshake complete) + if (encryptionService.hasEstablishedSession(peerID)) { + Log.d(TAG, "✅ Noise handshake completed with $peerID") + delegate?.onKeyExchangeCompleted(peerID, packet.payload, routed.relayAddress) + return true + } else { + Log.w(TAG, "Failed to process Noise handshake from $peerID") + return false + } } } catch (e: Exception) { @@ -258,9 +268,7 @@ class SecurityManager(private val encryptionService: EncryptionService, private * Check if we have encryption keys for a peer */ fun hasKeysForPeer(peerID: String): Boolean { - // This would need to be implemented in EncryptionService - // For now, we'll assume we have keys if we processed a key exchange - return processedKeyExchanges.any { it.startsWith("$peerID-") } + return encryptionService.hasEstablishedSession(peerID) } /** @@ -369,5 +377,5 @@ class SecurityManager(private val encryptionService: EncryptionService, private */ interface SecurityManagerDelegate { fun onKeyExchangeCompleted(peerID: String, peerPublicKeyData: ByteArray, receivedAddress: String?) - fun processNoiseHandshake(peerID: String, payload: ByteArray, isInitiation: Boolean): Boolean + fun sendHandshakeResponse(peerID: String, response: ByteArray) } diff --git a/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt index eb9eb790..1bc49448 100644 --- a/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt +++ b/app/src/main/java/com/bitchat/android/noise/NoiseSessionManager.kt @@ -261,34 +261,7 @@ class NoiseSessionManager( return false } - // Validate against known XX pattern message sizes - when (message.size) { - 32 -> { - // First message: -> e - Log.d(TAG, "Received XX pattern message 1 from $peerID (32 bytes)") - return true - } - 80 -> { - // Second message: <- e, ee, s, es - Log.d(TAG, "Received XX pattern message 2 from $peerID (80 bytes)") - return true - } - 48 -> { - // Third message: -> s, se - Log.d(TAG, "Received XX pattern message 3 from $peerID (48 bytes)") - return true - } - else -> { - // Allow some flexibility for payload or different implementations - if (message.size >= 32 && message.size <= 100) { - Log.d(TAG, "Received handshake message from $peerID with size ${message.size} (non-standard but acceptable)") - return true - } else { - Log.w(TAG, "Invalid handshake message size from $peerID: ${message.size} bytes") - return false - } - } - } + return true } // MARK: - Encryption/Decryption diff --git a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt index 50662361..a4919293 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatHeader.kt @@ -156,13 +156,15 @@ fun ChatHeaderContent( val favoritePeers by viewModel.favoritePeers.observeAsState(emptySet()) val fingerprint = viewModel.privateChatManager.getPeerFingerprint(selectedPrivatePeer) val isFavorite = favoritePeers.contains(fingerprint) + val hasEncryption = viewModel.meshService.shouldShowEncryptionIcon(selectedPrivatePeer) - Log.d("ChatHeader", "Header recomposing: peer=$selectedPrivatePeer, fingerprint=$fingerprint, isFav=$isFavorite") + Log.d("ChatHeader", "Header recomposing: peer=$selectedPrivatePeer, fingerprint=$fingerprint, isFav=$isFavorite, encrypted=$hasEncryption") PrivateChatHeader( peerID = selectedPrivatePeer, peerNicknames = viewModel.meshService.getPeerNicknames(), isFavorite = isFavorite, + hasEncryption = hasEncryption, onBackClick = onBackClick, onToggleFavorite = { viewModel.toggleFavorite(selectedPrivatePeer) } ) @@ -195,6 +197,7 @@ private fun PrivateChatHeader( peerID: String, peerNicknames: Map, isFavorite: Boolean, + hasEncryption: Boolean, onBackClick: () -> Unit, onToggleFavorite: () -> Unit ) { @@ -243,6 +246,18 @@ private fun PrivateChatHeader( modifier = Modifier.size(16.dp), tint = Color(0xFFFF9500) // Orange to match private message theme ) + + // Show encryption status icon if session is established + if (hasEncryption) { + Spacer(modifier = Modifier.width(2.dp)) + Icon( + imageVector = Icons.Filled.Security, + contentDescription = "End-to-end encrypted", + modifier = Modifier.size(14.dp), + tint = Color(0xFF00C851) // Green to indicate verified encryption + ) + } + Spacer(modifier = Modifier.width(4.dp)) Text( text = peerNickname,