Commit 62cc4fa1 authored by Lisa (AI Assistant)'s avatar Lisa (AI Assistant)

Fix device identity persistence across processes

Root cause: SharedPreferences are not shared between processes (Activity vs Service),
causing new keypair generation on every restart, resulting in 'device signature invalid'.

Fix: Use file-based storage (device_identity.json) instead of SharedPreferences.
File is shared across all processes in the same app.
parent dd734735
...@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.receiveAsFlow ...@@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
import okhttp3.* import okhttp3.*
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.File
import java.security.KeyFactory import java.security.KeyFactory
import java.security.KeyPairGenerator import java.security.KeyPairGenerator
import java.security.MessageDigest import java.security.MessageDigest
...@@ -84,18 +85,23 @@ class NodeClient() { ...@@ -84,18 +85,23 @@ class NodeClient() {
} }
private fun generateDeviceIdentity() { private fun generateDeviceIdentity() {
// Use a file instead of SharedPreferences to ensure it works across processes
// SharedPreferences are not shared between processes by default
val identityFile = context?.let { File(it.filesDir, "device_identity.json") }
if (identityFile?.exists() == true) {
try { try {
// Try to load existing keys from SharedPreferences val json = JSONObject(identityFile.readText())
val savedPublicKey = prefs?.getString("device_public_key", null) val savedPublicKey = json.optString("publicKey", null)
val savedPrivateKey = prefs?.getString("device_private_key", null) val savedPrivateKey = json.optString("privateKey", null)
if (savedPublicKey != null && savedPrivateKey != null) { if (savedPublicKey != null && savedPrivateKey != null) {
// Load existing keys
devicePublicKey = savedPublicKey devicePublicKey = savedPublicKey
devicePrivateKey = savedPrivateKey devicePrivateKey = savedPrivateKey
// Reconstruct PrivateKey object from PEM
val keyFactory = KeyFactory.getInstance("RSA") val keyFactory = KeyFactory.getInstance("RSA")
// Load private key
val privateKeyBytes = Base64.getDecoder().decode( val privateKeyBytes = Base64.getDecoder().decode(
savedPrivateKey savedPrivateKey
.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----BEGIN PRIVATE KEY-----", "")
...@@ -105,7 +111,7 @@ class NodeClient() { ...@@ -105,7 +111,7 @@ class NodeClient() {
val privateKeySpec = PKCS8EncodedKeySpec(privateKeyBytes) val privateKeySpec = PKCS8EncodedKeySpec(privateKeyBytes)
devicePrivateKeyObject = keyFactory.generatePrivate(privateKeySpec) devicePrivateKeyObject = keyFactory.generatePrivate(privateKeySpec)
// Derive deviceId from public key // Load public key
val publicKeyBytes = Base64.getDecoder().decode( val publicKeyBytes = Base64.getDecoder().decode(
savedPublicKey savedPublicKey
.replace("-----BEGIN PUBLIC KEY-----", "") .replace("-----BEGIN PUBLIC KEY-----", "")
...@@ -113,13 +119,22 @@ class NodeClient() { ...@@ -113,13 +119,22 @@ class NodeClient() {
.replace("\n", "") .replace("\n", "")
) )
val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(publicKeyBytes)) val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(publicKeyBytes))
// Derive deviceId from public key
val digest = MessageDigest.getInstance("SHA-256") val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey.encoded) val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) } deviceId = hash.joinToString("") { "%02x".format(it) }
Log.d(TAG, "Loaded existing device identity: deviceId=$deviceId") Log.d(TAG, "Loaded existing device identity from file: deviceId=$deviceId")
} else { return
}
} catch (e: Exception) {
Log.e(TAG, "Failed to load device identity from file: ${e.message}")
}
}
// Generate new key pair // Generate new key pair
Log.d(TAG, "Generating new device identity...")
val keyGen = KeyPairGenerator.getInstance("RSA") val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(2048) keyGen.initialize(2048)
val keyPair = keyGen.generateKeyPair() val keyPair = keyGen.generateKeyPair()
...@@ -142,7 +157,20 @@ class NodeClient() { ...@@ -142,7 +157,20 @@ class NodeClient() {
val hash = digest.digest(publicKey.encoded) val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) } deviceId = hash.joinToString("") { "%02x".format(it) }
// Persist keys for future use // Persist to file (works across processes)
try {
val json = JSONObject().apply {
put("publicKey", devicePublicKey)
put("privateKey", devicePrivateKey)
put("deviceId", deviceId)
}
identityFile?.writeText(json.toString())
Log.d(TAG, "Saved device identity to file: deviceId=$deviceId")
} catch (e: Exception) {
Log.e(TAG, "Failed to save device identity to file: ${e.message}")
}
// Legacy: Also save to SharedPreferences for backwards compatibility
prefs?.edit() prefs?.edit()
?.putString("device_public_key", devicePublicKey) ?.putString("device_public_key", devicePublicKey)
?.putString("device_private_key", devicePrivateKey) ?.putString("device_private_key", devicePrivateKey)
...@@ -150,12 +178,6 @@ class NodeClient() { ...@@ -150,12 +178,6 @@ class NodeClient() {
Log.d(TAG, "Generated new device identity: deviceId=$deviceId") Log.d(TAG, "Generated new device identity: deviceId=$deviceId")
} }
} catch (e: Exception) {
Log.e(TAG, "Failed to generate/load device identity: ${e.message}")
// Generate a random device ID as fallback
deviceId = "device-${System.currentTimeMillis()}"
}
}
private fun signData(data: String): String { private fun signData(data: String): String {
return try { return try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment