From 057835ad02bf25848b657c8d62e703835bb98062 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 18 Oct 2021 18:08:20 +0300 Subject: [PATCH 1/4] Used PGPainless to identify missed passphrases for keys that fit.| #1473 --- .../email/security/pgp/PgpDecrypt.kt | 51 +++++-------------- .../FixNeedPassphraseIssueDialogFragment.kt | 7 ++- 2 files changed, 18 insertions(+), 40 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt index d01865cb9a..7976310822 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt @@ -10,17 +10,16 @@ import android.os.Parcelable import com.flowcrypt.email.util.exception.DecryptionException import org.bouncycastle.openpgp.PGPDataValidationException import org.bouncycastle.openpgp.PGPException -import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRingCollection import org.pgpainless.PGPainless import org.pgpainless.decryption_verification.ConsumerOptions -import org.pgpainless.decryption_verification.MessageInspector +import org.pgpainless.decryption_verification.MissingKeyPassphraseStrategy import org.pgpainless.decryption_verification.OpenPgpMetadata import org.pgpainless.exception.MessageNotIntegrityProtectedException import org.pgpainless.exception.MissingDecryptionMethodException +import org.pgpainless.exception.MissingPassphraseException import org.pgpainless.exception.ModificationDetectionException import org.pgpainless.exception.WrongPassphraseException -import org.pgpainless.key.OpenPgpV4Fingerprint import org.pgpainless.key.protection.SecretKeyRingProtector import java.io.ByteArrayOutputStream import java.io.IOException @@ -51,6 +50,7 @@ object PgpDecrypt { .withOptions( ConsumerOptions() .addDecryptionKeys(pgpSecretKeyRingCollection, protector) + .setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy.THROW_EXCEPTION) ) decryptionStream.use { it.copyTo(outStream) } return decryptionStream.result @@ -70,16 +70,13 @@ object PgpDecrypt { srcInputStream.use { srcStream -> val destOutputStream = ByteArrayOutputStream() destOutputStream.use { outStream -> - if (srcStream.markSupported()) { - srcStream.mark(0) - } - try { val decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(srcStream) .withOptions( ConsumerOptions() .addDecryptionKeys(pgpSecretKeyRingCollection, protector) + .setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy.THROW_EXCEPTION) .setIgnoreMDCErrors(ignoreMdcErrors) ) @@ -91,38 +88,6 @@ object PgpDecrypt { filename = decryptionStream.result.fileName ) } catch (e: Exception) { - if (e is DecryptionException && e.decryptionErrorType == DecryptionErrorType.NEED_PASSPHRASE) { - if (srcStream.markSupported()) { - srcStream.reset() - val keyIds = MessageInspector.determineEncryptionInfoForMessage(srcInputStream).keyIds - val fingerprints = mutableListOf() - for (id in keyIds) { - var key: PGPKeyRing? = null - try { - key = pgpSecretKeyRingCollection.getSecretKeyRing(id) - protector.getDecryptor(id) - } catch (e: DecryptionException) { - key?.let { - val fingerprint = OpenPgpV4Fingerprint(key) - fingerprints.add(fingerprint.toString()) - } - } - } - - if (fingerprints.isNotEmpty()) { - return DecryptionResult.withError( - processDecryptionException( - DecryptionException( - decryptionErrorType = DecryptionErrorType.NEED_PASSPHRASE, - e = PGPException("flowcrypt: need passphrase"), - fingerprints = fingerprints - ) - ) - ) - } - } - } - return DecryptionResult.withError( processDecryptionException(e) ) @@ -137,6 +102,14 @@ object PgpDecrypt { DecryptionException(DecryptionErrorType.WRONG_PASSPHRASE, e) } + is MissingPassphraseException -> { + DecryptionException( + decryptionErrorType = DecryptionErrorType.NEED_PASSPHRASE, + e = PGPException("flowcrypt: need passphrase"), + fingerprints = e.keyIds.map { it.fingerprint.toString().uppercase() } + ) + } + is MessageNotIntegrityProtectedException -> { DecryptionException(DecryptionErrorType.NO_MDC, e) } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt index f7c01bbbb5..cbd75af6c5 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt @@ -24,6 +24,7 @@ import com.flowcrypt.email.extensions.decrementSafely import com.flowcrypt.email.extensions.gone import com.flowcrypt.email.extensions.incrementSafely import com.flowcrypt.email.extensions.invisible +import com.flowcrypt.email.extensions.kotlin.uppercase import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.toast import com.flowcrypt.email.extensions.visible @@ -144,7 +145,11 @@ class FixNeedPassphraseIssueDialogFragment : BaseDialogFragment() { Result.Status.SUCCESS -> { binding?.pBLoading?.gone() val filteredKeyDetailsList = (it.data ?: emptyList()).filter { pgpKeyDetails -> - fingerprintList.any { element -> element.equals(pgpKeyDetails.fingerprint, true) } + fingerprintList.any { element -> + element.uppercase() in pgpKeyDetails.ids.map { keyId -> + keyId.fingerprint.uppercase() + } + } } if (filteredKeyDetailsList.isEmpty()) { binding?.tVStatusMessage?.text = getString(R.string.error_no_keys) From ca1d9f943666130ad2364749b761056f157d25f3 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Tue, 19 Oct 2021 09:09:28 +0300 Subject: [PATCH 2/4] Fixed missing imports.| #1473 --- .../src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt index 7976310822..d89b21f5f7 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpDecrypt.kt @@ -7,6 +7,7 @@ package com.flowcrypt.email.security.pgp import android.os.Parcel import android.os.Parcelable +import com.flowcrypt.email.extensions.kotlin.uppercase import com.flowcrypt.email.util.exception.DecryptionException import org.bouncycastle.openpgp.PGPDataValidationException import org.bouncycastle.openpgp.PGPException From ac85ed97bdae6624f3fe5bef58f4f6fb7d5388f8 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 21 Oct 2021 14:48:30 +0300 Subject: [PATCH 3/4] Fixed a bug with subkeys after upgrading to PGPainless 0.2.17| #1473 --- .../dialog/FixNeedPassphraseIssueDialogFragment.kt | 9 ++++++++- .../main/res/layout/fragment_fix_empty_passphrase.xml | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt index cbd75af6c5..299bfd8163 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt @@ -124,7 +124,14 @@ class FixNeedPassphraseIssueDialogFragment : BaseDialogFragment() { binding?.eTKeyPassword?.let { val passPhrase = Passphrase.fromPassword(typedText) val keys = keysWithEmptyPassphraseViewModel.keysWithEmptyPassphrasesLiveData - .value?.data?.filter { it.fingerprint in fingerprintList } ?: return@let + .value?.data?.filter { pgpKeyDetails -> + for (fingerprint in pgpKeyDetails.ids.map { id -> id.fingerprint }) { + if (fingerprint in fingerprintList) { + return@filter true + } + } + return@filter false + } ?: return@let checkPrivateKeysViewModel.checkKeys( keys = keys, passphrase = passPhrase diff --git a/FlowCrypt/src/main/res/layout/fragment_fix_empty_passphrase.xml b/FlowCrypt/src/main/res/layout/fragment_fix_empty_passphrase.xml index 9c23d1d685..c4c5bf8599 100644 --- a/FlowCrypt/src/main/res/layout/fragment_fix_empty_passphrase.xml +++ b/FlowCrypt/src/main/res/layout/fragment_fix_empty_passphrase.xml @@ -64,6 +64,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" + android:imeOptions="actionDone" android:inputType="textPassword" /> From a743abca9b521dbb0c0a4b8b03846a7278c95afa Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 21 Oct 2021 15:42:07 +0300 Subject: [PATCH 4/4] Refactored code.| #1473 --- .../FixNeedPassphraseIssueDialogFragment.kt | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt index 299bfd8163..58ae871d4b 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/FixNeedPassphraseIssueDialogFragment.kt @@ -123,17 +123,15 @@ class FixNeedPassphraseIssueDialogFragment : BaseDialogFragment() { } else { binding?.eTKeyPassword?.let { val passPhrase = Passphrase.fromPassword(typedText) - val keys = keysWithEmptyPassphraseViewModel.keysWithEmptyPassphrasesLiveData - .value?.data?.filter { pgpKeyDetails -> - for (fingerprint in pgpKeyDetails.ids.map { id -> id.fingerprint }) { - if (fingerprint in fingerprintList) { - return@filter true - } - } - return@filter false - } ?: return@let + val existedKeys = + keysWithEmptyPassphraseViewModel.keysWithEmptyPassphrasesLiveData.value?.data + val matchingKeys = (existedKeys ?: emptyList()).filter { pgpKeyDetails -> + fingerprintList.any { element -> + element.uppercase() in pgpKeyDetails.ids.map { keyId -> keyId.fingerprint.uppercase() } + } + } checkPrivateKeysViewModel.checkKeys( - keys = keys, + keys = matchingKeys, passphrase = passPhrase ) } @@ -151,14 +149,12 @@ class FixNeedPassphraseIssueDialogFragment : BaseDialogFragment() { Result.Status.SUCCESS -> { binding?.pBLoading?.gone() - val filteredKeyDetailsList = (it.data ?: emptyList()).filter { pgpKeyDetails -> + val matchingKeys = (it.data ?: emptyList()).filter { pgpKeyDetails -> fingerprintList.any { element -> - element.uppercase() in pgpKeyDetails.ids.map { keyId -> - keyId.fingerprint.uppercase() - } + element.uppercase() in pgpKeyDetails.ids.map { keyId -> keyId.fingerprint.uppercase() } } } - if (filteredKeyDetailsList.isEmpty()) { + if (matchingKeys.isEmpty()) { binding?.tVStatusMessage?.text = getString(R.string.error_no_keys) } else { binding?.btnUpdatePassphrase?.visible() @@ -168,19 +164,19 @@ class FixNeedPassphraseIssueDialogFragment : BaseDialogFragment() { LogicType.ALL -> { binding?.tVStatusMessage?.text = resources.getQuantityString( R.plurals.please_provide_passphrase_for_all_following_keys, - filteredKeyDetailsList.size + matchingKeys.size ) } LogicType.AT_LEAST_ONE -> { if (checkPrivateKeysViewModel.checkPrvKeysLiveData.value == null) { binding?.tVStatusMessage?.text = resources.getQuantityString( R.plurals.please_provide_passphrase_for_following_keys, - filteredKeyDetailsList.size + matchingKeys.size ) } } } - prvKeysRecyclerViewAdapter.submitList(filteredKeyDetailsList) + prvKeysRecyclerViewAdapter.submitList(matchingKeys) } baseActivity?.countingIdlingResource?.decrementSafely() }