Merge pull request #375 from greenart7c3/claude/fix-invalid-key-exception-cWPcF

Handle AndroidKeyStore failures and display warning to users
This commit is contained in:
greenart7c3
2026-04-06 08:23:41 -03:00
committed by GitHub
4 changed files with 44 additions and 6 deletions
@@ -140,6 +140,9 @@ class Amber :
val isOnOfflineState = mutableStateOf(false)
private val isStartingApp = MutableStateFlow(false)
/** npubs whose AndroidKeyStore key failed to decrypt (device KeyMint bug). */
val keystoreFailedAccounts = MutableStateFlow<List<String>>(emptyList())
@Volatile var intentionalDisconnectTime = 0L
val isStartingAppState = isStartingApp
val notificationCache = LruCache<String, Long>(10)
@@ -23,6 +23,8 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.cache.LargeCache
import java.io.File
import java.security.InvalidKeyException
import java.security.KeyStoreException
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
@@ -201,7 +203,7 @@ object LocalPreferences {
val torMode = if (contains(SettingsKeys.TOR_MODE.key)) {
try {
TorMode.valueOf(getString(SettingsKeys.TOR_MODE.key, TorMode.DISABLED.name)!!)
} catch (e: IllegalArgumentException) {
} catch (_: IllegalArgumentException) {
TorMode.DISABLED
}
} else if (getBoolean(SettingsKeys.USE_PROXY.key, false)) {
@@ -482,11 +484,21 @@ object LocalPreferences {
if (!containsAccount(context, npub)) {
return null
}
val privKey = DataStoreAccess.getEncryptedKey(
context,
npub,
DataStoreAccess.NOSTR_PRIVKEY,
)
val privKey = try {
DataStoreAccess.getEncryptedKey(
context,
npub,
DataStoreAccess.NOSTR_PRIVKEY,
)
} catch (e: InvalidKeyException) {
Log.e(Amber.TAG, "AndroidKeyStore key for $npub is broken (device KeyMint may not support key upgrade). Account skipped.", e)
Amber.instance.keystoreFailedAccounts.value = (Amber.instance.keystoreFailedAccounts.value + npub).distinct()
return null
} catch (e: KeyStoreException) {
Log.e(Amber.TAG, "KeyStore operation failed for $npub. Account skipped.", e)
Amber.instance.keystoreFailedAccounts.value = (Amber.instance.keystoreFailedAccounts.value + npub).distinct()
return null
}
sharedPrefs(context, npub).apply {
val pubKey = getString(PrefKeys.NOSTR_PUBKEY.key, null) ?: return null
val name = getString(PrefKeys.ACCOUNT_NAME.key, "") ?: ""
@@ -24,6 +24,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -32,6 +34,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.MainViewModel
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
@@ -56,6 +59,8 @@ fun AccountScreen(
val accountState by accountStateViewModel.accountContent.collectAsState()
val context = LocalContext.current
KeystoreFailureWarning()
Column {
Crossfade(
targetState = accountState,
@@ -119,6 +124,22 @@ fun AccountScreen(
}
}
@Composable
private fun KeystoreFailureWarning() {
val failedAccounts by Amber.instance.keystoreFailedAccounts.collectAsState()
var dismissed by remember { mutableStateOf(false) }
if (failedAccounts.isNotEmpty() && !dismissed) {
val accountList = failedAccounts.joinToString("\n") { "• ${it.take(12)}…" }
InformationDialog(
title = stringResource(R.string.keystore_error_title),
textContent = stringResource(R.string.keystore_error_message, accountList),
) {
dismissed = true
}
}
}
@Composable
private fun DisplayErrorMessages() {
val openDialogMsg = ToastManager.toasts.collectAsState(null)
+2
View File
@@ -693,4 +693,6 @@
<string name="auth_whitelist_description">When the whitelist is non-empty, relay client authentication (NIP-42) will only be allowed for relays in the list. Auth requests from other relays will be rejected automatically.</string>
<string name="add_relay_to_whitelist">Add relay to whitelist</string>
<string name="relay_not_in_auth_whitelist">Relay is not in the auth whitelist</string>
<string name="keystore_error_title">Account Inaccessible</string>
<string name="keystore_error_message">One or more accounts could not be loaded because your device\'s secure storage (KeyStore) returned an error:\n\n%1$s\n\nThis is a known firmware bug on some devices where the system cannot upgrade stored encryption keys.\n\nWhat you can do:\n• If you have your nsec (private key) backed up, log out of the affected account and log back in — this will create a new working key.\n• Check for a system firmware update from your device manufacturer, which may fix this.\n• Do not uninstall Amber; keeping the app installed preserves the possibility of recovery if a firmware update is released.</string>
</resources>