pgpainless/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/TeeBCPGInputStream.kt

134 lines
4.3 KiB
Kotlin

// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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.PGPLiteralData
import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.PGPSignature
import org.pgpainless.algorithm.OpenPgpPacket
/**
* 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 [BCPGInputStream.read] to tee the data
* out though, since [BCPGInputStream.readPacket] inconsistently calls a mix of
* [BCPGInputStream.read] and [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 [DelayedTeeInputStream] which wraps the
* underlying stream. Since calling [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 [TeeBCPGInputStream] handle pushing this byte to the output stream using
* [DelayedTeeInputStream.squeeze].
*/
class TeeBCPGInputStream(inputStream: BCPGInputStream, outputStream: OutputStream) {
private val delayedTee: DelayedTeeInputStream
private val packetInputStream: BCPGInputStream
init {
delayedTee = DelayedTeeInputStream(inputStream, outputStream)
packetInputStream = BCPGInputStream(delayedTee)
}
fun nextPacketTag(): OpenPgpPacket? {
return packetInputStream.nextPacketTag().let {
if (it == -1) null else OpenPgpPacket.requireFromTag(it)
}
}
fun readPacket(): Packet = packetInputStream.readPacket()
fun readCompressedData(): PGPCompressedData {
delayedTee.squeeze()
return PGPCompressedData(packetInputStream)
}
fun readLiteralData(): PGPLiteralData {
delayedTee.squeeze()
return PGPLiteralData(packetInputStream)
}
fun readEncryptedDataList(): PGPEncryptedDataList {
delayedTee.squeeze()
return PGPEncryptedDataList(packetInputStream)
}
fun readOnePassSignature(): PGPOnePassSignature {
return PGPOnePassSignature(packetInputStream).also { delayedTee.squeeze() }
}
fun readSignature(): PGPSignature {
return PGPSignature(packetInputStream).also { delayedTee.squeeze() }
}
fun readMarker(): MarkerPacket {
return (readPacket() as MarkerPacket).also { delayedTee.squeeze() }
}
fun close() {
packetInputStream.close()
}
class DelayedTeeInputStream(
private val inputStream: InputStream,
private val outputStream: OutputStream
) : InputStream() {
private var last: Int = -1
override fun read(): Int {
if (last != -1) {
outputStream.write(last)
}
return try {
last = inputStream.read()
last
} catch (e: IOException) {
if (e.message?.contains("crc check failed in armored message") == true) {
throw e
}
-1
}
}
override fun read(b: ByteArray, off: Int, len: Int): Int {
if (last != -1) {
outputStream.write(last)
}
inputStream.read(b, off, len).let { r ->
last =
if (r > 0) {
outputStream.write(b, off, r - 1)
b[off + r - 1].toInt()
} else {
-1
}
return r
}
}
/** Squeeze the last byte out and update the output stream. */
fun squeeze() {
if (last != -1) {
outputStream.write(last)
}
last = -1
}
override fun close() {
inputStream.close()
outputStream.close()
}
}
}