mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-06-17 09:04:50 +02:00
Move ArmorUtils to org.pgpainless.ascii_armor Move Armored*StreamFactory to org.pgpainless.ascii_armor Move CRCingArmoredInputStreamWrapper to org.pgpainless.ascii_armor Move SessionKey to org.pgpainless.s2k Move RevocationAttributes to org.pgpainless.key Move UserId to org.pgpainless.key Move Passphrase to org.pgpainless.s2k Move NotationRegistry to org.pgpainless.policy
170 lines
5.4 KiB
Java
170 lines
5.4 KiB
Java
// 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.ascii_armor.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 == ' ';
|
|
}
|
|
}
|