1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-05 20:15:59 +01:00

Fix CRCing test and fully depend on new stream for decryption

This commit is contained in:
Paul Schaub 2022-10-21 16:16:45 +02:00
parent 7aaeb8ccfd
commit 09d036f17b
5 changed files with 137 additions and 55 deletions

View file

@ -31,7 +31,7 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
throw new IllegalArgumentException("Consumer options cannot be null."); throw new IllegalArgumentException("Consumer options cannot be null.");
} }
return DecryptionStreamFactory.create(inputStream, consumerOptions); return OpenPgpMessageInputStream.create(inputStream, consumerOptions);
} }
} }
} }

View file

@ -164,19 +164,35 @@ public class MessageMetadata {
} }
public String getFilename() { public String getFilename() {
return findLiteralData().getFileName(); LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getFileName();
} }
public Date getModificationDate() { public Date getModificationDate() {
return findLiteralData().getModificationDate(); LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getModificationDate();
} }
public StreamEncoding getFormat() { public StreamEncoding getFormat() {
return findLiteralData().getFormat(); LiteralData literalData = findLiteralData();
if (literalData == null) {
return null;
}
return literalData.getFormat();
} }
private LiteralData findLiteralData() { private LiteralData findLiteralData() {
Nested nested = message.child; Nested nested = message.child;
if (nested == null) {
return null;
}
while (nested.hasNestedChild()) { while (nested.hasNestedChild()) {
Layer layer = (Layer) nested; Layer layer = (Layer) nested;
nested = layer.child; nested = layer.child;

View file

@ -37,6 +37,8 @@ import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.util.io.Streams;
import org.bouncycastle.util.io.TeeInputStream;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.EncryptionPurpose; import org.pgpainless.algorithm.EncryptionPurpose;
@ -79,32 +81,87 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
// Options to consume the data // Options to consume the data
protected final ConsumerOptions options; protected final ConsumerOptions options;
private final Policy policy;
// Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message // Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message
protected final PDA syntaxVerifier = new PDA(); protected final PDA syntaxVerifier = new PDA();
// InputStream of OpenPGP packets // InputStream of OpenPGP packets
protected TeeBCPGInputStream packetInputStream; protected TeeBCPGInputStream packetInputStream;
// InputStream of a nested data packet // InputStream of a data packet containing nested data
protected InputStream nestedInputStream; protected InputStream nestedInputStream;
private boolean closed = false; private boolean closed = false;
private final Signatures signatures; private final Signatures signatures;
private final MessageMetadata.Layer metadata; private final MessageMetadata.Layer metadata;
private final Policy policy;
public OpenPgpMessageInputStream(@Nonnull InputStream inputStream, /**
* Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of
* OpenPGP messages and signatures.
* This constructor will use the global PGPainless {@link Policy}.
*
* @param inputStream underlying input stream
* @param options options for consuming the stream
*
* @throws IOException in case of an IO error
* @throws PGPException in case of an OpenPGP error
*/
public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream,
@Nonnull ConsumerOptions options) @Nonnull ConsumerOptions options)
throws IOException, PGPException { throws IOException, PGPException {
this(inputStream, options, PGPainless.getPolicy()); return create(inputStream, options, PGPainless.getPolicy());
} }
public OpenPgpMessageInputStream(@Nonnull InputStream inputStream, /**
* Create an {@link OpenPgpMessageInputStream} suitable for decryption and verification of
* OpenPGP messages and signatures.
*
* @param inputStream underlying input stream containing the OpenPGP message
* @param options options for consuming the message
* @param policy policy for acceptable algorithms etc.
*
* @throws PGPException in case of an OpenPGP error
* @throws IOException in case of an IO error
*/
public static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream,
@Nonnull ConsumerOptions options, @Nonnull ConsumerOptions options,
@Nonnull Policy policy) @Nonnull Policy policy)
throws PGPException, IOException { throws PGPException, IOException {
this( return create(inputStream, options, new MessageMetadata.Message(), policy);
prepareInputStream(inputStream, options, policy), }
options, new MessageMetadata.Message(), policy);
protected static OpenPgpMessageInputStream create(@Nonnull InputStream inputStream,
@Nonnull ConsumerOptions options,
@Nonnull MessageMetadata.Layer metadata,
@Nonnull Policy policy)
throws IOException, PGPException {
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream);
openPgpIn.reset();
if (openPgpIn.isNonOpenPgp() || options.isForceNonOpenPgpData()) {
return new OpenPgpMessageInputStream(Type.non_openpgp,
openPgpIn, options, metadata, policy);
}
if (openPgpIn.isBinaryOpenPgp()) {
// Simply consume OpenPGP message
return new OpenPgpMessageInputStream(Type.standard,
openPgpIn, options, metadata, policy);
}
if (openPgpIn.isAsciiArmored()) {
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn);
if (armorIn.isClearText()) {
return new OpenPgpMessageInputStream(Type.cleartext_signed,
armorIn, options, metadata, policy);
} else {
// Simply consume dearmored OpenPGP message
return new OpenPgpMessageInputStream(Type.standard,
armorIn, options, metadata, policy);
}
} else {
throw new AssertionError("Huh?");
}
} }
protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream, protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream,
@ -131,52 +188,56 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
consumePackets(); consumePackets();
} }
protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream, enum Type {
@Nonnull Policy policy, standard,
@Nonnull ConsumerOptions options) { cleartext_signed,
non_openpgp
}
protected OpenPgpMessageInputStream(@Nonnull Type type,
@Nonnull InputStream inputStream,
@Nonnull ConsumerOptions options,
@Nonnull MessageMetadata.Layer metadata,
@Nonnull Policy policy) throws PGPException, IOException {
super(OpenPgpMetadata.getBuilder()); super(OpenPgpMetadata.getBuilder());
this.policy = policy; this.policy = policy;
this.options = options; this.options = options;
this.metadata = new MessageMetadata.Message(); this.metadata = metadata;
this.signatures = new Signatures(options); this.signatures = new Signatures(options);
this.signatures.addDetachedSignatures(options.getDetachedSignatures());
this.packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures);
}
private static InputStream prepareInputStream(InputStream inputStream, ConsumerOptions options, Policy policy) if (metadata instanceof MessageMetadata.Message) {
throws IOException, PGPException { this.signatures.addDetachedSignatures(options.getDetachedSignatures());
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream);
openPgpIn.reset();
if (openPgpIn.isBinaryOpenPgp()) {
return openPgpIn;
} }
if (openPgpIn.isAsciiArmored()) { switch (type) {
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn); case standard:
if (armorIn.isClearText()) { // tee out packet bytes for signature verification
return parseCleartextSignedMessage(armorIn, options, policy); packetInputStream = new TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), this.signatures);
} else {
return armorIn; // *omnomnom*
} consumePackets();
} else { break;
return openPgpIn; case cleartext_signed:
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
PGPSignatureList detachedSignatures = ClearsignedMessageUtil
.detachSignaturesFromInbandClearsignedMessage(
inputStream, multiPassStrategy.getMessageOutputStream());
for (PGPSignature signature : detachedSignatures) {
options.addVerificationOfDetachedSignature(signature);
}
options.forceNonOpenPgpData();
packetInputStream = null;
nestedInputStream = new TeeInputStream(multiPassStrategy.getMessageInputStream(), this.signatures);
break;
case non_openpgp:
packetInputStream = null;
nestedInputStream = new TeeInputStream(inputStream, this.signatures);
break;
} }
} }
private static DecryptionStream parseCleartextSignedMessage(ArmoredInputStream armorIn, ConsumerOptions options, Policy policy)
throws IOException, PGPException {
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
PGPSignatureList signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, multiPassStrategy.getMessageOutputStream());
for (PGPSignature signature : signatures) {
options.addVerificationOfDetachedSignature(signature);
}
options.forceNonOpenPgpData();
return new OpenPgpMessageInputStream(multiPassStrategy.getMessageInputStream(), policy, options);
}
/** /**
* Consume OpenPGP packets from the current {@link BCPGInputStream}. * Consume OpenPGP packets from the current {@link BCPGInputStream}.
* Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached, * Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached,
@ -196,6 +257,9 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
private void consumePackets() private void consumePackets()
throws IOException, PGPException { throws IOException, PGPException {
OpenPgpPacket nextPacket; OpenPgpPacket nextPacket;
if (packetInputStream == null) {
return;
}
loop: // we break this when we go deeper. loop: // we break this when we go deeper.
while ((nextPacket = packetInputStream.nextPacketTag()) != null) { while ((nextPacket = packetInputStream.nextPacketTag()) != null) {

View file

@ -4,6 +4,11 @@
package org.pgpainless.decryption_verification; package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.NoSuchElementException;
import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.MarkerPacket; import org.bouncycastle.bcpg.MarkerPacket;
import org.bouncycastle.bcpg.Packet; import org.bouncycastle.bcpg.Packet;
@ -15,11 +20,6 @@ import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.OpenPgpPacket; import org.pgpainless.algorithm.OpenPgpPacket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.NoSuchElementException;
/** /**
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. * Since we need to update signatures with data from the underlying stream, this class is used to tee out the data.
* Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since * Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since
@ -96,7 +96,6 @@ public class TeeBCPGInputStream {
return markerPacket; return markerPacket;
} }
public void close() throws IOException { public void close() throws IOException {
this.packetInputStream.close(); this.packetInputStream.close();
this.delayedTee.close(); this.delayedTee.close();
@ -122,6 +121,9 @@ public class TeeBCPGInputStream {
last = inputStream.read(); last = inputStream.read();
return last; return last;
} catch (IOException e) { } catch (IOException e) {
if ("crc check failed in armored message.".equals(e.getMessage())) {
throw e;
}
return -1; return -1;
} }
} }

View file

@ -677,7 +677,7 @@ public class OpenPgpMessageInputStreamTest {
throws IOException, PGPException { throws IOException, PGPException {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)); ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8));
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
OpenPgpMessageInputStream pgpIn = new OpenPgpMessageInputStream(armorIn, options); OpenPgpMessageInputStream pgpIn = OpenPgpMessageInputStream.create(armorIn, options);
return pgpIn; return pgpIn;
} }
} }