diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index 58699fcd..df3d6805 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -56,6 +56,7 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -64,15 +65,19 @@ public class OpenPgpMessageInputStream extends InputStream { private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class); - protected final PDA automaton = new PDA(); + // Options to consume the data protected final ConsumerOptions options; protected final OpenPgpMetadata.Builder resultBuilder; - protected final BCPGInputStream bcpgIn; - protected InputStream in; + // Pushdown Automaton to verify validity of OpenPGP packet sequence in an OpenPGP message + protected final PDA automaton = new PDA(); + // InputStream of OpenPGP packets of the current layer + protected final BCPGInputStream packetInputStream; + // InputStream of a nested data packet + protected InputStream nestedInputStream; private boolean closed = false; - private Signatures signatures; + private final Signatures signatures; private MessageMetadata.Layer metadata; public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options) @@ -82,31 +87,45 @@ public class OpenPgpMessageInputStream extends InputStream { OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options, MessageMetadata.Layer metadata) throws PGPException, IOException { - // TODO: Use BCPGInputStream.wrap(inputStream); - if (inputStream instanceof BCPGInputStream) { - this.bcpgIn = (BCPGInputStream) inputStream; - } else { - this.bcpgIn = new BCPGInputStream(inputStream); - } this.options = options; this.metadata = metadata; this.resultBuilder = OpenPgpMetadata.getBuilder(); this.signatures = new Signatures(options); - this.signatures.addDetachedSignatures(options.getDetachedSignatures()); - consumePackets(); // nom nom nom + // Add detached signatures only on the outermost OpenPgpMessageInputStream + if (metadata instanceof MessageMetadata.Message) { + this.signatures.addDetachedSignatures(options.getDetachedSignatures()); + } + + // TODO: Use BCPGInputStream.wrap(inputStream); + BCPGInputStream bcpg = null; + if (inputStream instanceof BCPGInputStream) { + bcpg = (BCPGInputStream) inputStream; + } else { + bcpg = new BCPGInputStream(inputStream); + } + this.packetInputStream = new TeeBCPGInputStream(bcpg, signatures); + + // *omnomnom* + consumePackets(); } /** - * This method consumes OpenPGP packets from the current {@link BCPGInputStream}. - * Once it reaches a "nested" OpenPGP packet (Literal Data, Compressed Data, Encrypted Data), it sets
in- * to the nested stream and breaks the loop. + * Consume OpenPGP packets from the current {@link BCPGInputStream}. + * Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached, + * set
nestedInputStreamto the nested stream and breaks the loop. * The nested stream is either a simple {@link InputStream} (in case of Literal Data), or another * {@link OpenPgpMessageInputStream} in case of Compressed and Encrypted Data. + * Once the nested data is processed, this method is called again to consume the remainder + * of packets following the nested data packet. * - * @throws IOException - * @throws PGPException + * @throws IOException in case of an IO error + * @throws PGPException in case of an OpenPGP error + * @throws MissingDecryptionMethodException if there is an encrypted data packet which cannot be decrypted + * due to missing decryption methods (no key, no password, no sessionkey) + * @throws MalformedOpenPgpMessageException if the message is made of an invalid packet sequence which + * does not follow the packet syntax of RFC4880. */ private void consumePackets() throws IOException, PGPException { @@ -127,17 +146,23 @@ public class OpenPgpMessageInputStream extends InputStream { processCompressedData(); break loop; - // One Pass Signatures + // One Pass Signature case OPS: automaton.next(InputAlphabet.OnePassSignatures); - signatures.addOnePassSignatures(readOnePassSignatures()); + signatures.addOnePassSignature(readOnePassSignature()); + // signatures.addOnePassSignatures(readOnePassSignatures()); break; - // Signatures - either prepended to the message, or corresponding to the One Pass Signatures + // Signature - either prepended to the message, or corresponding to a One Pass Signature case SIG: boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops; automaton.next(InputAlphabet.Signatures); - processSignature(isSigForOPS); + PGPSignature signature = readSignature(); + processSignature(signature, isSigForOPS); + /* + PGPSignatureList signatureList = readSignatures(); + processSignatures(signatureList, isSigForOPS); + */ break; // Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC) @@ -154,7 +179,7 @@ public class OpenPgpMessageInputStream extends InputStream { // Marker Packets need to be skipped and ignored case MARKER: - bcpgIn.readPacket(); // skip + packetInputStream.readPacket(); // skip break; // Key Packets are illegal in this context @@ -182,8 +207,15 @@ public class OpenPgpMessageInputStream extends InputStream { } } - private void processSignature(boolean isSigForOPS) throws IOException { - PGPSignatureList signatureList = readSignatures(); + private void processSignature(PGPSignature signature, boolean isSigForOPS) { + if (isSigForOPS) { + signatures.addOnePassCorrespondingSignature(signature); + } else { + signatures.addPrependedSignature(signature); + } + } + + private void processSignatures(PGPSignatureList signatureList, boolean isSigForOPS) throws IOException { if (isSigForOPS) { signatures.addOnePassCorrespondingSignatures(signatureList); } else { @@ -192,21 +224,21 @@ public class OpenPgpMessageInputStream extends InputStream { } private void processCompressedData() throws IOException, PGPException { - PGPCompressedData compressedData = new PGPCompressedData(bcpgIn); + PGPCompressedData compressedData = new PGPCompressedData(packetInputStream); MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData( CompressionAlgorithm.fromId(compressedData.getAlgorithm())); - in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer); + nestedInputStream = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer); } private void processLiteralData() throws IOException { - PGPLiteralData literalData = new PGPLiteralData(bcpgIn); + PGPLiteralData literalData = new PGPLiteralData(packetInputStream); this.metadata.setChild(new MessageMetadata.LiteralData(literalData.getFileName(), literalData.getModificationTime(), StreamEncoding.requireFromCode(literalData.getFormat()))); - in = literalData.getDataStream(); + nestedInputStream = literalData.getDataStream(); } private boolean processEncryptedData() throws IOException, PGPException { - PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn); + PGPEncryptedDataList encDataList = new PGPEncryptedDataList(packetInputStream); // TODO: Replace with !encDataList.isIntegrityProtected() if (!encDataList.get(0).isIntegrityProtected()) { @@ -225,11 +257,11 @@ public class OpenPgpMessageInputStream extends InputStream { MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(options.getSessionKey().getAlgorithm()); if (esk instanceof PGPPBEEncryptedData) { PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk; - in = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData); + nestedInputStream = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData); return true; } else if (esk instanceof PGPPublicKeyEncryptedData) { PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk; - in = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData); + nestedInputStream = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData); return true; } else { throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName()); @@ -248,7 +280,7 @@ public class OpenPgpMessageInputStream extends InputStream { InputStream decrypted = skesk.getDataStream(decryptorFactory); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( SymmetricKeyAlgorithm.requireFromId(skesk.getSymmetricAlgorithm(decryptorFactory))); - in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); + nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData); return true; } catch (PGPException e) { // password mismatch? Try next password @@ -274,7 +306,7 @@ public class OpenPgpMessageInputStream extends InputStream { InputStream decrypted = pkesk.getDataStream(decryptorFactory); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory))); - in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); + nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData); return true; } catch (PGPException e) { // hm :/ @@ -293,7 +325,7 @@ public class OpenPgpMessageInputStream extends InputStream { InputStream decrypted = pkesk.getDataStream(decryptorFactory); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory))); - in = new OpenPgpMessageInputStream(decrypted, options, encryptedData); + nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData); return true; } catch (PGPException e) { // hm :/ @@ -307,7 +339,7 @@ public class OpenPgpMessageInputStream extends InputStream { private int nextTag() throws IOException { try { - return bcpgIn.nextPacketTag(); + return packetInputStream.nextPacketTag(); } catch (IOException e) { if ("Stream closed".equals(e.getMessage())) { // ZipInflater Streams sometimes close under our feet -.- @@ -345,13 +377,23 @@ public class OpenPgpMessageInputStream extends InputStream { return null; } + private PGPOnePassSignature readOnePassSignature() + throws PGPException, IOException { + return new PGPOnePassSignature(packetInputStream); + } + + private PGPSignature readSignature() + throws PGPException, IOException { + return new PGPSignature(packetInputStream); + } + private PGPOnePassSignatureListWrapper readOnePassSignatures() throws IOException { List