From 8ec8a55f108463ee39ad78677de5e85dc34ee676 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Oct 2021 13:54:51 +0200 Subject: [PATCH] Add ConsumerOptions.setIgnoreMDCErrors() This method can be used to make PGPainless ignore certain MDC related errors or mishabits. Use of this options is discouraged, but may come in handy in some situations. Fixes #190 --- .../ConsumerOptions.java | 36 +++ .../DecryptionStream.java | 1 - .../DecryptionStreamFactory.java | 9 +- .../IntegrityProtectedInputStream.java | 9 +- .../ModificationDetectionTests.java | 258 ++++++++++++++---- 5 files changed, 254 insertions(+), 59 deletions(-) rename pgpainless-core/src/main/java/org/pgpainless/{util => decryption_verification}/IntegrityProtectedInputStream.java (86%) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java index 9ddecba0..e958d3b5 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java @@ -43,6 +43,8 @@ import org.pgpainless.util.Passphrase; */ public class ConsumerOptions { + private boolean ignoreMDCErrors = false; + private Date verifyNotBefore = null; private Date verifyNotAfter = new Date(); @@ -264,4 +266,38 @@ public class ConsumerOptions { public @Nonnull Set getDetachedSignatures() { return Collections.unmodifiableSet(detachedSignatures); } + + /** + * By default, PGPainless will require encrypted messages to make use of SEIP data packets. + * Those are Symmetrically Encrypted Integrity Protected Data packets. + * Symmetrically Encrypted Data Packets without integrity protection are rejected by default. + * Furthermore, PGPainless will throw an exception if verification of the MDC error detection code of the SEIP packet + * fails. + * + * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an attack or data corruption. + * + * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data without integrity protection. + * If the flag
ignoreMDCErrors
is set to true, PGPainless will + * + * + * It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC + * + * @see Sym. Encrypted Integrity Protected Data Packet + * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. + * @return options + */ + @Deprecated + public ConsumerOptions setIgnoreMDCErrors(boolean ignoreMDCErrors) { + this.ignoreMDCErrors = ignoreMDCErrors; + return this; + } + + boolean isIgnoreMDCErrors() { + return ignoreMDCErrors; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java index cdee6e77..d7cc1013 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStream.java @@ -20,7 +20,6 @@ import java.io.InputStream; import javax.annotation.Nonnull; import org.bouncycastle.util.io.Streams; -import org.pgpainless.util.IntegrityProtectedInputStream; /** * Decryption Stream that handles updating and verification of detached signatures, diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index 746ecb32..359408cb 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -67,7 +67,6 @@ import org.pgpainless.signature.DetachedSignature; import org.pgpainless.signature.OnePassSignatureCheck; import org.pgpainless.signature.SignatureUtils; import org.pgpainless.util.CRCingArmoredInputStreamWrapper; -import org.pgpainless.util.IntegrityProtectedInputStream; import org.pgpainless.util.Passphrase; import org.pgpainless.util.Tuple; import org.slf4j.Logger; @@ -286,8 +285,8 @@ public final class DecryptionStreamFactory { // Sort PKESK and SKESK packets while (encryptedDataIterator.hasNext()) { PGPEncryptedData encryptedData = encryptedDataIterator.next(); - // TODO: Maybe just skip non-integrity-protected packages? - if (!encryptedData.isIntegrityProtected()) { + + if (!encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) { throw new MessageNotIntegrityProtectedException(); } @@ -314,7 +313,7 @@ public final class DecryptionStreamFactory { throwIfAlgorithmIsRejected(symmetricKeyAlgorithm); resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); - integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData); + integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options); return integrityProtectedEncryptedInputStream; } catch (PGPException e) { @@ -461,7 +460,7 @@ public final class DecryptionStreamFactory { throwIfAlgorithmIsRejected(symmetricKeyAlgorithm); resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); - integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey); + integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options); return integrityProtectedEncryptedInputStream; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/IntegrityProtectedInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java similarity index 86% rename from pgpainless-core/src/main/java/org/pgpainless/util/IntegrityProtectedInputStream.java rename to pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java index ee6d3c45..41695ff3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/IntegrityProtectedInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/IntegrityProtectedInputStream.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.pgpainless.util; +package org.pgpainless.decryption_verification; import java.io.IOException; import java.io.InputStream; - import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPEncryptedData; @@ -28,10 +27,12 @@ public class IntegrityProtectedInputStream extends InputStream { private final InputStream inputStream; private final PGPEncryptedData encryptedData; + private final ConsumerOptions options; - public IntegrityProtectedInputStream(InputStream inputStream, PGPEncryptedData encryptedData) { + public IntegrityProtectedInputStream(InputStream inputStream, PGPEncryptedData encryptedData, ConsumerOptions options) { this.inputStream = inputStream; this.encryptedData = encryptedData; + this.options = options; } @Override @@ -46,7 +47,7 @@ public class IntegrityProtectedInputStream extends InputStream { @Override public void close() throws IOException { - if (encryptedData.isIntegrityProtected()) { + if (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) { try { if (!encryptedData.verify()) { throw new ModificationDetectionException(); diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java index cf116cc2..dcd29467 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java @@ -124,6 +124,96 @@ public class ModificationDetectionTests { "=miES\n" + "-----END PGP PRIVATE KEY BLOCK-----\n"; + private static final String MESSAGE_TAMPERED_CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQv+NgaEl1h8ZLRD0YiGFqVyO4G0slWQotmgPuovBU8YpNPd\n" + + "A/sROQAOpkxR0mSzhUpMcgkpwi1r7dC3HGQCf+mitGwe+JFXCTx7N/4t1U321BKj\n" + + "c7k7Zu9pDHpPzWi52T6yL30dyR7XkU85P2BrZoOc7B6GuZF07f4qIG1c3+YzBWuw\n" + + "cXyqAmLx/zHUkX72Ga6vzUX/ud08gGYeWthLim7jLG9JzNr+BGnOb93+bKTwe7GX\n" + + "dxNkBP7NTtGaFBM9epvsBiMSBIUHxuJDFgN4KSzpTP3+tcgrpTAaNWalJPzzphqD\n" + + "Iu7ZrBDARQb/8FIymHt0QITZXu3ml8WopiIDbC42JOt16aMy5VDbcP/LlVZn/DB7\n" + + "xr3waDZoxIMhNDcnu8R0w25pbf9T6Lt90a8/UIewCVjciUF82TK6KWLJkTJhmRK3\n" + + "QJ/XgkMhDhS94Rj+l+FIRxJF/oyTtRFoeBHOB8V5neqXmOgGf3aX5UIJu3pf+GH/\n" + + "n+PEoyEw7S7gQxF4VV4S0kwBZlc6xwHfvO+NPf7W0rAtUxMdOl3LBLiILDxF+cq2\n" + + "eKG76gkewCp4EhTfK+iDr4KlObXDzADm1cXRlqFhtoQZrHV0poZVw2kIwSlF\n" + + "=3G7i\n" + + "-----END PGP MESSAGE-----\n"; + + private static final String MESSAGE_MISSING_MDC = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQwAnTmchA6ve/aF7cPEnyJSb9Ot61LSIMrU3+RaEdA90qn4\n" + + "iC+yA7rH+nBX4t9nYSLI4EbQibSfzgxj0Bon1sAwfUfU88UMHypnL1HYsZRoiiLe\n" + + "crRr/9Vot2X1firhSu6kwqPZw5eIbvPPhHojZxWo7Plv7lDsXdtgRXc544jKA+Cx\n" + + "4Rt9D0WG7sWDifHUaitNHC4klZbvO29qmaND1F+RNUpO6H1j63UCPvHqSEvfV+kT\n" + + "vQXtOqk34SLo8SOfpni8Dy1wUePIbuaXyqe5uwSprWoAAmRZOjskv6z28pj9jVs3\n" + + "dWRkWca5Mmm3VQZlmxcNeFyTAgSth0GNalwWSVNcPK9W/VaDX8ecw7xYU04cpbQr\n" + + "a4JF9oc33bhgn4ZDdcvcP8/QUQP+TyN4vGjp1k9+AgkIsJjLanqHE29chsh7ZcVF\n" + + "GDjq3DppEo/Hh647rYRqXpxLfJB6fsDyYLmqNKsBcgtBqE9DtiXQ16GuGFrePxd2\n" + + "nRKcSWQbisEa1LHr8G4d0jYBMjIoPiEhw4sgEt1ZCiQPO1HXqaK7VN3PhPOqjyjf\n" + + "Rt6lN5kVA3+Dd2DRov9NQ83TQPJdg7I=\n" + + "=pgX9\n" + + "-----END PGP MESSAGE-----\n"; + + private static final String MESSAGE_TAMPERED_MDC = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQv+NgaEl1h8ZLRD0YiGFqVyO4G0slWQotmgPuovBU8YpNPd\n" + + "A/sROQAOpkxR0mSzhUpMcgkpwi1r7dC3HGQCf+mitGwe+JFXCTx7N/4t1U321BKj\n" + + "c7k7Zu9pDHpPzWi52T6yL30dyR7XkU85P2BrZoOc7B6GuZF07f4qIG1c3+YzBWuw\n" + + "cXyqAmLx/zHUkX72Ga6vzUX/ud08gGYeWthLim7jLG9JzNr+BGnOb93+bKTwe7GX\n" + + "dxNkBP7NTtGaFBM9epvsBiMSBIUHxuJDFgN4KSzpTP3+tcgrpTAaNWalJPzzphqD\n" + + "Iu7ZrBDARQb/8FIymHt0QITZXu3ml8WopiIDbC42JOt16aMy5VDbcP/LlVZn/DB7\n" + + "xr3waDZoxIMhNDcnu8R0w25pbf9T6Lt90a8/UIewCVjciUF82TK6KWLJkTJhmRK3\n" + + "QJ/XgkMhDhS94Rj+l+FIRxJF/oyTtRFoeBHOB8V5neqXmOgGf3aX5UIJu3pf+GH/\n" + + "n+PEoyEw7S7gQxF4VV4S0kwBZlc6xwHfvO+NPf7W0rAtUxMdOl3LBLiILDxF+cq2\n" + + "eKG76gkewCp4EhTfK+iDr4KlObXDzB/m1cXRlqFhtoQZrHV0poZVw2kIwSkA\n" + + "=Ishh\n" + + "-----END PGP MESSAGE-----\n"; + + private static final String MESSAGE_TRUNCATED_MDC = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQv+NgaEl1h8ZLRD0YiGFqVyO4G0slWQotmgPuovBU8YpNPd\n" + + "A/sROQAOpkxR0mSzhUpMcgkpwi1r7dC3HGQCf+mitGwe+JFXCTx7N/4t1U321BKj\n" + + "c7k7Zu9pDHpPzWi52T6yL30dyR7XkU85P2BrZoOc7B6GuZF07f4qIG1c3+YzBWuw\n" + + "cXyqAmLx/zHUkX72Ga6vzUX/ud08gGYeWthLim7jLG9JzNr+BGnOb93+bKTwe7GX\n" + + "dxNkBP7NTtGaFBM9epvsBiMSBIUHxuJDFgN4KSzpTP3+tcgrpTAaNWalJPzzphqD\n" + + "Iu7ZrBDARQb/8FIymHt0QITZXu3ml8WopiIDbC42JOt16aMy5VDbcP/LlVZn/DB7\n" + + "xr3waDZoxIMhNDcnu8R0w25pbf9T6Lt90a8/UIewCVjciUF82TK6KWLJkTJhmRK3\n" + + "QJ/XgkMhDhS94Rj+l+FIRxJF/oyTtRFoeBHOB8V5neqXmOgGf3aX5UIJu3pf+GH/\n" + + "n+PEoyEw7S7gQxF4VV4S0ksBZlc6xwHfvO+NPf7W0rAtUxMdOl3LBLiILDxF+cq2\n" + + "eKG76gkewCp4EhTfK+iDr4KlObXDzB/m1cXRlqFhtoQZrHV0poZVw2kIwSk=\n" + + "=FDIu\n" + + "-----END PGP MESSAGE-----\n"; + + private static final String MESSAGE_MDC_WITH_BAD_CTB = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQv+NgaEl1h8ZLRD0YiGFqVyO4G0slWQotmgPuovBU8YpNPd\n" + + "A/sROQAOpkxR0mSzhUpMcgkpwi1r7dC3HGQCf+mitGwe+JFXCTx7N/4t1U321BKj\n" + + "c7k7Zu9pDHpPzWi52T6yL30dyR7XkU85P2BrZoOc7B6GuZF07f4qIG1c3+YzBWuw\n" + + "cXyqAmLx/zHUkX72Ga6vzUX/ud08gGYeWthLim7jLG9JzNr+BGnOb93+bKTwe7GX\n" + + "dxNkBP7NTtGaFBM9epvsBiMSBIUHxuJDFgN4KSzpTP3+tcgrpTAaNWalJPzzphqD\n" + + "Iu7ZrBDARQb/8FIymHt0QITZXu3ml8WopiIDbC42JOt16aMy5VDbcP/LlVZn/DB7\n" + + "xr3waDZoxIMhNDcnu8R0w25pbf9T6Lt90a8/UIewCVjciUF82TK6KWLJkTJhmRK3\n" + + "QJ/XgkMhDhS94Rj+l+FIRxJF/oyTtRFoeBHOB8V5neqXmOgGf3aX5UIJu3pf+GH/\n" + + "n+PEoyEw7S7gQxF4VV4S0kwBZlc6xwHfvO+NPf7W0rAtUxMdOl3LBLiILDxF+cq2\n" + + "eKG76gkewCp4EhTfK+iDr4KlObXDzB/n1cXRlqFhtoQZrHV0poZVw2kIwSlF\n" + + "=nOqY\n" + + "-----END PGP MESSAGE-----\n"; + + private static final String MESSAGE_MDC_WITH_BAD_LENGTH = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wcDMA3wvqk35PDeyAQv+NgaEl1h8ZLRD0YiGFqVyO4G0slWQotmgPuovBU8YpNPd\n" + + "A/sROQAOpkxR0mSzhUpMcgkpwi1r7dC3HGQCf+mitGwe+JFXCTx7N/4t1U321BKj\n" + + "c7k7Zu9pDHpPzWi52T6yL30dyR7XkU85P2BrZoOc7B6GuZF07f4qIG1c3+YzBWuw\n" + + "cXyqAmLx/zHUkX72Ga6vzUX/ud08gGYeWthLim7jLG9JzNr+BGnOb93+bKTwe7GX\n" + + "dxNkBP7NTtGaFBM9epvsBiMSBIUHxuJDFgN4KSzpTP3+tcgrpTAaNWalJPzzphqD\n" + + "Iu7ZrBDARQb/8FIymHt0QITZXu3ml8WopiIDbC42JOt16aMy5VDbcP/LlVZn/DB7\n" + + "xr3waDZoxIMhNDcnu8R0w25pbf9T6Lt90a8/UIewCVjciUF82TK6KWLJkTJhmRK3\n" + + "QJ/XgkMhDhS94Rj+l+FIRxJF/oyTtRFoeBHOB8V5neqXmOgGf3aX5UIJu3pf+GH/\n" + + "n+PEoyEw7S7gQxF4VV4S0kwBZlc6xwHfvO+NPf7W0rAtUxMdOl3LBLiILDxF+cq2\n" + + "eKG76gkewCp4EhTfK+iDr4KlObXDzB/m1MXRlqFhtoQZrHV0poZVw2kIwSlF\n" + + "=Ak00\n" + + "-----END PGP MESSAGE-----\n"; + /** * Messages containing a missing MDC shall fail to decrypt. * @param implementationFactory implementation factory @@ -132,26 +222,12 @@ public class ModificationDetectionTests { */ @ParameterizedTest @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") - public void testMissingMDC(ImplementationFactory implementationFactory) throws IOException, PGPException { + public void testMissingMDCThrowsByDefault(ImplementationFactory implementationFactory) throws IOException, PGPException { ImplementationFactory.setFactoryImplementation(implementationFactory); - String message = "-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "wcDMA3wvqk35PDeyAQwAnTmchA6ve/aF7cPEnyJSb9Ot61LSIMrU3+RaEdA90qn4\n" + - "iC+yA7rH+nBX4t9nYSLI4EbQibSfzgxj0Bon1sAwfUfU88UMHypnL1HYsZRoiiLe\n" + - "crRr/9Vot2X1firhSu6kwqPZw5eIbvPPhHojZxWo7Plv7lDsXdtgRXc544jKA+Cx\n" + - "4Rt9D0WG7sWDifHUaitNHC4klZbvO29qmaND1F+RNUpO6H1j63UCPvHqSEvfV+kT\n" + - "vQXtOqk34SLo8SOfpni8Dy1wUePIbuaXyqe5uwSprWoAAmRZOjskv6z28pj9jVs3\n" + - "dWRkWca5Mmm3VQZlmxcNeFyTAgSth0GNalwWSVNcPK9W/VaDX8ecw7xYU04cpbQr\n" + - "a4JF9oc33bhgn4ZDdcvcP8/QUQP+TyN4vGjp1k9+AgkIsJjLanqHE29chsh7ZcVF\n" + - "GDjq3DppEo/Hh647rYRqXpxLfJB6fsDyYLmqNKsBcgtBqE9DtiXQ16GuGFrePxd2\n" + - "nRKcSWQbisEa1LHr8G4d0jYBMjIoPiEhw4sgEt1ZCiQPO1HXqaK7VN3PhPOqjyjf\n" + - "Rt6lN5kVA3+Dd2DRov9NQ83TQPJdg7I=\n" + - "=pgX9\n" + - "-----END PGP MESSAGE-----\n"; PGPSecretKeyRingCollection secretKeyRings = getDecryptionKey(); - InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); + InputStream in = new ByteArrayInputStream(MESSAGE_MISSING_MDC.getBytes(StandardCharsets.UTF_8)); DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(in) .withOptions(new ConsumerOptions() @@ -167,24 +243,9 @@ public class ModificationDetectionTests { @ParameterizedTest @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") - public void tamperedCiphertextTest(ImplementationFactory implementationFactory) throws IOException, PGPException { + public void testTamperedCiphertextThrows(ImplementationFactory implementationFactory) throws IOException, PGPException { ImplementationFactory.setFactoryImplementation(implementationFactory); - String message = "-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "wcDMA3wvqk35PDeyAQwAnTmchA6ve/aF7cPEnyJSb9Ot61LSIMrU3+RaEdA90qn4\n" + - "iC+yA7rH+nBX4t9nYSLI4EbQibSfzgxj0Bon1sAwfUfU88UMHypnL1HYsZRoiiLe\n" + - "crRr/9Vot2X1firhSu6kwqPZw5eIbvPPhHojZxWo7Plv7lDsXdtgRXc544jKA+Cx\n" + - "4Rt9D0WG7sWDifHUaitNHC4klZbvO29qmaND1F+RNUpO6H1j63UCPvHqSEvfV+kT\n" + - "vQXtOqk34SLo8SOfpni8Dy1wUePIbuaXyqe5uwSprWoAAmRZOjskv6z28pj9jVs3\n" + - "dWRkWca5Mmm3VQZlmxcNeFyTAgSth0GNalwWSVNcPK9W/VaDX8ecw7xYU04cpbQr\n" + - "a4JF9oc33bhgn4ZDdcvcP8/QUQP+TyN4vGjp1k9+AgkIsJjLanqHE29chsh7ZcVF\n" + - "GDjq3DppEo/Hh647rYRqXpxLfJB6fsDyYLmqNKsBcgtBqE9DtiXQ16GuGFrePxd2\n" + - "nRKcSWQbisEa1LHr8G4d0kwBMjIoPiEhw4sgEt1ZCiQPO1HXqaK7VN3PhPOqjyjf\n" + - "Rt6lN5kVA3+Dd2DRov9NQ83TQPJdgwCD5cXqlEliiMR4G0gWh8QZ4oAp541H\n" + - "=wx2s\n" + - "-----END PGP MESSAGE-----\n"; - - ByteArrayInputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_CIPHERTEXT.getBytes(StandardCharsets.UTF_8)); DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(in) .withOptions(new ConsumerOptions() @@ -198,24 +259,26 @@ public class ModificationDetectionTests { @ParameterizedTest @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") - public void tamperedMDCTest(ImplementationFactory implementationFactory) throws IOException, PGPException { + public void testIgnoreTamperedCiphertext(ImplementationFactory implementationFactory) throws IOException, PGPException { ImplementationFactory.setFactoryImplementation(implementationFactory); - String message = "-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "wcDMA3wvqk35PDeyAQwAnTmchA6ve/aF7cPEnyJSb9Ot61LSIMrU3+RaEdA90qn4\n" + - "iC+yA7rH+nBX4t9nYSLI4EbQibSfzgxj0Bon1sAwfUfU88UMHypnL1HYsZRoiiLe\n" + - "crRr/9Vot2X1firhSu6kwqPZw5eIbvPPhHojZxWo7Plv7lDsXdtgRXc544jKA+Cx\n" + - "4Rt9D0WG7sWDifHUaitNHC4klZbvO29qmaND1F+RNUpO6H1j63UCPvHqSEvfV+kT\n" + - "vQXtOqk34SLo8SOfpni8Dy1wUePIbuaXyqe5uwSprWoAAmRZOjskv6z28pj9jVs3\n" + - "dWRkWca5Mmm3VQZlmxcNeFyTAgSth0GNalwWSVNcPK9W/VaDX8ecw7xYU04cpbQr\n" + - "a4JF9oc33bhgn4ZDdcvcP8/QUQP+TyN4vGjp1k9+AgkIsJjLanqHE29chsh7ZcVF\n" + - "GDjq3DppEo/Hh647rYRqXpxLfJB6fsDyYLmqNKsBcgtBqE9DtiXQ16GuGFrePxd2\n" + - "nRKcSWQbisEa1LHr8G4d0kwBMjIoPiEhw4sgEt1ZCiQPO1HXqaK7VN3PhPOqjyjf\n" + - "Rt6lN5kVA3+Dd2DRov9NQ83TQPJdg7KD5cXqlEliiMR4G0gWh8QZ4oAp540A\n" + - "=ucHU\n" + - "-----END PGP MESSAGE-----\n"; + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_CIPHERTEXT.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + .setIgnoreMDCErrors(true) + ); - ByteArrayInputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testTamperedMDCThrowsByDefault(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_MDC.getBytes(StandardCharsets.UTF_8)); DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(in) .withOptions(new ConsumerOptions() @@ -227,6 +290,103 @@ public class ModificationDetectionTests { assertThrows(ModificationDetectionException.class, decryptionStream::close); } + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testIgnoreTamperedMDC(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_MDC.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + .setIgnoreMDCErrors(true) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testTruncatedMDCThrows(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TRUNCATED_MDC.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + assertThrows(EOFException.class, () -> Streams.pipeAll(decryptionStream, out)); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testMDCWithBadCTBThrows(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_MDC_WITH_BAD_CTB.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + assertThrows(ModificationDetectionException.class, decryptionStream::close); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testIgnoreMDCWithBadCTB(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_MDC_WITH_BAD_CTB.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + .setIgnoreMDCErrors(true) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testMDCWithBadLengthThrows(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_MDC_WITH_BAD_LENGTH.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + assertThrows(ModificationDetectionException.class, decryptionStream::close); + } + + @ParameterizedTest + @MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") + public void testIgnoreMDCWithBadLength(ImplementationFactory implementationFactory) throws IOException, PGPException { + ImplementationFactory.setFactoryImplementation(implementationFactory); + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_MDC_WITH_BAD_LENGTH.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(in) + .withOptions(new ConsumerOptions() + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys()) + .setIgnoreMDCErrors(true) + ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + } + @Test public void decryptMessageWithSEDPacket() throws IOException, PGPException { Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests");