diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CloseForResultInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CloseForResultInputStream.java new file mode 100644 index 00000000..bb42e036 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CloseForResultInputStream.java @@ -0,0 +1,50 @@ +/* + * 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.decryption_verification; + +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nonnull; + +public abstract class CloseForResultInputStream extends InputStream { + + protected final OpenPgpMetadata.Builder resultBuilder; + private boolean isClosed = false; + + public CloseForResultInputStream(@Nonnull OpenPgpMetadata.Builder resultBuilder) { + this.resultBuilder = resultBuilder; + } + + @Override + public void close() throws IOException { + this.isClosed = true; + } + + /** + * Return the result of the decryption. + * The result contains metadata about the decryption, such as signatures, used keys and algorithms, as well as information + * about the decrypted file/stream. + * + * Can only be obtained once the stream got successfully closed ({@link #close()}). + * @return metadata + */ + public OpenPgpMetadata getResult() { + if (!isClosed) { + throw new IllegalStateException("Stream MUST be closed before the result can be accessed."); + } + return resultBuilder.build(); + } +} 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 2f04502e..cdee6e77 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 @@ -26,11 +26,9 @@ import org.pgpainless.util.IntegrityProtectedInputStream; * Decryption Stream that handles updating and verification of detached signatures, * as well as verification of integrity-protected input streams once the stream gets closed. */ -public class DecryptionStream extends InputStream { +public class DecryptionStream extends CloseForResultInputStream { private final InputStream inputStream; - private final OpenPgpMetadata.Builder resultBuilder; - private boolean isClosed = false; private final IntegrityProtectedInputStream integrityProtectedInputStream; private final InputStream armorStream; @@ -46,12 +44,24 @@ public class DecryptionStream extends InputStream { @Nonnull OpenPgpMetadata.Builder resultBuilder, IntegrityProtectedInputStream integrityProtectedInputStream, InputStream armorStream) { + super(resultBuilder); this.inputStream = wrapped; - this.resultBuilder = resultBuilder; this.integrityProtectedInputStream = integrityProtectedInputStream; this.armorStream = armorStream; } + @Override + public void close() throws IOException { + if (armorStream != null) { + Streams.drain(armorStream); + } + inputStream.close(); + if (integrityProtectedInputStream != null) { + integrityProtectedInputStream.close(); + } + super.close(); + } + @Override public int read() throws IOException { int r = inputStream.read(); @@ -64,30 +74,4 @@ public class DecryptionStream extends InputStream { return read; } - @Override - public void close() throws IOException { - if (armorStream != null) { - Streams.drain(armorStream); - } - inputStream.close(); - if (integrityProtectedInputStream != null) { - integrityProtectedInputStream.close(); - } - this.isClosed = true; - } - - /** - * Return the result of the decryption. - * The result contains metadata about the decryption, such as signatures, used keys and algorithms, as well as information - * about the decrypted file/stream. - * - * Can only be obtained once the stream got successfully closed ({@link #close()}). - * @return metadata - */ - public OpenPgpMetadata getResult() { - if (!isClosed) { - throw new IllegalStateException("DecryptionStream MUST be closed before the result can be accessed."); - } - return resultBuilder.build(); - } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/CleartextSignatureProcessor.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/CleartextSignatureProcessor.java index a8366b01..394de734 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/CleartextSignatureProcessor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/CleartextSignatureProcessor.java @@ -15,18 +15,12 @@ */ package org.pgpainless.decryption_verification.cleartext_signatures; -import static org.pgpainless.signature.SignatureValidator.signatureWasCreatedInBounds; - -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.logging.Level; import java.util.logging.Logger; import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.pgpainless.PGPainless; @@ -34,12 +28,9 @@ import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.decryption_verification.OpenPgpMetadata; -import org.pgpainless.decryption_verification.SignatureVerification; import org.pgpainless.exception.SignatureValidationException; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.signature.CertificateValidator; -import org.pgpainless.signature.SignatureVerifier; import org.pgpainless.util.ArmoredInputStreamFactory; /** @@ -67,19 +58,21 @@ public class CleartextSignatureProcessor { } /** - * Unpack the message from the ascii armor and process the signature. - * This method only returns the signature, if it is correct. + * Perform the first pass of cleartext signed message processing: + * Unpack the message from the ascii armor and detach signatures. + * The plaintext message is being written to cache/disk according to the used {@link MultiPassStrategy}. * - * After the message has been processed, the content can be retrieved from the {@link MultiPassStrategy}. - * If an {@link InMemoryMultiPassStrategy} was used, the message can be accessed via {@link InMemoryMultiPassStrategy#getBytes()}. - * If {@link MultiPassStrategy#writeMessageToFile(File)} was used, the message content was written to the given file. + * The result of this method is a {@link DecryptionStream} which will perform the second pass. + * It again outputs the plaintext message and performs signature verification. + * + * The result of {@link DecryptionStream#getResult()} contains information about the messages signatures. * * @return validated signature * @throws IOException if the signature cannot be read. * @throws PGPException if the signature cannot be initialized. * @throws SignatureValidationException if the signature is invalid. */ - public OpenPgpMetadata process() throws IOException, PGPException { + public DecryptionStream getVerificationStream() throws IOException, PGPException { OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) .setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm.NULL) @@ -88,38 +81,12 @@ public class CleartextSignatureProcessor { PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(in, multiPassStrategy.getMessageOutputStream()); for (PGPSignature signature : signatures) { - PGPPublicKeyRing certificate = null; - PGPPublicKey signingKey = null; - for (PGPPublicKeyRing cert : options.getCertificates()) { - signingKey = cert.getPublicKey(signature.getKeyID()); - if (signingKey != null) { - certificate = cert; - break; - } - } - - try { - if (signingKey == null) { - throw new SignatureValidationException("Missing verification key with key-id " + Long.toHexString(signature.getKeyID())); - } - - SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(certificate, signingKey.getKeyID()); - - signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(signature); - SignatureVerifier.initializeSignatureAndUpdateWithSignedData(signature, multiPassStrategy.getMessageInputStream(), signingKey); - CertificateValidator.validateCertificateAndVerifyInitializedSignature(signature, certificate, PGPainless.getPolicy()); - resultBuilder.addVerifiedInbandSignature(new SignatureVerification(signature, signingKeyIdentifier)); - } catch (SignatureValidationException e) { - LOGGER.log(Level.INFO, "Cannot verify signature made by key " + Long.toHexString(signature.getKeyID()) + ": " + e.getMessage()); - SubkeyIdentifier signingKeyIdentifier = null; - if (signingKey != null) { - signingKeyIdentifier = new SubkeyIdentifier(certificate, signingKey.getKeyID()); - } - resultBuilder.addInvalidInbandSignature(new SignatureVerification(signature, signingKeyIdentifier), e); - } + options.addVerificationOfDetachedSignature(signature); } - return resultBuilder.build(); + return PGPainless.decryptAndOrVerify() + .onInputStream(multiPassStrategy.getMessageInputStream()) + .withOptions(options); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java index 1302e12c..ca86da0a 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CleartextSignatureVerificationTest.java @@ -89,13 +89,18 @@ public class CleartextSignatureVerificationTest { .withStrategy(multiPassStrategy) .withOptions(options); - OpenPgpMetadata result = processor.process(); + DecryptionStream decryptionStream = processor.getVerificationStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + + OpenPgpMetadata result = decryptionStream.getResult(); assertTrue(result.isVerified()); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); - assertArrayEquals(MESSAGE_BODY, multiPassStrategy.getBytes()); + assertArrayEquals(MESSAGE_BODY, out.toByteArray()); } @Test @@ -112,7 +117,13 @@ public class CleartextSignatureVerificationTest { .withStrategy(multiPassStrategy) .withOptions(options); - OpenPgpMetadata result = processor.process(); + DecryptionStream decryptionStream = processor.getVerificationStream(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decryptionStream, out); + decryptionStream.close(); + + OpenPgpMetadata result = decryptionStream.getResult(); assertTrue(result.isVerified()); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); @@ -205,6 +216,6 @@ public class CleartextSignatureVerificationTest { .onInputStream(new ByteArrayInputStream(inlineSignedMessage.getBytes(StandardCharsets.UTF_8))) .withStrategy(new InMemoryMultiPassStrategy()) .withOptions(options) - .process()); + .getVerificationStream()); } }