1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-18 02:12:06 +01:00

CleartextSignedMessage processing: Reuse normal processing API

This commit is contained in:
Paul Schaub 2021-09-27 11:47:54 +02:00
parent f15f3a4e2a
commit ece5897bae
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 92 additions and 80 deletions

View file

@ -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();
}
}

View file

@ -26,11 +26,9 @@ 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,
* as well as verification of integrity-protected input streams once the stream gets closed. * 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 InputStream inputStream;
private final OpenPgpMetadata.Builder resultBuilder;
private boolean isClosed = false;
private final IntegrityProtectedInputStream integrityProtectedInputStream; private final IntegrityProtectedInputStream integrityProtectedInputStream;
private final InputStream armorStream; private final InputStream armorStream;
@ -46,12 +44,24 @@ public class DecryptionStream extends InputStream {
@Nonnull OpenPgpMetadata.Builder resultBuilder, @Nonnull OpenPgpMetadata.Builder resultBuilder,
IntegrityProtectedInputStream integrityProtectedInputStream, IntegrityProtectedInputStream integrityProtectedInputStream,
InputStream armorStream) { InputStream armorStream) {
super(resultBuilder);
this.inputStream = wrapped; this.inputStream = wrapped;
this.resultBuilder = resultBuilder;
this.integrityProtectedInputStream = integrityProtectedInputStream; this.integrityProtectedInputStream = integrityProtectedInputStream;
this.armorStream = armorStream; 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 @Override
public int read() throws IOException { public int read() throws IOException {
int r = inputStream.read(); int r = inputStream.read();
@ -64,30 +74,4 @@ public class DecryptionStream extends InputStream {
return read; 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();
}
} }

View file

@ -15,18 +15,12 @@
*/ */
package org.pgpainless.decryption_verification.cleartext_signatures; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.PGPSignatureList;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
@ -34,12 +28,9 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata; import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.decryption_verification.SignatureVerification;
import org.pgpainless.exception.SignatureValidationException; 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; import org.pgpainless.util.ArmoredInputStreamFactory;
/** /**
@ -67,19 +58,21 @@ public class CleartextSignatureProcessor {
} }
/** /**
* Unpack the message from the ascii armor and process the signature. * Perform the first pass of cleartext signed message processing:
* This method only returns the signature, if it is correct. * 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}. * The result of this method is a {@link DecryptionStream} which will perform the second pass.
* If an {@link InMemoryMultiPassStrategy} was used, the message can be accessed via {@link InMemoryMultiPassStrategy#getBytes()}. * It again outputs the plaintext message and performs signature verification.
* If {@link MultiPassStrategy#writeMessageToFile(File)} was used, the message content was written to the given file. *
* The result of {@link DecryptionStream#getResult()} contains information about the messages signatures.
* *
* @return validated signature * @return validated signature
* @throws IOException if the signature cannot be read. * @throws IOException if the signature cannot be read.
* @throws PGPException if the signature cannot be initialized. * @throws PGPException if the signature cannot be initialized.
* @throws SignatureValidationException if the signature is invalid. * @throws SignatureValidationException if the signature is invalid.
*/ */
public OpenPgpMetadata process() throws IOException, PGPException { public DecryptionStream getVerificationStream() throws IOException, PGPException {
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
.setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm.NULL) .setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm.NULL)
@ -88,38 +81,12 @@ public class CleartextSignatureProcessor {
PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(in, multiPassStrategy.getMessageOutputStream()); PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(in, multiPassStrategy.getMessageOutputStream());
for (PGPSignature signature : signatures) { for (PGPSignature signature : signatures) {
PGPPublicKeyRing certificate = null; options.addVerificationOfDetachedSignature(signature);
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);
}
} }
return resultBuilder.build(); return PGPainless.decryptAndOrVerify()
.onInputStream(multiPassStrategy.getMessageInputStream())
.withOptions(options);
} }
} }

View file

@ -89,13 +89,18 @@ public class CleartextSignatureVerificationTest {
.withStrategy(multiPassStrategy) .withStrategy(multiPassStrategy)
.withOptions(options); .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()); assertTrue(result.isVerified());
PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next();
assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID()); assertEquals(signature.getKeyID(), signingKeys.getPublicKey().getKeyID());
assertArrayEquals(MESSAGE_BODY, multiPassStrategy.getBytes()); assertArrayEquals(MESSAGE_BODY, out.toByteArray());
} }
@Test @Test
@ -112,7 +117,13 @@ public class CleartextSignatureVerificationTest {
.withStrategy(multiPassStrategy) .withStrategy(multiPassStrategy)
.withOptions(options); .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()); assertTrue(result.isVerified());
PGPSignature signature = result.getVerifiedSignatures().values().iterator().next(); PGPSignature signature = result.getVerifiedSignatures().values().iterator().next();
@ -205,6 +216,6 @@ public class CleartextSignatureVerificationTest {
.onInputStream(new ByteArrayInputStream(inlineSignedMessage.getBytes(StandardCharsets.UTF_8))) .onInputStream(new ByteArrayInputStream(inlineSignedMessage.getBytes(StandardCharsets.UTF_8)))
.withStrategy(new InMemoryMultiPassStrategy()) .withStrategy(new InMemoryMultiPassStrategy())
.withOptions(options) .withOptions(options)
.process()); .getVerificationStream());
} }
} }