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");