mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 11:28:22 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user