mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 11:27:57 +01:00
Fix unvalid cursor mark for large cleartext signed messages
Fixes #219, #220
This commit is contained in:
parent
50f565dd8c
commit
16e283f3a6
5 changed files with 88 additions and 5 deletions
|
@ -53,6 +53,7 @@ public class ConsumerOptions {
|
||||||
private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE;
|
private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE;
|
||||||
|
|
||||||
private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy();
|
private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy();
|
||||||
|
private boolean cleartextSigned;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consider signatures on the message made before the given timestamp invalid.
|
* Consider signatures on the message made before the given timestamp invalid.
|
||||||
|
@ -352,4 +353,20 @@ public class ConsumerOptions {
|
||||||
public MultiPassStrategy getMultiPassStrategy() {
|
public MultiPassStrategy getMultiPassStrategy() {
|
||||||
return multiPassStrategy;
|
return multiPassStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL method to mark cleartext signed messages.
|
||||||
|
* Do not call this manually.
|
||||||
|
*/
|
||||||
|
public void setIsCleartextSigned() {
|
||||||
|
this.cleartextSigned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the message is cleartext signed.
|
||||||
|
* @return cleartext signed
|
||||||
|
*/
|
||||||
|
public boolean isCleartextSigned() {
|
||||||
|
return this.cleartextSigned;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
|
@ -126,6 +127,12 @@ public final class DecryptionStreamFactory {
|
||||||
InputStream decoderStream;
|
InputStream decoderStream;
|
||||||
PGPObjectFactory objectFactory;
|
PGPObjectFactory objectFactory;
|
||||||
|
|
||||||
|
if (options.isCleartextSigned()) {
|
||||||
|
inputStream = wrapInVerifySignatureStream(bufferedIn, null);
|
||||||
|
return new DecryptionStream(inputStream, resultBuilder, integrityProtectedEncryptedInputStream,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
decoderStream = PGPUtilWrapper.getDecoderStream(bufferedIn);
|
decoderStream = PGPUtilWrapper.getDecoderStream(bufferedIn);
|
||||||
decoderStream = CRCingArmoredInputStreamWrapper.possiblyWrap(decoderStream);
|
decoderStream = CRCingArmoredInputStreamWrapper.possiblyWrap(decoderStream);
|
||||||
|
@ -170,7 +177,7 @@ public final class DecryptionStreamFactory {
|
||||||
(decoderStream instanceof ArmoredInputStream) ? decoderStream : null);
|
(decoderStream instanceof ArmoredInputStream) ? decoderStream : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream wrapInVerifySignatureStream(InputStream bufferedIn, PGPObjectFactory objectFactory) {
|
private InputStream wrapInVerifySignatureStream(InputStream bufferedIn, @Nullable PGPObjectFactory objectFactory) {
|
||||||
return new SignatureInputStream.VerifySignatures(
|
return new SignatureInputStream.VerifySignatures(
|
||||||
bufferedIn, objectFactory, onePassSignatureChecks,
|
bufferedIn, objectFactory, onePassSignatureChecks,
|
||||||
onePassSignaturesWithMissingCert, detachedSignatureChecks, options,
|
onePassSignaturesWithMissingCert, detachedSignatureChecks, options,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
@ -46,7 +47,7 @@ public abstract class SignatureInputStream extends FilterInputStream {
|
||||||
|
|
||||||
public VerifySignatures(
|
public VerifySignatures(
|
||||||
InputStream literalDataStream,
|
InputStream literalDataStream,
|
||||||
PGPObjectFactory objectFactory,
|
@Nullable PGPObjectFactory objectFactory,
|
||||||
List<OnePassSignatureCheck> opSignatures,
|
List<OnePassSignatureCheck> opSignatures,
|
||||||
Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert,
|
Map<Long, OnePassSignatureCheck> onePassSignaturesWithMissingCert,
|
||||||
List<DetachedSignatureCheck> detachedSignatures,
|
List<DetachedSignatureCheck> detachedSignatures,
|
||||||
|
@ -93,6 +94,9 @@ public abstract class SignatureInputStream extends FilterInputStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseAndCombineSignatures() throws IOException {
|
public void parseAndCombineSignatures() throws IOException {
|
||||||
|
if (objectFactory == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Parse signatures from message
|
// Parse signatures from message
|
||||||
PGPSignatureList signatures;
|
PGPSignatureList signatures;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -6,7 +6,6 @@ package org.pgpainless.decryption_verification.cleartext_signatures;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream;
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
@ -27,8 +26,6 @@ import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||||
*/
|
*/
|
||||||
public class CleartextSignatureProcessor {
|
public class CleartextSignatureProcessor {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(CleartextSignatureProcessor.class.getName());
|
|
||||||
|
|
||||||
private final ArmoredInputStream in;
|
private final ArmoredInputStream in;
|
||||||
private final ConsumerOptions options;
|
private final ConsumerOptions options;
|
||||||
|
|
||||||
|
@ -71,6 +68,7 @@ public class CleartextSignatureProcessor {
|
||||||
options.addVerificationOfDetachedSignature(signature);
|
options.addVerificationOfDetachedSignature(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.setIsCleartextSigned();
|
||||||
return PGPainless.decryptAndOrVerify()
|
return PGPainless.decryptAndOrVerify()
|
||||||
.onInputStream(multiPassStrategy.getMessageInputStream())
|
.onInputStream(multiPassStrategy.getMessageInputStream())
|
||||||
.withOptions(options);
|
.withOptions(options);
|
||||||
|
|
|
@ -15,6 +15,9 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
@ -72,6 +75,9 @@ public class CleartextSignatureVerificationTest {
|
||||||
"=Z2SO\n" +
|
"=Z2SO\n" +
|
||||||
"-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8);
|
"-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
public static final String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
public static final Random random = new Random();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cleartextSignVerification_InMemoryMultiPassStrategy() throws IOException, PGPException {
|
public void cleartextSignVerification_InMemoryMultiPassStrategy() throws IOException, PGPException {
|
||||||
PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing();
|
PGPPublicKeyRing signingKeys = TestKeys.getEmilPublicKeyRing();
|
||||||
|
@ -228,4 +234,55 @@ public class CleartextSignatureVerificationTest {
|
||||||
OpenPgpMetadata metadata = verificationStream.getResult();
|
OpenPgpMetadata metadata = verificationStream.getResult();
|
||||||
assertTrue(metadata.isVerified());
|
assertTrue(metadata.isVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecryptionOfVeryLongClearsignedMessage() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
|
String message = randomString(28, 4000);
|
||||||
|
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().modernKeyRing("Alice", null);
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||||
|
.onOutputStream(out)
|
||||||
|
.withOptions(ProducerOptions.sign(
|
||||||
|
SigningOptions.get()
|
||||||
|
.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(),
|
||||||
|
secretKeys, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
|
||||||
|
).setCleartextSigned());
|
||||||
|
|
||||||
|
Streams.pipeAll(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)), encryptionStream);
|
||||||
|
encryptionStream.close();
|
||||||
|
|
||||||
|
String cleartextSigned = out.toString();
|
||||||
|
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(cleartextSigned.getBytes(StandardCharsets.UTF_8));
|
||||||
|
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||||
|
.onInputStream(in)
|
||||||
|
.withOptions(new ConsumerOptions()
|
||||||
|
.addVerificationCert(PGPainless.extractCertificate(secretKeys)));
|
||||||
|
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
Streams.pipeAll(decryptionStream, out);
|
||||||
|
decryptionStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String randomString(int maxWordLen, int wordCount) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < wordCount; i++) {
|
||||||
|
sb.append(randomWord(maxWordLen)).append(' ');
|
||||||
|
int n = random.nextInt(12);
|
||||||
|
if (n == 11) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String randomWord(int maxWordLen) {
|
||||||
|
int len = random.nextInt(maxWordLen);
|
||||||
|
char[] word = new char[len];
|
||||||
|
for (int i = 0; i < word.length; i++) {
|
||||||
|
word[i] = alphabet.charAt(random.nextInt(alphabet.length()));
|
||||||
|
}
|
||||||
|
return new String(word);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue