From f7a7035059cc699017f1a61ad127bab1e0aac55e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Oct 2021 15:04:37 +0200 Subject: [PATCH] Workaround for PGPUtil accidentally mistaking plain data for base64 encoded data. --- .../DecryptionStreamFactory.java | 5 +- .../key/collection/PGPKeyRingCollection.java | 4 +- .../java/org/pgpainless/util/ArmorUtils.java | 3 +- .../org/pgpainless/util/PGPUtilWrapper.java | 52 ++++++++++++++++ .../org/bouncycastle/PGPUtilWrapperTest.java | 59 +++++++++++++++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/PGPUtilWrapper.java create mode 100644 pgpainless-core/src/test/java/org/bouncycastle/PGPUtilWrapperTest.java 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 359408cb..10496a7d 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,6 +67,7 @@ import org.pgpainless.signature.DetachedSignature; import org.pgpainless.signature.OnePassSignatureCheck; import org.pgpainless.signature.SignatureUtils; import org.pgpainless.util.CRCingArmoredInputStreamWrapper; +import org.pgpainless.util.PGPUtilWrapper; import org.pgpainless.util.Passphrase; import org.pgpainless.util.Tuple; import org.slf4j.Logger; @@ -121,10 +122,10 @@ public final class DecryptionStreamFactory { } private DecryptionStream parseOpenPGPDataAndCreateDecryptionStream(InputStream inputStream) throws IOException, PGPException { + // Make sure we handle armored and non-armored data properly BufferedInputStream bufferedIn = new BufferedInputStream(inputStream); - bufferedIn.mark(200); + InputStream decoderStream = PGPUtilWrapper.getDecoderStream(bufferedIn); - InputStream decoderStream = PGPUtil.getDecoderStream(bufferedIn); decoderStream = CRCingArmoredInputStreamWrapper.possiblyWrap(decoderStream); if (decoderStream instanceof ArmoredInputStream) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java index 473b392b..8951b4bc 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java @@ -31,8 +31,8 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.PGPUtil; import org.pgpainless.implementation.ImplementationFactory; +import org.pgpainless.util.ArmorUtils; /** * This class describes a logic of handling a collection of different {@link PGPKeyRing}. The logic was inspired by @@ -57,7 +57,7 @@ public class PGPKeyRingCollection { */ public PGPKeyRingCollection(@Nonnull InputStream in, boolean isSilent) throws IOException, PGPException { // Double getDecoderStream because of #96 - InputStream decoderStream = PGPUtil.getDecoderStream(PGPUtil.getDecoderStream(in)); + InputStream decoderStream = ArmorUtils.getDecoderStream(in); PGPObjectFactory pgpFact = new PGPObjectFactory(decoderStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator()); Object obj; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java index 18b33574..3979b5ac 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java @@ -228,7 +228,8 @@ public final class ArmorUtils { * @return BufferedInputStreamExt */ public static InputStream getDecoderStream(InputStream inputStream) throws IOException { - InputStream decoderStream = PGPUtil.getDecoderStream(inputStream); + BufferedInputStream buf = new BufferedInputStream(inputStream, 512); + InputStream decoderStream = PGPUtilWrapper.getDecoderStream(buf); // Data is not armored -> return if (decoderStream instanceof BufferedInputStream) { return decoderStream; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/PGPUtilWrapper.java b/pgpainless-core/src/main/java/org/pgpainless/util/PGPUtilWrapper.java new file mode 100644 index 00000000..838b777e --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/PGPUtilWrapper.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.openpgp.PGPUtil; + +public final class PGPUtilWrapper { + + private PGPUtilWrapper() { + + } + + /** + * {@link PGPUtil#getDecoderStream(InputStream)} sometimes mistakens non-base64 data for base64 encoded data. + * + * This method expects a {@link BufferedInputStream} which is being reset in case an {@link IOException} is encountered. + * Therefore, we can properly handle non-base64 encoded data. + * + * @param buf buffered input stream + * @return input stream + * @throws IOException in case of an io error which is unrelated to base64 encoding + */ + public static InputStream getDecoderStream(BufferedInputStream buf) throws IOException { + buf.mark(512); + try { + return PGPUtil.getDecoderStream(buf); + } catch (IOException e) { + if (e.getMessage().contains("invalid characters encountered at end of base64 data")) { + buf.reset(); + return buf; + } + throw e; + } + } +} diff --git a/pgpainless-core/src/test/java/org/bouncycastle/PGPUtilWrapperTest.java b/pgpainless-core/src/test/java/org/bouncycastle/PGPUtilWrapperTest.java new file mode 100644 index 00000000..c24d990e --- /dev/null +++ b/pgpainless-core/src/test/java/org/bouncycastle/PGPUtilWrapperTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.bouncycastle; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; +import org.pgpainless.util.PGPUtilWrapper; + +public class PGPUtilWrapperTest { + + @Test + public void testGetDecoderStream() throws IOException { + ByteArrayInputStream msg = new ByteArrayInputStream("Foo\nBar".getBytes(StandardCharsets.UTF_8)); + PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream litOut = literalDataGenerator.open(out, PGPLiteralDataGenerator.TEXT, "", new Date(), new byte[1 << 9]); + Streams.pipeAll(msg, litOut); + literalDataGenerator.close(); + + InputStream in = new ByteArrayInputStream(out.toByteArray()); + PGPObjectFactory objectFactory = new BcPGPObjectFactory(in); + PGPLiteralData literalData = (PGPLiteralData) objectFactory.nextObject(); + InputStream litIn = literalData.getDataStream(); + BufferedInputStream bufIn = new BufferedInputStream(litIn); + InputStream decoderStream = PGPUtilWrapper.getDecoderStream(bufIn); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + Streams.pipeAll(decoderStream, result); + assertEquals("Foo\nBar", result.toString()); + } +}