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
import okhttp3.*
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.MessageDigest
......@@ -84,77 +85,98 @@ class NodeClient() {
}
private fun generateDeviceIdentity() {
try {
// Try to load existing keys from SharedPreferences
val savedPublicKey = prefs?.getString("device_public_key", null)
val savedPrivateKey = prefs?.getString("device_private_key", null)
if (savedPublicKey != null && savedPrivateKey != null) {
// Load existing keys
devicePublicKey = savedPublicKey
devicePrivateKey = savedPrivateKey
// Reconstruct PrivateKey object from PEM
val keyFactory = KeyFactory.getInstance("RSA")
val privateKeyBytes = Base64.getDecoder().decode(
savedPrivateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
)
val privateKeySpec = PKCS8EncodedKeySpec(privateKeyBytes)
devicePrivateKeyObject = keyFactory.generatePrivate(privateKeySpec)
// Derive deviceId from public key
val publicKeyBytes = Base64.getDecoder().decode(
savedPublicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
)
val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(publicKeyBytes))
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) }
Log.d(TAG, "Loaded existing device identity: deviceId=$deviceId")
} else {
// Generate new key pair
val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(2048)
val keyPair = keyGen.generateKeyPair()
val publicKey = keyPair.public
val privateKey = keyPair.private
devicePrivateKeyObject = privateKey
// Convert to PEM format
devicePublicKey = "-----BEGIN PUBLIC KEY-----\n" +
Base64.getEncoder().encodeToString(publicKey.encoded).chunked(64).joinToString("\n") +
"\n-----END PUBLIC KEY-----"
devicePrivateKey = "-----BEGIN PRIVATE KEY-----\n" +
Base64.getEncoder().encodeToString(privateKey.encoded).chunked(64).joinToString("\n") +
"\n-----END PRIVATE KEY-----"
// Generate device ID from public key hash
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) }
// 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 {
val json = JSONObject(identityFile.readText())
val savedPublicKey = json.optString("publicKey", null)
val savedPrivateKey = json.optString("privateKey", null)
// Persist keys for future use
prefs?.edit()
?.putString("device_public_key", devicePublicKey)
?.putString("device_private_key", devicePrivateKey)
?.apply()
Log.d(TAG, "Generated new device identity: deviceId=$deviceId")
if (savedPublicKey != null && savedPrivateKey != null) {
devicePublicKey = savedPublicKey
devicePrivateKey = savedPrivateKey
val keyFactory = KeyFactory.getInstance("RSA")
// Load private key
val privateKeyBytes = Base64.getDecoder().decode(
savedPrivateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\n", "")
)
val privateKeySpec = PKCS8EncodedKeySpec(privateKeyBytes)
devicePrivateKeyObject = keyFactory.generatePrivate(privateKeySpec)
// Load public key
val publicKeyBytes = Base64.getDecoder().decode(
savedPublicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\n", "")
)
val publicKey = keyFactory.generatePublic(X509EncodedKeySpec(publicKeyBytes))
// Derive deviceId from public key
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) }
Log.d(TAG, "Loaded existing device identity from file: deviceId=$deviceId")
return
}
} catch (e: Exception) {
Log.e(TAG, "Failed to load device identity from file: ${e.message}")
}
}
// Generate new key pair
Log.d(TAG, "Generating new device identity...")
val keyGen = KeyPairGenerator.getInstance("RSA")
keyGen.initialize(2048)
val keyPair = keyGen.generateKeyPair()
val publicKey = keyPair.public
val privateKey = keyPair.private
devicePrivateKeyObject = privateKey
// Convert to PEM format
devicePublicKey = "-----BEGIN PUBLIC KEY-----\n" +
Base64.getEncoder().encodeToString(publicKey.encoded).chunked(64).joinToString("\n") +
"\n-----END PUBLIC KEY-----"
devicePrivateKey = "-----BEGIN PRIVATE KEY-----\n" +
Base64.getEncoder().encodeToString(privateKey.encoded).chunked(64).joinToString("\n") +
"\n-----END PRIVATE KEY-----"
// Generate device ID from public key hash
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(publicKey.encoded)
deviceId = hash.joinToString("") { "%02x".format(it) }
// 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 generate/load device identity: ${e.message}")
// Generate a random device ID as fallback
deviceId = "device-${System.currentTimeMillis()}"
Log.e(TAG, "Failed to save device identity to file: ${e.message}")
}
// Legacy: Also save to SharedPreferences for backwards compatibility
prefs?.edit()
?.putString("device_public_key", devicePublicKey)
?.putString("device_private_key", devicePrivateKey)
?.apply()
Log.d(TAG, "Generated new device identity: deviceId=$deviceId")
}
private fun signData(data: String): String {
......
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