From 5d83ab03e22903faf93c5bab194ff153857786e2 Mon Sep 17 00:00:00 2001 From: Ivan Pizhenko Date: Fri, 28 Jan 2022 22:37:23 +0200 Subject: [PATCH 1/8] wip issue 1669 --- .../email/security/pgp/PgpKeyTest.kt | 13 ++++ .../keys/issue-1669-corrupted.private.gpg-key | 64 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 FlowCrypt/src/test/resources/PgpKeyTest/keys/issue-1669-corrupted.private.gpg-key diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index 59ca219096..d79f2535c4 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -10,9 +10,11 @@ import com.flowcrypt.email.security.model.Algo import com.flowcrypt.email.security.model.KeyId import com.flowcrypt.email.security.model.PgpKeyDetails import com.flowcrypt.email.util.TestUtil +import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows import org.junit.Test import org.pgpainless.PGPainless import org.pgpainless.key.OpenPgpV4Fingerprint @@ -114,4 +116,15 @@ class PgpKeyTest { val actual = PgpKey.parseKeys(keyText) assertEquals(1, actual.getAllKeys().size) } + + @Test + fun testReadInvalidPrivateKey() { + assertThrows(PGPException::class.java) { + val encryptedKeyText = loadResourceAsString("keys/issue-1669-malformed.private.gpg-key") + val decryptedKeyText = PgpKey.decryptKey(encryptedKeyText, Passphrase.fromPassword("123")) + // should not reach here + val actual = PgpKey.parseKeys(decryptedKeyText) + assertEquals(1, actual.getAllKeys().size) + } + } } diff --git a/FlowCrypt/src/test/resources/PgpKeyTest/keys/issue-1669-corrupted.private.gpg-key b/FlowCrypt/src/test/resources/PgpKeyTest/keys/issue-1669-corrupted.private.gpg-key new file mode 100644 index 0000000000..9ffc7a33d2 --- /dev/null +++ b/FlowCrypt/src/test/resources/PgpKeyTest/keys/issue-1669-corrupted.private.gpg-key @@ -0,0 +1,64 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Corrupted encrypted RSA private key +Comment: Passphrase is 123 + +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 +=kFcl +-----END PGP PRIVATE KEY BLOCK----- From babacdca3f0292ea78272b2ab052958ef639139b Mon Sep 17 00:00:00 2001 From: Ivan Pizhenko Date: Fri, 28 Jan 2022 22:39:01 +0200 Subject: [PATCH 2/8] wip --- .../test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index d79f2535c4..d60a8ddbdf 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -120,7 +120,7 @@ class PgpKeyTest { @Test fun testReadInvalidPrivateKey() { assertThrows(PGPException::class.java) { - val encryptedKeyText = loadResourceAsString("keys/issue-1669-malformed.private.gpg-key") + val encryptedKeyText = loadResourceAsString("keys/issue-1669-corrupted.private.gpg-key") val decryptedKeyText = PgpKey.decryptKey(encryptedKeyText, Passphrase.fromPassword("123")) // should not reach here val actual = PgpKey.parseKeys(decryptedKeyText) From af674d579d9efbaf09ad47c6cef7852e0565b883 Mon Sep 17 00:00:00 2001 From: Ivan Pizhenko Date: Fri, 28 Jan 2022 22:45:20 +0200 Subject: [PATCH 3/8] wip --- .../test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index d60a8ddbdf..6372673c0e 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -118,7 +118,7 @@ class PgpKeyTest { } @Test - fun testReadInvalidPrivateKey() { + fun testReadCorruptedPrivateKey() { assertThrows(PGPException::class.java) { val encryptedKeyText = loadResourceAsString("keys/issue-1669-corrupted.private.gpg-key") val decryptedKeyText = PgpKey.decryptKey(encryptedKeyText, Passphrase.fromPassword("123")) From caebc9856fac58ff4a9643a71f51ecac552c3388 Mon Sep 17 00:00:00 2001 From: Ivan Pizhenko Date: Wed, 2 Feb 2022 21:16:48 +0200 Subject: [PATCH 4/8] update test --- .../email/security/pgp/PgpKeyTest.kt | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index 6372673c0e..c1d10ab9b8 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -10,17 +10,20 @@ import com.flowcrypt.email.security.model.Algo import com.flowcrypt.email.security.model.KeyId import com.flowcrypt.email.security.model.PgpKeyDetails import com.flowcrypt.email.util.TestUtil -import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing import org.junit.Assert.assertEquals import org.junit.Assert.assertThrows import org.junit.Test import org.pgpainless.PGPainless +import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.key.OpenPgpV4Fingerprint +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.util.Passphrase import java.nio.charset.Charset import java.nio.charset.StandardCharsets +import kotlin.jvm.Throws class PgpKeyTest { companion object { @@ -119,12 +122,23 @@ class PgpKeyTest { @Test fun testReadCorruptedPrivateKey() { - assertThrows(PGPException::class.java) { - val encryptedKeyText = loadResourceAsString("keys/issue-1669-corrupted.private.gpg-key") - val decryptedKeyText = PgpKey.decryptKey(encryptedKeyText, Passphrase.fromPassword("123")) - // should not reach here - val actual = PgpKey.parseKeys(decryptedKeyText) - assertEquals(1, actual.getAllKeys().size) + val encryptedKeyText = loadResourceAsString("keys/issue-1669-corrupted.private.gpg-key") + val passphrase = Passphrase.fromPassword("123") + assertThrows(KeyIntegrityException::class.java) { + checkSecretKeyIntegrity(encryptedKeyText, passphrase) + } + } + + @Throws(KeyIntegrityException::class) + private fun checkSecretKeyIntegrity(armored: String, passphrase: Passphrase) { + val collection = PGPainless.readKeyRing().keyRingCollection(armored, false) + val secretKeyRings = collection.pgpSecretKeyRingCollection + if (secretKeyRings.size() == 0) throw KeyIntegrityException() + for (keyRing in secretKeyRings) { + val protector = SecretKeyRingProtector.unlockEachKeyWith(passphrase, keyRing) + for (key in keyRing.secretKeys) { + UnlockSecretKey.unlockSecretKey(key, protector) + } } } } From 0bd3f8cc108b1be45433490811eca9b5301035aa Mon Sep 17 00:00:00 2001 From: Ivan Pizhenko Date: Wed, 2 Feb 2022 21:34:31 +0200 Subject: [PATCH 5/8] wip --- .../com/flowcrypt/email/security/pgp/PgpKey.kt | 17 +++++++++++++++++ .../flowcrypt/email/security/pgp/PgpKeyTest.kt | 17 +---------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt index 6eb98ab9a8..2412282b69 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt @@ -19,9 +19,13 @@ import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.PGPainless +import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.key.collection.PGPKeyRingCollection +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.util.Passphrase import java.io.InputStream +import kotlin.jvm.Throws @Suppress("unused") object PgpKey { @@ -111,6 +115,19 @@ object PgpKey { .done() } + @Throws(KeyIntegrityException::class) + fun checkSecretKeyIntegrity(armored: String, passphrase: Passphrase) { + val collection = PGPainless.readKeyRing().keyRingCollection(armored, false) + val secretKeyRings = collection.pgpSecretKeyRingCollection + if (secretKeyRings.size() == 0) throw KeyIntegrityException() + for (keyRing in secretKeyRings) { + val protector = SecretKeyRingProtector.unlockEachKeyWith(passphrase, keyRing) + for (key in keyRing.secretKeys) { + UnlockSecretKey.unlockSecretKey(key, protector) + } + } + } + suspend fun parsePrivateKeys(source: String): List = withContext(Dispatchers.IO) { parseKeys(source, false).pgpKeyRingCollection .pgpSecretKeyRingCollection.map { it.toPgpKeyDetails() } diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index c1d10ab9b8..5ce31fa0f1 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -18,12 +18,9 @@ import org.junit.Test import org.pgpainless.PGPainless import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.key.OpenPgpV4Fingerprint -import org.pgpainless.key.protection.SecretKeyRingProtector -import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.util.Passphrase import java.nio.charset.Charset import java.nio.charset.StandardCharsets -import kotlin.jvm.Throws class PgpKeyTest { companion object { @@ -125,20 +122,8 @@ class PgpKeyTest { val encryptedKeyText = loadResourceAsString("keys/issue-1669-corrupted.private.gpg-key") val passphrase = Passphrase.fromPassword("123") assertThrows(KeyIntegrityException::class.java) { - checkSecretKeyIntegrity(encryptedKeyText, passphrase) + PgpKey.checkSecretKeyIntegrity(encryptedKeyText, passphrase) } } - @Throws(KeyIntegrityException::class) - private fun checkSecretKeyIntegrity(armored: String, passphrase: Passphrase) { - val collection = PGPainless.readKeyRing().keyRingCollection(armored, false) - val secretKeyRings = collection.pgpSecretKeyRingCollection - if (secretKeyRings.size() == 0) throw KeyIntegrityException() - for (keyRing in secretKeyRings) { - val protector = SecretKeyRingProtector.unlockEachKeyWith(passphrase, keyRing) - for (key in keyRing.secretKeys) { - UnlockSecretKey.unlockSecretKey(key, protector) - } - } - } } From 2a8da8d5e41a42ffe48b2db3a684c6aaa368637f Mon Sep 17 00:00:00 2001 From: Denys Bondarenko Date: Thu, 3 Feb 2022 10:11:22 +0200 Subject: [PATCH 6/8] Modified UI to prevent importing invalid keys.| #1669 --- .../viewmodel/CheckPrivateKeysViewModel.kt | 20 +++++++++++++------ .../flowcrypt/email/security/pgp/PgpKey.kt | 10 +++++++++- .../email/ui/activity/CheckKeysActivity.kt | 14 ++++++++++++- FlowCrypt/src/main/res/values-ru/strings.xml | 1 + FlowCrypt/src/main/res/values/strings.xml | 1 + .../email/security/pgp/PgpKeyTest.kt | 1 - 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/CheckPrivateKeysViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/CheckPrivateKeysViewModel.kt index 058ac81091..859e7aa282 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/CheckPrivateKeysViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/CheckPrivateKeysViewModel.kt @@ -20,6 +20,7 @@ import com.flowcrypt.email.util.exception.WrongPassPhraseException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.util.Passphrase /** @@ -52,7 +53,7 @@ class CheckPrivateKeysViewModel(application: Application) : BaseAndroidViewModel val resultList = mutableListOf() for (keyDetails in keys) { val copy = keyDetails.copy() - var e: Exception? = null + var e: Throwable? = null if (copy.isPrivate) { if (copy.passphraseType == null) { e = IllegalArgumentException(context.getString(R.string.passphrase_type_undefined)) @@ -66,15 +67,22 @@ class CheckPrivateKeysViewModel(application: Application) : BaseAndroidViewModel } else { try { PgpKey.decryptKey(prvKey, passphrase) + //https://github.com/FlowCrypt/flowcrypt-android/issues/1669 + PgpKey.checkSecretKeyIntegrity(prvKey, passphrase) copy.tempPassphrase = passphrase.chars - } catch (ex: Exception) { + } catch (ex: Throwable) { //to prevent leak sensitive info we skip printing stack trace for release builds if (GeneralUtil.isDebugBuild()) { ex.printStackTrace() } - e = WrongPassPhraseException( - message = context.getString(R.string.password_is_incorrect), cause = ex - ) + + e = when (ex) { + is KeyIntegrityException -> ex + + else -> WrongPassPhraseException( + message = context.getString(R.string.password_is_incorrect), cause = ex + ) + } } } } @@ -96,6 +104,6 @@ class CheckPrivateKeysViewModel(application: Application) : BaseAndroidViewModel data class CheckResult( val pgpKeyDetails: PgpKeyDetails, - val passphrase: String, val e: Exception? = null + val passphrase: String, val e: Throwable? = null ) } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt index 2412282b69..8acfe9132f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpKey.kt @@ -17,6 +17,7 @@ import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.PGPainless import org.pgpainless.exception.KeyIntegrityException @@ -25,7 +26,6 @@ import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.key.protection.UnlockSecretKey import org.pgpainless.util.Passphrase import java.io.InputStream -import kotlin.jvm.Throws @Suppress("unused") object PgpKey { @@ -120,6 +120,14 @@ object PgpKey { val collection = PGPainless.readKeyRing().keyRingCollection(armored, false) val secretKeyRings = collection.pgpSecretKeyRingCollection if (secretKeyRings.size() == 0) throw KeyIntegrityException() + checkSecretKeyIntegrity(secretKeyRings, passphrase) + } + + @Throws(KeyIntegrityException::class) + fun checkSecretKeyIntegrity( + secretKeyRings: PGPSecretKeyRingCollection, + passphrase: Passphrase + ) { for (keyRing in secretKeyRings) { val protector = SecretKeyRingProtector.unlockEachKeyWith(passphrase, keyRing) for (key in keyRing.secretKeys) { diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CheckKeysActivity.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CheckKeysActivity.kt index 2a7bc9b866..fc4787f468 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CheckKeysActivity.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/CheckKeysActivity.kt @@ -33,6 +33,7 @@ import com.flowcrypt.email.ui.activity.fragment.dialog.WebViewInfoDialogFragment import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.UIUtil import org.apache.commons.io.IOUtils +import org.pgpainless.exception.KeyIntegrityException import org.pgpainless.util.Passphrase import java.io.IOException import java.nio.charset.StandardCharsets @@ -286,7 +287,18 @@ class CheckKeysActivity : BaseActivity(), View.OnClickListener, } else { if (resultKeys.size == 1) { - showInfoSnackbar(rootView, resultKeys.first().e?.message) + when (resultKeys.first().e) { + is KeyIntegrityException -> { + showInfoDialogFragment( + dialogMsg = getString( + R.string.warning_when_import_invalid_prv_key, + getString(R.string.support_email) + ) + ) + } + + else -> showInfoSnackbar(rootView, resultKeys.first().e?.message) + } } else { showInfoSnackbar(rootView, getString(R.string.password_is_incorrect)) } diff --git a/FlowCrypt/src/main/res/values-ru/strings.xml b/FlowCrypt/src/main/res/values-ru/strings.xml index 7e67edc61b..1f23cf9c3a 100644 --- a/FlowCrypt/src/main/res/values-ru/strings.xml +++ b/FlowCrypt/src/main/res/values-ru/strings.xml @@ -488,4 +488,5 @@ Пожалуйста, не прописывайте пароль в тему сообщения. Передача пароля по электронной почте далает бессмысленным шифрование на основе пароля.\n\nВы можете попросить получателя также установить %1$s, сообщения между пользователями %1$s не нуждаются в пароле. Пожалуйста, не используйте Вашу ключевую фразу в качестве пароля для даного сообщения.\n\nВы должны придумать какой-нибудь другой уникальный пароль, которым Вы можете поделиться с получателем. Синхронизация… + Найден недействительный ключ. Импорт не возможен. Если это случится еще раз, пожалуйста, напишите нам на %1$s diff --git a/FlowCrypt/src/main/res/values/strings.xml b/FlowCrypt/src/main/res/values/strings.xml index ce3b10b9d5..965a5969b3 100644 --- a/FlowCrypt/src/main/res/values/strings.xml +++ b/FlowCrypt/src/main/res/values/strings.xml @@ -574,4 +574,5 @@ Please do not include the password in the email subject. Sharing password over email undermines password based encryption.\n\nYou can ask the recipient to also install %1$s, messages between %1$s users don\'t need a password. Please do not use your private key pass phrase as a password for this message.\n\nYou should come up with some other unique password that you can share with a recipient. Syncing… + Key is invalid, you could not import such a key.\n\nIf this happens again, please write us at %1$s diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt index 5ce31fa0f1..b6d325a462 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpKeyTest.kt @@ -125,5 +125,4 @@ class PgpKeyTest { PgpKey.checkSecretKeyIntegrity(encryptedKeyText, passphrase) } } - } From cce0b268d3fd463706985efbc2d5dc0d3961af95 Mon Sep 17 00:00:00 2001 From: Denys Bondarenko Date: Thu, 3 Feb 2022 10:14:27 +0200 Subject: [PATCH 7/8] Fixed ru strings.| #1669 --- FlowCrypt/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/main/res/values-ru/strings.xml b/FlowCrypt/src/main/res/values-ru/strings.xml index 1f23cf9c3a..7908765ee0 100644 --- a/FlowCrypt/src/main/res/values-ru/strings.xml +++ b/FlowCrypt/src/main/res/values-ru/strings.xml @@ -488,5 +488,5 @@ Пожалуйста, не прописывайте пароль в тему сообщения. Передача пароля по электронной почте далает бессмысленным шифрование на основе пароля.\n\nВы можете попросить получателя также установить %1$s, сообщения между пользователями %1$s не нуждаются в пароле. Пожалуйста, не используйте Вашу ключевую фразу в качестве пароля для даного сообщения.\n\nВы должны придумать какой-нибудь другой уникальный пароль, которым Вы можете поделиться с получателем. Синхронизация… - Найден недействительный ключ. Импорт не возможен. Если это случится еще раз, пожалуйста, напишите нам на %1$s + Найден недействительный ключ. Импорт невозможен.\n\nЕсли это случится еще раз, пожалуйста, напишите нам на %1$s From eff6748c7fa239e5bff1facec6af1157b75a66e6 Mon Sep 17 00:00:00 2001 From: Denys Bondarenko Date: Thu, 3 Feb 2022 10:26:20 +0200 Subject: [PATCH 8/8] Added a test.| #1669 --- .../pgp/keys/issue_1669_corrupted_prv.asc | 64 +++++++++++++++++++ .../CheckKeysActivityTestMultiBackups.kt | 17 +++++ 2 files changed, 81 insertions(+) create mode 100644 FlowCrypt/src/androidTest/assets/pgp/keys/issue_1669_corrupted_prv.asc diff --git a/FlowCrypt/src/androidTest/assets/pgp/keys/issue_1669_corrupted_prv.asc b/FlowCrypt/src/androidTest/assets/pgp/keys/issue_1669_corrupted_prv.asc new file mode 100644 index 0000000000..9ffc7a33d2 --- /dev/null +++ b/FlowCrypt/src/androidTest/assets/pgp/keys/issue_1669_corrupted_prv.asc @@ -0,0 +1,64 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Comment: Corrupted encrypted RSA private key +Comment: Passphrase is 123 + +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 +=kFcl +-----END PGP PRIVATE KEY BLOCK----- diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CheckKeysActivityTestMultiBackups.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CheckKeysActivityTestMultiBackups.kt index 90a85091fb..c12603c923 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CheckKeysActivityTestMultiBackups.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/CheckKeysActivityTestMultiBackups.kt @@ -435,6 +435,23 @@ class CheckKeysActivityTestMultiBackups : BaseTest() { checkKeysTitleAtStart(4, keysPaths, KeyImportDetails.SourceType.FILE) } + /** + * It refers to https://github.com/FlowCrypt/flowcrypt-android/issues/1669 + */ + @Test + fun testImportCorruptedKey() { + val keysPaths = arrayOf("pgp/keys/issue_1669_corrupted_prv.asc") + launchActivity(keysPaths, KeyImportDetails.SourceType.FILE) + typePassword("123") + isDialogWithTextDisplayed( + decorView, + getResString( + R.string.warning_when_import_invalid_prv_key, + getResString(R.string.support_email) + ) + ) + } + private fun launchActivity( keysPaths: Array, sourceType: KeyImportDetails.SourceType = KeyImportDetails.SourceType.EMAIL