1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-26 22:32:07 +01:00

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
This commit is contained in:
Paul Schaub 2021-10-01 13:54:51 +02:00
parent dd77d6be74
commit 8ec8a55f10
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
5 changed files with 254 additions and 59 deletions

View file

@ -43,6 +43,8 @@ import org.pgpainless.util.Passphrase;
*/ */
public class ConsumerOptions { public class ConsumerOptions {
private boolean ignoreMDCErrors = false;
private Date verifyNotBefore = null; private Date verifyNotBefore = null;
private Date verifyNotAfter = new Date(); private Date verifyNotAfter = new Date();
@ -264,4 +266,38 @@ public class ConsumerOptions {
public @Nonnull Set<PGPSignature> getDetachedSignatures() { public @Nonnull Set<PGPSignature> getDetachedSignatures() {
return Collections.unmodifiableSet(detachedSignatures); 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 <pre>ignoreMDCErrors</pre> is set to true, PGPainless will
* <ul>
* <li>not throw exceptions for SEIP packets with tampered ciphertext</li>
* <li>not throw exceptions for SEIP packets with tampered MDC</li>
* <li>not throw exceptions for MDCs with bad CTB</li>
* <li>not throw exceptions for MDCs with bad length</li>
* </ul>
*
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.13">Sym. Encrypted Integrity Protected Data Packet</a>
* @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;
}
} }

View file

@ -20,7 +20,6 @@ import java.io.InputStream;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.pgpainless.util.IntegrityProtectedInputStream;
/** /**
* Decryption Stream that handles updating and verification of detached signatures, * Decryption Stream that handles updating and verification of detached signatures,

View file

@ -67,7 +67,6 @@ import org.pgpainless.signature.DetachedSignature;
import org.pgpainless.signature.OnePassSignatureCheck; import org.pgpainless.signature.OnePassSignatureCheck;
import org.pgpainless.signature.SignatureUtils; import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.util.CRCingArmoredInputStreamWrapper; import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
import org.pgpainless.util.IntegrityProtectedInputStream;
import org.pgpainless.util.Passphrase; import org.pgpainless.util.Passphrase;
import org.pgpainless.util.Tuple; import org.pgpainless.util.Tuple;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -286,8 +285,8 @@ public final class DecryptionStreamFactory {
// Sort PKESK and SKESK packets // Sort PKESK and SKESK packets
while (encryptedDataIterator.hasNext()) { while (encryptedDataIterator.hasNext()) {
PGPEncryptedData encryptedData = encryptedDataIterator.next(); PGPEncryptedData encryptedData = encryptedDataIterator.next();
// TODO: Maybe just skip non-integrity-protected packages?
if (!encryptedData.isIntegrityProtected()) { if (!encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
throw new MessageNotIntegrityProtectedException(); throw new MessageNotIntegrityProtectedException();
} }
@ -314,7 +313,7 @@ public final class DecryptionStreamFactory {
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm); throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData); integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
return integrityProtectedEncryptedInputStream; return integrityProtectedEncryptedInputStream;
} catch (PGPException e) { } catch (PGPException e) {
@ -461,7 +460,7 @@ public final class DecryptionStreamFactory {
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm); throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm); resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey); integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
return integrityProtectedEncryptedInputStream; return integrityProtectedEncryptedInputStream;
} }

View file

@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.pgpainless.util; package org.pgpainless.decryption_verification;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedData;
@ -28,10 +27,12 @@ public class IntegrityProtectedInputStream extends InputStream {
private final InputStream inputStream; private final InputStream inputStream;
private final PGPEncryptedData encryptedData; 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.inputStream = inputStream;
this.encryptedData = encryptedData; this.encryptedData = encryptedData;
this.options = options;
} }
@Override @Override
@ -46,7 +47,7 @@ public class IntegrityProtectedInputStream extends InputStream {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (encryptedData.isIntegrityProtected()) { if (encryptedData.isIntegrityProtected() && !options.isIgnoreMDCErrors()) {
try { try {
if (!encryptedData.verify()) { if (!encryptedData.verify()) {
throw new ModificationDetectionException(); throw new ModificationDetectionException();

View file

@ -124,6 +124,96 @@ public class ModificationDetectionTests {
"=miES\n" + "=miES\n" +
"-----END PGP PRIVATE KEY BLOCK-----\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. * Messages containing a missing MDC shall fail to decrypt.
* @param implementationFactory implementation factory * @param implementationFactory implementation factory
@ -132,26 +222,12 @@ public class ModificationDetectionTests {
*/ */
@ParameterizedTest @ParameterizedTest
@MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") @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); 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(); 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() DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(in) .onInputStream(in)
.withOptions(new ConsumerOptions() .withOptions(new ConsumerOptions()
@ -167,24 +243,9 @@ public class ModificationDetectionTests {
@ParameterizedTest @ParameterizedTest
@MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") @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); ImplementationFactory.setFactoryImplementation(implementationFactory);
String message = "-----BEGIN PGP MESSAGE-----\n" + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_CIPHERTEXT.getBytes(StandardCharsets.UTF_8));
"\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));
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(in) .onInputStream(in)
.withOptions(new ConsumerOptions() .withOptions(new ConsumerOptions()
@ -198,24 +259,26 @@ public class ModificationDetectionTests {
@ParameterizedTest @ParameterizedTest
@MethodSource("org.pgpainless.util.TestImplementationFactoryProvider#provideImplementationFactories") @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); ImplementationFactory.setFactoryImplementation(implementationFactory);
String message = "-----BEGIN PGP MESSAGE-----\n" + ByteArrayInputStream in = new ByteArrayInputStream(MESSAGE_TAMPERED_CIPHERTEXT.getBytes(StandardCharsets.UTF_8));
"\n" + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
"wcDMA3wvqk35PDeyAQwAnTmchA6ve/aF7cPEnyJSb9Ot61LSIMrU3+RaEdA90qn4\n" + .onInputStream(in)
"iC+yA7rH+nBX4t9nYSLI4EbQibSfzgxj0Bon1sAwfUfU88UMHypnL1HYsZRoiiLe\n" + .withOptions(new ConsumerOptions()
"crRr/9Vot2X1firhSu6kwqPZw5eIbvPPhHojZxWo7Plv7lDsXdtgRXc544jKA+Cx\n" + .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys())
"4Rt9D0WG7sWDifHUaitNHC4klZbvO29qmaND1F+RNUpO6H1j63UCPvHqSEvfV+kT\n" + .setIgnoreMDCErrors(true)
"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.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() DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(in) .onInputStream(in)
.withOptions(new ConsumerOptions() .withOptions(new ConsumerOptions()
@ -227,6 +290,103 @@ public class ModificationDetectionTests {
assertThrows(ModificationDetectionException.class, decryptionStream::close); 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 @Test
public void decryptMessageWithSEDPacket() throws IOException, PGPException { public void decryptMessageWithSEDPacket() throws IOException, PGPException {
Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests"); Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests");