mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-05 12:05:58 +01:00
Kotlin conversion: Cleartext Signature Framework
This commit is contained in:
parent
f871adc293
commit
2e4637c62f
10 changed files with 297 additions and 337 deletions
|
@ -1,169 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures;
|
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
|
||||||
import org.bouncycastle.util.Strings;
|
|
||||||
import org.pgpainless.exception.WrongConsumingMethodException;
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
|
||||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to deal with cleartext-signed messages.
|
|
||||||
* Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}.
|
|
||||||
*/
|
|
||||||
public final class ClearsignedMessageUtil {
|
|
||||||
|
|
||||||
private ClearsignedMessageUtil() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
|
|
||||||
* messageOutputStream.
|
|
||||||
*
|
|
||||||
* @param clearsignedInputStream input stream containing a clearsigned message
|
|
||||||
* @param messageOutputStream output stream to which the dearmored message shall be written
|
|
||||||
* @return signatures
|
|
||||||
*
|
|
||||||
* @throws IOException if the message is not clearsigned or some other IO error happens
|
|
||||||
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
|
||||||
*/
|
|
||||||
public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream,
|
|
||||||
OutputStream messageOutputStream)
|
|
||||||
throws IOException, WrongConsumingMethodException {
|
|
||||||
ArmoredInputStream in;
|
|
||||||
if (clearsignedInputStream instanceof ArmoredInputStream) {
|
|
||||||
in = (ArmoredInputStream) clearsignedInputStream;
|
|
||||||
} else {
|
|
||||||
in = ArmoredInputStreamFactory.get(clearsignedInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in.isClearText()) {
|
|
||||||
throw new WrongConsumingMethodException("Message is not using the Cleartext Signature Framework.");
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputStream out = new BufferedOutputStream(messageOutputStream);
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
|
|
||||||
int lookAhead = readInputLine(lineOut, in);
|
|
||||||
byte[] lineSep = getLineSeparator();
|
|
||||||
|
|
||||||
if (lookAhead != -1 && in.isClearText()) {
|
|
||||||
byte[] line = lineOut.toByteArray();
|
|
||||||
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
|
|
||||||
|
|
||||||
while (lookAhead != -1 && in.isClearText()) {
|
|
||||||
lookAhead = readInputLine(lineOut, lookAhead, in);
|
|
||||||
line = lineOut.toByteArray();
|
|
||||||
out.write(lineSep);
|
|
||||||
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (lookAhead != -1) {
|
|
||||||
byte[] line = lineOut.toByteArray();
|
|
||||||
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(in);
|
|
||||||
PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject();
|
|
||||||
|
|
||||||
return signatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn)
|
|
||||||
throws IOException {
|
|
||||||
bOut.reset();
|
|
||||||
|
|
||||||
int lookAhead = -1;
|
|
||||||
int ch;
|
|
||||||
|
|
||||||
while ((ch = fIn.read()) >= 0) {
|
|
||||||
bOut.write(ch);
|
|
||||||
if (ch == '\r' || ch == '\n') {
|
|
||||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookAhead;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn)
|
|
||||||
throws IOException {
|
|
||||||
bOut.reset();
|
|
||||||
|
|
||||||
int ch = lookAhead;
|
|
||||||
|
|
||||||
do {
|
|
||||||
bOut.write(ch);
|
|
||||||
if (ch == '\r' || ch == '\n') {
|
|
||||||
lookAhead = readPassedEOL(bOut, ch, fIn);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while ((ch = fIn.read()) >= 0);
|
|
||||||
|
|
||||||
if (ch < 0) {
|
|
||||||
lookAhead = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookAhead;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn)
|
|
||||||
throws IOException {
|
|
||||||
int lookAhead = fIn.read();
|
|
||||||
|
|
||||||
if (lastCh == '\r' && lookAhead == '\n') {
|
|
||||||
bOut.write(lookAhead);
|
|
||||||
lookAhead = fIn.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookAhead;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static byte[] getLineSeparator() {
|
|
||||||
String nl = Strings.lineSeparator();
|
|
||||||
byte[] nlBytes = new byte[nl.length()];
|
|
||||||
|
|
||||||
for (int i = 0; i != nlBytes.length; i++) {
|
|
||||||
nlBytes[i] = (byte) nl.charAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nlBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) {
|
|
||||||
int end = line.length - 1;
|
|
||||||
|
|
||||||
while (end >= 0 && isWhiteSpace(line[end])) {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
|
|
||||||
return end + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isLineEnding(byte b) {
|
|
||||||
return b == '\r' || b == '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isWhiteSpace(byte b) {
|
|
||||||
return isLineEnding(b) || b == '\t' || b == ' ';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of the {@link MultiPassStrategy}.
|
|
||||||
* This class keeps the read data in memory by caching the data inside a {@link ByteArrayOutputStream}.
|
|
||||||
*
|
|
||||||
* Note, that this class is suitable and efficient for processing small amounts of data.
|
|
||||||
* For larger data like encrypted files, use of the {@link WriteToFileMultiPassStrategy} is recommended to
|
|
||||||
* prevent {@link OutOfMemoryError OutOfMemoryErrors} and other issues.
|
|
||||||
*/
|
|
||||||
public class InMemoryMultiPassStrategy implements MultiPassStrategy {
|
|
||||||
|
|
||||||
private final ByteArrayOutputStream cache = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteArrayOutputStream getMessageOutputStream() {
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ByteArrayInputStream getMessageInputStream() {
|
|
||||||
return new ByteArrayInputStream(getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return getMessageOutputStream().toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures,
|
|
||||||
* a strategy for how to cache the read data is required.
|
|
||||||
* Otherwise, large data kept in memory could cause {@link OutOfMemoryError OutOfMemoryErrors} or other issues.
|
|
||||||
*
|
|
||||||
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes
|
|
||||||
* to do verification.
|
|
||||||
*
|
|
||||||
* This interface can be used to write the signed data stream out via {@link #getMessageOutputStream()} and later
|
|
||||||
* get access to the data again via {@link #getMessageInputStream()}.
|
|
||||||
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away.
|
|
||||||
*/
|
|
||||||
public interface MultiPassStrategy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide an {@link OutputStream} into which the signed data can be read into.
|
|
||||||
*
|
|
||||||
* @return output stream
|
|
||||||
* @throws IOException io error
|
|
||||||
*/
|
|
||||||
OutputStream getMessageOutputStream() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide an {@link InputStream} which contains the data that was previously written away in
|
|
||||||
* {@link #getMessageOutputStream()}.
|
|
||||||
*
|
|
||||||
* As there may be multiple signatures that need to be processed, each call of this method MUST return
|
|
||||||
* a new {@link InputStream}.
|
|
||||||
*
|
|
||||||
* @return input stream
|
|
||||||
* @throws IOException io error
|
|
||||||
*/
|
|
||||||
InputStream getMessageInputStream() throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the message content out to a file and re-read it to verify signatures.
|
|
||||||
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory.
|
|
||||||
* After the message has been processed completely, the messages content are available at the provided file.
|
|
||||||
*
|
|
||||||
* @param file target file
|
|
||||||
* @return strategy
|
|
||||||
*/
|
|
||||||
static MultiPassStrategy writeMessageToFile(File file) {
|
|
||||||
return new WriteToFileMultiPassStrategy(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the message content into memory.
|
|
||||||
* This strategy is best suited for small messages which fit into memory.
|
|
||||||
* After the message has been processed completely, the message content can be accessed by calling
|
|
||||||
* {@link ByteArrayOutputStream#toByteArray()} on {@link #getMessageOutputStream()}.
|
|
||||||
*
|
|
||||||
* @return strategy
|
|
||||||
*/
|
|
||||||
static InMemoryMultiPassStrategy keepMessageInMemory() {
|
|
||||||
return new InMemoryMultiPassStrategy();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of the {@link MultiPassStrategy}.
|
|
||||||
* When processing signed data the first time, the data is being written out into a file.
|
|
||||||
* For the second pass, that file is being read again.
|
|
||||||
*
|
|
||||||
* This strategy is recommended when larger amounts of data need to be processed.
|
|
||||||
* For smaller files, {@link InMemoryMultiPassStrategy} yields higher efficiency.
|
|
||||||
*/
|
|
||||||
public class WriteToFileMultiPassStrategy implements MultiPassStrategy {
|
|
||||||
|
|
||||||
private final File file;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@link MultiPassStrategy} which writes data to a file.
|
|
||||||
* Note that {@link #getMessageOutputStream()} will create the file if necessary.
|
|
||||||
*
|
|
||||||
* @param file file to write the data to and read from
|
|
||||||
*/
|
|
||||||
public WriteToFileMultiPassStrategy(File file) {
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OutputStream getMessageOutputStream() throws IOException {
|
|
||||||
if (!file.exists()) {
|
|
||||||
boolean created = file.createNewFile();
|
|
||||||
if (!created) {
|
|
||||||
throw new IOException("New file '" + file.getAbsolutePath() + "' was not created.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new FileOutputStream(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InputStream getMessageInputStream() throws IOException {
|
|
||||||
if (!file.exists()) {
|
|
||||||
throw new IOException("File '" + file.getAbsolutePath() + "' does no longer exist.");
|
|
||||||
}
|
|
||||||
return new FileInputStream(file);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Classes related to cleartext signature verification.
|
|
||||||
*/
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures;
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList
|
||||||
|
import org.bouncycastle.util.Strings
|
||||||
|
import org.pgpainless.exception.WrongConsumingMethodException
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
|
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||||
|
import java.io.*
|
||||||
|
import kotlin.jvm.Throws
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to deal with cleartext-signed messages.
|
||||||
|
* Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
|
||||||
|
*/
|
||||||
|
class ClearsignedMessageUtil {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
|
||||||
|
* messageOutputStream.
|
||||||
|
*
|
||||||
|
* @param clearsignedInputStream input stream containing a clearsigned message
|
||||||
|
* @param messageOutputStream output stream to which the dearmored message shall be written
|
||||||
|
* @return signatures
|
||||||
|
*
|
||||||
|
* @throws IOException if the message is not clearsigned or some other IO error happens
|
||||||
|
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(WrongConsumingMethodException::class, IOException::class)
|
||||||
|
fun detachSignaturesFromInbandClearsignedMessage(
|
||||||
|
clearsignedInputStream: InputStream,
|
||||||
|
messageOutputStream: OutputStream): PGPSignatureList {
|
||||||
|
val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) {
|
||||||
|
clearsignedInputStream
|
||||||
|
} else {
|
||||||
|
ArmoredInputStreamFactory.get(clearsignedInputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!input.isClearText) {
|
||||||
|
throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.")
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedOutputStream(messageOutputStream).use { output ->
|
||||||
|
val lineOut = ByteArrayOutputStream()
|
||||||
|
var lookAhead = readInputLine(lineOut, input)
|
||||||
|
val lineSep = getLineSeparator()
|
||||||
|
|
||||||
|
if (lookAhead != -1 && input.isClearText) {
|
||||||
|
var line = lineOut.toByteArray()
|
||||||
|
output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line))
|
||||||
|
|
||||||
|
while (lookAhead != -1 && input.isClearText) {
|
||||||
|
lookAhead = readInputLine(lineOut, lookAhead, input)
|
||||||
|
line = lineOut.toByteArray()
|
||||||
|
output.write(lineSep)
|
||||||
|
output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lookAhead != -1) {
|
||||||
|
val line = lineOut.toByteArray()
|
||||||
|
output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(input)
|
||||||
|
val next = objectFactory.nextObject() ?: PGPSignatureList(arrayOf())
|
||||||
|
return next as PGPSignatureList
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun readInputLine(bOut: ByteArrayOutputStream, fIn: InputStream): Int {
|
||||||
|
bOut.reset()
|
||||||
|
|
||||||
|
var lookAhead = -1
|
||||||
|
var ch: Int
|
||||||
|
|
||||||
|
while (fIn.read().also { ch = it } >= 0) {
|
||||||
|
bOut.write(ch)
|
||||||
|
if (ch == '\r'.code || ch == '\n'.code) {
|
||||||
|
lookAhead = readPassedEOL(bOut, ch, fIn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookAhead
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int {
|
||||||
|
var mLookAhead = lookAhead
|
||||||
|
bOut.reset()
|
||||||
|
var ch = mLookAhead
|
||||||
|
do {
|
||||||
|
bOut.write(ch)
|
||||||
|
if (ch == '\r'.code || ch == '\n'.code) {
|
||||||
|
mLookAhead = readPassedEOL(bOut, ch, fIn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} while (fIn.read().also { ch = it } >= 0)
|
||||||
|
if (ch < 0) {
|
||||||
|
mLookAhead = -1
|
||||||
|
}
|
||||||
|
return mLookAhead
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun readPassedEOL(bOut: ByteArrayOutputStream, lastCh: Int, fIn: InputStream): Int {
|
||||||
|
var lookAhead = fIn.read()
|
||||||
|
if (lastCh == '\r'.code && lookAhead == '\n'.code) {
|
||||||
|
bOut.write(lookAhead)
|
||||||
|
lookAhead = fIn.read()
|
||||||
|
}
|
||||||
|
return lookAhead
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun getLineSeparator(): ByteArray {
|
||||||
|
val nl = Strings.lineSeparator()
|
||||||
|
val nlBytes = ByteArray(nl.length)
|
||||||
|
for (i in nlBytes.indices) {
|
||||||
|
nlBytes[i] = nl[i].code.toByte()
|
||||||
|
}
|
||||||
|
return nlBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun getLengthWithoutSeparatorOrTrailingWhitespace(line: ByteArray): Int {
|
||||||
|
var end = line.size - 1
|
||||||
|
while (end >= 0 && isWhiteSpace(line[end])) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
return end + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun isLineEnding(b: Byte): Boolean {
|
||||||
|
return b == '\r'.code.toByte() || b == '\n'.code.toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private fun isWhiteSpace(b: Byte): Boolean {
|
||||||
|
return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the [MultiPassStrategy].
|
||||||
|
* This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream].
|
||||||
|
*
|
||||||
|
* Note, that this class is suitable and efficient for processing small amounts of data.
|
||||||
|
* For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to
|
||||||
|
* prevent [OutOfMemoryError] and other issues.
|
||||||
|
*/
|
||||||
|
class InMemoryMultiPassStrategy : MultiPassStrategy {
|
||||||
|
|
||||||
|
private val cache = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
override val messageOutputStream: ByteArrayOutputStream
|
||||||
|
get() = cache
|
||||||
|
|
||||||
|
override val messageInputStream: ByteArrayInputStream
|
||||||
|
get() = ByteArrayInputStream(getBytes())
|
||||||
|
|
||||||
|
fun getBytes(): ByteArray = messageOutputStream.toByteArray()
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures,
|
||||||
|
* a strategy for how to cache the read data is required.
|
||||||
|
* Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues.
|
||||||
|
*
|
||||||
|
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes
|
||||||
|
* to do verification.
|
||||||
|
*
|
||||||
|
* This interface can be used to write the signed data stream out via [messageOutputStream] and later
|
||||||
|
* get access to the data again via [messageInputStream].
|
||||||
|
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away.
|
||||||
|
*/
|
||||||
|
interface MultiPassStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an [OutputStream] into which the signed data can be read into.
|
||||||
|
*
|
||||||
|
* @return output stream
|
||||||
|
* @throws IOException io error
|
||||||
|
*/
|
||||||
|
val messageOutputStream: OutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an [InputStream] which contains the data that was previously written away in
|
||||||
|
* [messageOutputStream].
|
||||||
|
*
|
||||||
|
* As there may be multiple signatures that need to be processed, each call of this method MUST return
|
||||||
|
* a new [InputStream].
|
||||||
|
*
|
||||||
|
* @return input stream
|
||||||
|
* @throws IOException io error
|
||||||
|
*/
|
||||||
|
val messageInputStream: InputStream
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the message content out to a file and re-read it to verify signatures.
|
||||||
|
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory.
|
||||||
|
* After the message has been processed completely, the messages content are available at the provided file.
|
||||||
|
*
|
||||||
|
* @param file target file
|
||||||
|
* @return strategy
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun writeMessageToFile(file: File): MultiPassStrategy {
|
||||||
|
return WriteToFileMultiPassStrategy(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the message content into memory.
|
||||||
|
* This strategy is best suited for small messages which fit into memory.
|
||||||
|
* After the message has been processed completely, the message content can be accessed by calling
|
||||||
|
* [ByteArrayOutputStream.toByteArray] on [messageOutputStream].
|
||||||
|
*
|
||||||
|
* @return strategy
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun keepMessageInMemory(): InMemoryMultiPassStrategy {
|
||||||
|
return InMemoryMultiPassStrategy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the [MultiPassStrategy].
|
||||||
|
* When processing signed data the first time, the data is being written out into a file.
|
||||||
|
* For the second pass, that file is being read again.
|
||||||
|
*
|
||||||
|
* This strategy is recommended when larger amounts of data need to be processed.
|
||||||
|
* For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency.
|
||||||
|
*
|
||||||
|
* @param file file to write the data to and read from
|
||||||
|
*/
|
||||||
|
class WriteToFileMultiPassStrategy(
|
||||||
|
private val file: File
|
||||||
|
) : MultiPassStrategy {
|
||||||
|
|
||||||
|
override val messageOutputStream: OutputStream
|
||||||
|
@Throws(IOException::class)
|
||||||
|
get() {
|
||||||
|
if (!file.exists()) {
|
||||||
|
if (!file.createNewFile()) {
|
||||||
|
throw IOException("New file '${file.absolutePath}' could not be created.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FileOutputStream(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val messageInputStream: InputStream
|
||||||
|
@Throws(IOException::class)
|
||||||
|
get() {
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw IOException("File '${file.absolutePath}' does no longer exist.")
|
||||||
|
}
|
||||||
|
return FileInputStream(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ public class InlineDetachImpl implements InlineDetach {
|
||||||
if (armorIn.isClearText()) {
|
if (armorIn.isClearText()) {
|
||||||
try {
|
try {
|
||||||
signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, messageOutputStream);
|
signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, messageOutputStream);
|
||||||
if (signatures == null) {
|
if (signatures.isEmpty()) {
|
||||||
throw new SOPGPException.BadData("Data did not contain OpenPGP signatures.");
|
throw new SOPGPException.BadData("Data did not contain OpenPGP signatures.");
|
||||||
}
|
}
|
||||||
} catch (WrongConsumingMethodException e) {
|
} catch (WrongConsumingMethodException e) {
|
||||||
|
|
Loading…
Reference in a new issue