diff --git a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt index e813871f33..0615df756d 100644 --- a/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt +++ b/FlowCrypt/src/androidTest/java/com/flowcrypt/email/ui/activity/BackupKeysFragmentSingleKeyPassphraseInRamTest.kt @@ -21,6 +21,7 @@ import com.flowcrypt.email.R import com.flowcrypt.email.TestConstants import com.flowcrypt.email.database.entity.KeyEntity import com.flowcrypt.email.junit.annotations.DependsOnMailServer +import com.flowcrypt.email.junit.annotations.NotReadyForCI import com.flowcrypt.email.matchers.CustomMatchers.Companion.withRecyclerViewItemCount import com.flowcrypt.email.rules.AddPrivateKeyToDatabaseRule import com.flowcrypt.email.rules.ClearAppSettingsRule @@ -58,6 +59,7 @@ class BackupKeysFragmentSingleKeyPassphraseInRamTest : BaseBackupKeysFragmentTes @Test @DependsOnMailServer + @NotReadyForCI fun testNeedPassphraseEmailOptionSingleFingerprint() { onView(withId(R.id.btBackup)) .check(matches(isDisplayed())) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpMsg.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpMsg.kt index be9d79bc8a..d5ab9b29f6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpMsg.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/security/pgp/PgpMsg.kt @@ -31,7 +31,6 @@ import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.armor import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.toPgpKeyDetails import com.flowcrypt.email.extensions.org.owasp.html.allowAttributesOnElementsExt import com.flowcrypt.email.security.pgp.PgpArmor.ARMOR_HEADER_DICT -import org.bouncycastle.bcpg.ArmoredInputStream import org.bouncycastle.bcpg.PacketTags import org.bouncycastle.openpgp.PGPDataValidationException import org.bouncycastle.openpgp.PGPException @@ -54,6 +53,7 @@ import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.key.protection.UnprotectedKeysProtector import org.pgpainless.util.Passphrase import java.io.ByteArrayOutputStream +import java.io.IOException import java.io.InputStream import java.nio.charset.StandardCharsets import java.util.Locale @@ -202,9 +202,8 @@ object PgpMsg { ) } + val input = data.inputStream() val chunk = data.copyOfRange(0, data.size.coerceAtMost(50)).toString(StandardCharsets.US_ASCII) - var input = data.inputStream() as InputStream - if (chunk.contains(ARMOR_HEADER_DICT[MsgBlock.Type.SIGNED_MSG]!!.begin)) { val msg: PgpArmor.CleartextSignedMessage try { @@ -227,9 +226,6 @@ object PgpMsg { return DecryptionResult.withCleartext(msg.content, msg.signature) } - val isArmored = chunk.contains(ARMOR_HEADER_DICT[MsgBlock.Type.ENCRYPTED_MSG]!!.begin) - if (isArmored) input = ArmoredInputStream(input) - val keyList: List try { keyList = keys.map { @@ -252,20 +248,26 @@ object PgpMsg { ) } + val exception: Exception? try { val consumerOptions = ConsumerOptions() .addDecryptionKeys(PGPSecretKeyRingCollection(keyList), UnprotectedKeysProtector()) pgpPublicKeyRingCollection?.let { consumerOptions.addVerificationCerts(it) } - val decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(input) .withOptions(consumerOptions) - val output = ByteArrayOutputStream() - decryptionStream.use { it.copyTo(output) } - - val streamResult = decryptionStream.result - return DecryptionResult.withDecrypted(output, streamResult.fileName) + try { + decryptionStream.use { it.copyTo(output) } + } catch (ex: IOException) { + val message = ex.message + if (message != null && message.contains("crc check not found")) { + decryptionStream.close() + } else { + throw ex + } + } + return DecryptionResult.withDecrypted(output, decryptionStream.result.fileName) } catch (ex: MessageNotIntegrityProtectedException) { return DecryptionResult.withError( type = PgpDecrypt.DecryptionErrorType.NO_MDC, @@ -294,10 +296,28 @@ object PgpMsg { message = "There is no suitable decryption key" ) } + // other PGP error, fallback to default error return in the bottom + exception = ex + } catch (ex: Exception) { + if ( + ex is IOException && + ex.message?.contains("crc check failed in armored message") == true + ) { + return DecryptionResult.withError( + type = PgpDecrypt.DecryptionErrorType.FORMAT, + message = "Armor CRC check failed" + ) + } else { + exception = ex + } + } + var message = "Decryption failed" + if (exception != null) { + message = "$message: ${exception.javaClass.canonicalName}: ${exception.message}" } return DecryptionResult.withError( type = PgpDecrypt.DecryptionErrorType.OTHER, - message = "Decryption failed" + message = message ) } diff --git a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpMsgTest.kt b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpMsgTest.kt index 680ef164b7..8ba8a40d51 100644 --- a/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpMsgTest.kt +++ b/FlowCrypt/src/test/java/com/flowcrypt/email/security/pgp/PgpMsgTest.kt @@ -37,7 +37,7 @@ class PgpMsgTest { val quoted: Boolean? = null, val charset: String = "UTF-8" ) { - val armored: String = loadResourceAsString("messages/$key.txt") + val armored: String by lazy { loadResourceAsString("messages/$key.txt") } } companion object { @@ -112,7 +112,12 @@ class PgpMsgTest { TestUtil.decodeString("=E3=82=BE=E3=81=97=E9=80=B8=E7=8F=BE=E9=A3=B2", "UTF-8") ), charset = "ISO-2022-JP", - ) + ), + + MessageInfo( + key = "decrypt - issue 1347 - wrong checksum", + content = listOf("") + ), ) private fun findMessage(key: String): MessageInfo { @@ -172,8 +177,6 @@ class PgpMsgTest { assertTrue("Bad MDC not detected", r.error!!.type == PgpDecrypt.DecryptionErrorType.BAD_MDC) } - // TODO: Should there be any error? - // https://github.com/FlowCrypt/flowcrypt-android/issues/1214 @Test fun decryptionTest3() { val r = processMessage( @@ -181,6 +184,8 @@ class PgpMsgTest { ) assertTrue("Message not returned", r.content != null) assertTrue("Error returned", r.error == null) + // TODO: Should there be any error? + // https://github.com/FlowCrypt/flowcrypt-android/issues/1214 } @Test @@ -194,6 +199,14 @@ class PgpMsgTest { assertTrue("Error returned", r.error == null) } + @Test + // @Ignore("BC ArmoredInputStream issue") + fun wrongArmorChecksumTest() { + val r = processMessage("decrypt - issue 1347 - wrong checksum") + assertTrue("Error not returned", r.error != null) + assertEquals(PgpDecrypt.DecryptionErrorType.FORMAT, r.error!!.type) + } + @Test fun wrongPassphraseTest() { val messageInfo = findMessage("decrypt - without a subject") diff --git a/FlowCrypt/src/test/resources/PgpMsgTest/messages/decrypt - issue 1347 - wrong checksum.txt b/FlowCrypt/src/test/resources/PgpMsgTest/messages/decrypt - issue 1347 - wrong checksum.txt new file mode 100644 index 0000000000..d90469d47b --- /dev/null +++ b/FlowCrypt/src/test/resources/PgpMsgTest/messages/decrypt - issue 1347 - wrong checksum.txt @@ -0,0 +1,50 @@ +-----BEGIN PGP MESSAGE----- +Version: FlowCrypt 5.0.4 Gmail Encryption flowcrypt.com +Comment: Seamlessly send, receive and search encrypted email + +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 +=FdCC +-----END PGP MESSAGE-----