Skip to content

Commit

Permalink
(android) Clear key from keystore if seed fallback also fails
Browse files Browse the repository at this point in the history
If there's an issue with the keystore (typically after a system
update that fails to properly upgrade keys), then we should discard
the old key and create a new one to store the seed (provided the
seed is correct).
  • Loading branch information
dpad85 committed Nov 13, 2024
1 parent 5224957 commit eb8729b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ object KeystoreHelper {
else -> throw IllegalArgumentException("unhandled key=$keyName")
}

/**
* Get the encryption cipher for the key. If it fails once, delete the key, and try again. This should only be used by the seed fallback to check if the
* keystore is accessible. This seems to (rarely) happen after an OS update that fails to upgrade the key (raising a `upgrade_keyblob_if_required_with`
* error), and might be related to the secure element option?
*/
fun checkEncryptionCipherOrReset(keyName: String) = when (keyName) {
KEY_NO_AUTH -> {
try {
getEncryptionCipher(keyName)
} catch (e: Exception) {
log.error("could not get encryption cipher: ${e.localizedMessage}")
try {
log.error("deleting key=$keyName from keystore")
keyStore.deleteEntry(keyName)
getEncryptionCipher(keyName)
} catch (e: Exception) {
log.error("cannot delete $keyName entry from keystore: ${e.localizedMessage}")
throw e
}
}
}

else -> {
throw IllegalArgumentException("unhandled key_name=$keyName")
}
}

/** Get encryption Cipher for given key. */
internal fun getEncryptionCipher(keyName: String): Cipher = Cipher.getInstance("$ENC_ALGO/$ENC_BLOCK_MODE/$ENC_PADDING").apply {
init(Cipher.ENCRYPT_MODE, getKeyForName(keyName), parameters)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ private fun StartupSeedFallback(
icon = R.drawable.ic_check_circle
)
}
is StartupDecryptionState.SeedInputFallback.Error.KeyStoreFailure -> {
Text(text = stringResource(id = R.string.startup_fallback_error_keystore_error))
}
}
Spacer(modifier = Modifier.height(100.dp))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import fr.acinq.bitcoin.MnemonicCode
import fr.acinq.bitcoin.byteVector
import fr.acinq.lightning.crypto.LocalKeyManager
import fr.acinq.phoenix.android.security.EncryptedSeed
import fr.acinq.phoenix.android.security.KeystoreHelper
import fr.acinq.phoenix.android.security.SeedManager
import fr.acinq.phoenix.android.services.NodeService
import fr.acinq.phoenix.managers.NodeParamsManager
Expand All @@ -39,23 +40,24 @@ import java.security.KeyStoreException


sealed class StartupDecryptionState {
object Init : StartupDecryptionState()
object DecryptingSeed : StartupDecryptionState()
object DecryptionSuccess : StartupDecryptionState()
data object Init : StartupDecryptionState()
data object DecryptingSeed : StartupDecryptionState()
data object DecryptionSuccess : StartupDecryptionState()
sealed class DecryptionError : StartupDecryptionState() {
data class Other(val cause: Throwable): DecryptionError()
data class KeystoreFailure(val cause: Throwable): DecryptionError()
}
sealed class SeedInputFallback : StartupDecryptionState() {
object Init: SeedInputFallback()
object CheckingSeed: SeedInputFallback()
data object Init: SeedInputFallback()
data object CheckingSeed: SeedInputFallback()
sealed class Success: SeedInputFallback() {
object MatchingData: Success()
object WrittenToDisk: Success()
}
sealed class Error: SeedInputFallback() {
data class Other(val cause: Throwable): Error()
object SeedDoesNotMatch: Error()
data object SeedDoesNotMatch: Error()
data class KeyStoreFailure(val cause: Throwable): Error()
}
}
}
Expand Down Expand Up @@ -103,6 +105,12 @@ class StartupViewModel : ViewModel() {
if (channelsDbFile.exists()) {
decryptionState.value = StartupDecryptionState.SeedInputFallback.Success.MatchingData
val encodedSeed = EncryptedSeed.fromMnemonics(words)
try {
KeystoreHelper.checkEncryptionCipherOrReset(KeystoreHelper.KEY_NO_AUTH)
} catch (e: Exception) {
decryptionState.value = StartupDecryptionState.SeedInputFallback.Error.SeedDoesNotMatch
return@launch
}
val encrypted = EncryptedSeed.V2.NoAuth.encrypt(encodedSeed)
SeedManager.writeSeedToDisk(context, encrypted, overwrite = true)
delay(1000)
Expand Down
3 changes: 2 additions & 1 deletion phoenix-android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@
<string name="startup_fallback_unlock_button">Unlock wallet</string>
<string name="startup_fallback_checking">Checking seed…</string>
<string name="startup_fallback_error_default">An error occurred. Please try again.</string>
<string name="startup_fallback_error_incorrect_seed">This seed does not match your wallet.</string>
<string name="startup_fallback_error_incorrect_seed">This seed does not match your wallet data.</string>
<string name="startup_fallback_error_keystore_error">Failed to perform keystore operations.</string>
<string name="startup_fallback_success_match">Seed matches.\nWriting to disk…</string>
<string name="startup_fallback_success_starting">Starting Phoenix…</string>

Expand Down

0 comments on commit eb8729b

Please sign in to comment.