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 c23ede3d..7b8d1e70 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 @@ -36,6 +36,7 @@ public class ConsumerOptions { private boolean ignoreMDCErrors = false; + private boolean forceNonOpenPgpData = false; private Date verifyNotBefore = null; private Date verifyNotAfter = new Date(); @@ -296,6 +297,21 @@ public class ConsumerOptions { return ignoreMDCErrors; } + /** + * Force PGPainless to handle the data provided by the {@link InputStream} as non-OpenPGP data. + * This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data. + * + * @return options + */ + public ConsumerOptions forceNonOpenPgpData() { + this.forceNonOpenPgpData = true; + return this; + } + + boolean isForceNonOpenPgpData() { + return forceNonOpenPgpData; + } + /** * Specify the {@link MissingKeyPassphraseStrategy}. * This strategy defines, how missing passphrases for unlocking secret keys are handled. 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 7f8e7f0d..a8847c33 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 @@ -134,7 +134,7 @@ public final class DecryptionStreamFactory { PGPObjectFactory objectFactory; // Non-OpenPGP data. We are probably verifying detached signatures - if (openPgpIn.isNonOpenPgp()) { + if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) { outerDecodingStream = openPgpIn; pgpInStream = wrapInVerifySignatureStream(outerDecodingStream, null); return new DecryptionStream(pgpInStream, resultBuilder, integrityProtectedEncryptedInputStream, null); diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpInputStream.java index c3668daa..d15f2ffb 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpInputStream.java @@ -18,7 +18,7 @@ public class OpenPgpInputStream extends BufferedInputStream { private static final byte[] ARMOR_HEADER = "-----BEGIN PGP ".getBytes(Charset.forName("UTF8")); // Buffer beginning bytes of the data - public static final int MAX_BUFFER_SIZE = 8192; + public static final int MAX_BUFFER_SIZE = 8192 * 2; private final byte[] buffer; private final int bufferLen; @@ -53,6 +53,14 @@ public class OpenPgpInputStream extends BufferedInputStream { return false; } + /** + * This method is still brittle. + * Basically we try to parse OpenPGP packets from the buffer. + * If we run into exceptions, then we know that the data is non-OpenPGP'ish. + * + * This breaks down though if we read plausible garbage where the data accidentally makes sense, + * or valid, yet incomplete packets (remember, we are still only working on a portion of the data). + */ private void determineIsBinaryOpenPgp() { if (bufferLen == -1) { // Empty data @@ -62,47 +70,24 @@ public class OpenPgpInputStream extends BufferedInputStream { try { ByteArrayInputStream bufferIn = new ByteArrayInputStream(buffer, 0, bufferLen); PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(bufferIn); + + boolean containsPackets = false; while (objectFactory.nextObject() != null) { - // read all packets in buffer + containsPackets = true; + // read all packets in buffer - hope to confirm invalid data via thrown IOExceptions } - containsOpenPgpPackets = true; + containsOpenPgpPackets = containsPackets; + } catch (IOException e) { - if (e.getMessage().contains("premature end of stream in PartialInputStream")) { - // We *probably* hit valid, but large OpenPGP data - // This is not an optimal way of determining the nature of data, but probably the best - // we can get from BC. - containsOpenPgpPackets = true; - } - // else: seemingly random, non-OpenPGP data - } - } + String msg = e.getMessage(); - private boolean startsWith(byte[] bytes, byte[] subsequence, int bufferLen) { - return indexOfSubsequence(bytes, subsequence, bufferLen) == 0; - } + // If true, we *probably* hit valid, but large OpenPGP data (not sure though) :/ + // Otherwise we hit garbage and can be sure that this is no OpenPGP data \o/ + containsOpenPgpPackets = (msg != null && msg.contains("premature end of stream in PartialInputStream")); - private int indexOfSubsequence(byte[] bytes, byte[] subsequence, int bufferLen) { - if (bufferLen == -1) { - return -1; + // This is not an optimal way of determining the nature of data, but probably the best + // we can do :/ } - // Naive implementation - // TODO: Could be improved by using e.g. Knuth-Morris-Pratt algorithm. - for (int i = 0; i < bufferLen; i++) { - if ((i + subsequence.length) <= bytes.length) { - boolean found = true; - for (int j = 0; j < subsequence.length; j++) { - if (bytes[i + j] != subsequence[j]) { - found = false; - break; - } - } - - if (found) { - return i; - } - } - } - return -1; } private boolean startsWithIgnoringWhitespace(byte[] bytes, byte[] subsequence, int bufferLen) {