// SPDX-FileCopyrightText: 2022 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.decryption_verification; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.MarkerPacket; import org.bouncycastle.bcpg.Packet; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.algorithm.OpenPgpPacket; import javax.annotation.Nonnull; /** * 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 * {@link BCPGInputStream#readPacket()} inconsistently calls a mix of {@link BCPGInputStream#read()} and * {@link InputStream#read()} of the underlying stream. This would cause the second length byte to get swallowed up. * * Therefore, this class delegates the teeing to an {@link DelayedTeeInputStream} which wraps the underlying * stream. Since calling {@link BCPGInputStream#nextPacketTag()} reads up to and including the next packets tag, * we need to delay teeing out that byte to signature verifiers. * Hence, the reading methods of the {@link TeeBCPGInputStream} handle pushing this byte to the output stream using * {@link DelayedTeeInputStream#squeeze()}. */ public class TeeBCPGInputStream { protected final DelayedTeeInputStream delayedTee; // InputStream of OpenPGP packets of the current layer protected final BCPGInputStream packetInputStream; public TeeBCPGInputStream(BCPGInputStream inputStream, OutputStream outputStream) { this.delayedTee = new DelayedTeeInputStream(inputStream, outputStream); this.packetInputStream = BCPGInputStream.wrap(delayedTee); } public OpenPgpPacket nextPacketTag() throws IOException { int tag = packetInputStream.nextPacketTag(); if (tag == -1) { return null; } return OpenPgpPacket.requireFromTag(tag); } public Packet readPacket() throws IOException { return packetInputStream.readPacket(); } public PGPCompressedData readCompressedData() throws IOException { delayedTee.squeeze(); PGPCompressedData compressedData = new PGPCompressedData(packetInputStream); return compressedData; } public PGPLiteralData readLiteralData() throws IOException { delayedTee.squeeze(); return new PGPLiteralData(packetInputStream); } public PGPEncryptedDataList readEncryptedDataList() throws IOException { delayedTee.squeeze(); return new PGPEncryptedDataList(packetInputStream); } public PGPOnePassSignature readOnePassSignature() throws PGPException, IOException { PGPOnePassSignature onePassSignature = new PGPOnePassSignature(packetInputStream); delayedTee.squeeze(); return onePassSignature; } public PGPSignature readSignature() throws PGPException, IOException { PGPSignature signature = new PGPSignature(packetInputStream); delayedTee.squeeze(); return signature; } public MarkerPacket readMarker() throws IOException { MarkerPacket markerPacket = (MarkerPacket) readPacket(); delayedTee.squeeze(); return markerPacket; } public void close() throws IOException { this.packetInputStream.close(); } public static class DelayedTeeInputStream extends InputStream { private int last = -1; private final InputStream inputStream; private final OutputStream outputStream; public DelayedTeeInputStream(InputStream inputStream, OutputStream outputStream) { this.inputStream = inputStream; this.outputStream = outputStream; } @Override public int read() throws IOException { if (last != -1) { outputStream.write(last); } try { last = inputStream.read(); return last; } catch (IOException e) { if (e.getMessage().contains("crc check failed in armored message")) { throw e; } return -1; } } @Override public int read(@Nonnull byte[] b, int off, int len) throws IOException { if (last != -1) { outputStream.write(last); } int r = inputStream.read(b, off, len); if (r > 0) { outputStream.write(b, off, r - 1); last = b[off + r - 1]; } else { last = -1; } return r; } /** * Squeeze the last byte out and update the output stream. * * @throws IOException in case of an IO error */ public void squeeze() throws IOException { if (last != -1) { outputStream.write(last); } last = -1; } @Override public void close() throws IOException { inputStream.close(); outputStream.close(); } } }