2021-10-07 15:48:52 +02:00
|
|
|
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2021-09-13 18:09:53 +02:00
|
|
|
package org.pgpainless.decryption_verification.cleartext_signatures;
|
2021-08-18 18:27:22 +02:00
|
|
|
|
|
|
|
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;
|
2021-08-29 13:35:27 +02:00
|
|
|
import org.pgpainless.exception.WrongConsumingMethodException;
|
2021-08-18 18:27:22 +02:00
|
|
|
import org.pgpainless.implementation.ImplementationFactory;
|
2022-09-07 13:35:58 +02:00
|
|
|
import org.pgpainless.ascii_armor.ArmoredInputStreamFactory;
|
2021-08-18 18:27:22 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2022-04-02 18:56:05 +02:00
|
|
|
*
|
2021-08-18 18:27:22 +02:00
|
|
|
* @throws IOException if the message is not clearsigned or some other IO error happens
|
2022-04-02 18:56:05 +02:00
|
|
|
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
2021-08-18 18:27:22 +02:00
|
|
|
*/
|
|
|
|
public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream,
|
|
|
|
OutputStream messageOutputStream)
|
2021-08-29 13:35:27 +02:00
|
|
|
throws IOException, WrongConsumingMethodException {
|
2022-06-16 13:09:42 +02:00
|
|
|
ArmoredInputStream in;
|
|
|
|
if (clearsignedInputStream instanceof ArmoredInputStream) {
|
|
|
|
in = (ArmoredInputStream) clearsignedInputStream;
|
|
|
|
} else {
|
|
|
|
in = ArmoredInputStreamFactory.get(clearsignedInputStream);
|
|
|
|
}
|
|
|
|
|
2021-08-18 18:27:22 +02:00
|
|
|
if (!in.isClearText()) {
|
2021-08-29 13:35:27 +02:00
|
|
|
throw new WrongConsumingMethodException("Message is not using the Cleartext Signature Framework.");
|
2021-08-18 18:27:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2021-08-23 15:47:21 +02:00
|
|
|
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
|
2021-08-18 18:27:22 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (lookAhead != -1) {
|
|
|
|
byte[] line = lineOut.toByteArray();
|
|
|
|
out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
out.close();
|
|
|
|
}
|
|
|
|
|
2021-12-14 15:03:45 +01:00
|
|
|
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(in);
|
2021-08-18 18:27:22 +02:00
|
|
|
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 == ' ';
|
|
|
|
}
|
|
|
|
}
|