From 4c0149e9bc7a2c5fe4eb0ac5e0f6eccd91660c63 Mon Sep 17 00:00:00 2001 From: Drew Carlson Date: Mon, 8 Jun 2020 18:59:05 -0700 Subject: [PATCH] DROID-1740: Remove use of device credentials --- .../tools/security/BrdUserManager.kt | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/breadwallet/tools/security/BrdUserManager.kt b/app/src/main/java/com/breadwallet/tools/security/BrdUserManager.kt index d7397a07a1..00c19b0e58 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BrdUserManager.kt +++ b/app/src/main/java/com/breadwallet/tools/security/BrdUserManager.kt @@ -129,6 +129,7 @@ private const val PUT_PHRASE_RC = 400 private const val GET_PHRASE_RC = 401 private const val KEY_ACCOUNT = "account" +private const val KEY_PHRASE = "phrase" private const val KEY_AUTH_KEY = "authKey" private const val KEY_CREATION_TIME = "creationTimeSeconds" private const val KEY_TOKEN = "token" @@ -316,7 +317,9 @@ class CryptoUserManager( .flowOn(Default) override suspend fun getPhrase(): ByteArray? = - executeWithAuth { BRKeyStore.getPhrase(it, GET_PHRASE_RC) } + store?.getBytes(KEY_PHRASE, null) + ?: executeWithAuth { BRKeyStore.getPhrase(it, GET_PHRASE_RC) } + ?.also { bytes -> store?.edit { putBytes(KEY_PHRASE, bytes) } } override fun getAccount(): Account? = store?.getBytes(KEY_ACCOUNT, null)?.run { @@ -447,9 +450,9 @@ class CryptoUserManager( check(getPhrase() == null) { "Phrase already exists." } try { - val storedPhrase = executeWithAuth { - BRKeyStore.putPhrase(phrase, it, PUT_PHRASE_RC) - BRKeyStore.getPhrase(it, GET_PHRASE_RC) + val storedPhrase = checkNotNull(store).run { + edit { putBytes(KEY_PHRASE, phrase) } + getBytes(KEY_PHRASE, null) } ?: return@withLock SetupResult.FailedToPersistPhrase val account = Account.createFromPhrase( @@ -638,38 +641,45 @@ class CryptoUserManager( return context } - private fun isPhraseKeyValid(): Boolean = try { - // Attempt to retrieve the key that protects the paper key and initialize an encryption cipher. - val key = KeyStore.getInstance(ANDROID_KEY_STORE) - .apply { load(null) } - .getKey(BRKeyStore.PHRASE_ALIAS, null) + private fun isPhraseKeyValid(): Boolean { + if (store?.getBytes(KEY_PHRASE, null) != null) { + // BRKeyStore is not required anymore, phrase key is ignored. + return true + } - // If there is no key, then it has not been initialized yet. The key store is still considered valid. - if (key != null) { - val cipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM) - cipher.init(Cipher.ENCRYPT_MODE, key) + return try { + // Attempt to retrieve the key that protects the paper key and initialize an encryption cipher. + val key = KeyStore.getInstance(ANDROID_KEY_STORE) + .apply { load(null) } + .getKey(BRKeyStore.PHRASE_ALIAS, null) + + // If there is no key, then it has not been initialized yet. The key store is still considered valid. + if (key != null) { + val cipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM) + cipher.init(Cipher.ENCRYPT_MODE, key) + } + true + } catch (e: KeyPermanentlyInvalidatedException) { + // If KeyPermanentlyInvalidatedException + // -> with no cause happens, then the password was disabled. See DROID-1019. + // If UnrecoverableKeyException + // -> with cause "Key blob corrupted" happens then the password was disabled & re-enabled. See DROID-1207. + // -> with cause "Key blob corrupted" happens then after DROID-1019 the app was opened again while password is on. + // -> with cause "Key not found" happens then after DROID-1019 the app was opened again while password is off. + // -> with cause "System error" (KeyStoreException) after app wipe on devices that need uninstall to recover. + // Note: These exceptions would happen before a UserNotAuthenticatedException, so we don't need to handle that. + logError("Phrase key permanently invalidated", e) + false + } catch (e: UnrecoverableKeyException) { + logError("Phrase key is unrecoverable", e) + false + } catch (e: GeneralSecurityException) { + logWarning("Phrase key may still be valid.", e) + true + } catch (e: IOException) { + logWarning("Phrase key may still be valid.", e) + true } - true - } catch (e: KeyPermanentlyInvalidatedException) { - // If KeyPermanentlyInvalidatedException - // -> with no cause happens, then the password was disabled. See DROID-1019. - // If UnrecoverableKeyException - // -> with cause "Key blob corrupted" happens then the password was disabled & re-enabled. See DROID-1207. - // -> with cause "Key blob corrupted" happens then after DROID-1019 the app was opened again while password is on. - // -> with cause "Key not found" happens then after DROID-1019 the app was opened again while password is off. - // -> with cause "System error" (KeyStoreException) after app wipe on devices that need uninstall to recover. - // Note: These exceptions would happen before a UserNotAuthenticatedException, so we don't need to handle that. - logError("Phrase key permanently invalidated", e) - false - } catch (e: UnrecoverableKeyException) { - logError("Phrase key is unrecoverable", e) - false - } catch (e: GeneralSecurityException) { - logWarning("Phrase key may still be valid.", e) - true - } catch (e: IOException) { - logWarning("Phrase key may still be valid.", e) - true } private fun requiresUninstall(): Boolean {