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:
parent
f15f3a4e2a
commit
ece5897bae
4 changed files with 92 additions and 80 deletions
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
options.addVerificationOfDetachedSignature(signature);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue