mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-14 16:32:06 +01:00
Enfore max recursion depth and fix CRC test
This commit is contained in:
parent
de67461fb2
commit
96a545632e
5 changed files with 66 additions and 22 deletions
|
@ -7,6 +7,7 @@ package org.pgpainless.decryption_verification;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.util.SessionKey;
|
import org.pgpainless.util.SessionKey;
|
||||||
|
|
||||||
|
@ -202,6 +203,8 @@ public class MessageMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class Layer {
|
public abstract static class Layer {
|
||||||
|
public static final int MAX_LAYER_DEPTH = 16;
|
||||||
|
protected final int depth;
|
||||||
protected final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
|
protected final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
|
||||||
protected final List<SignatureVerification.Failure> rejectedDetachedSignatures = new ArrayList<>();
|
protected final List<SignatureVerification.Failure> rejectedDetachedSignatures = new ArrayList<>();
|
||||||
protected final List<SignatureVerification> verifiedOnePassSignatures = new ArrayList<>();
|
protected final List<SignatureVerification> verifiedOnePassSignatures = new ArrayList<>();
|
||||||
|
@ -210,6 +213,13 @@ public class MessageMetadata {
|
||||||
protected final List<SignatureVerification.Failure> rejectedPrependedSignatures = new ArrayList<>();
|
protected final List<SignatureVerification.Failure> rejectedPrependedSignatures = new ArrayList<>();
|
||||||
protected Nested child;
|
protected Nested child;
|
||||||
|
|
||||||
|
public Layer(int depth) {
|
||||||
|
this.depth = depth;
|
||||||
|
if (depth > MAX_LAYER_DEPTH) {
|
||||||
|
throw new MalformedOpenPgpMessageException("Maximum nesting depth exceeded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Nested getChild() {
|
public Nested getChild() {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
@ -274,6 +284,9 @@ public class MessageMetadata {
|
||||||
|
|
||||||
public static class Message extends Layer {
|
public static class Message extends Layer {
|
||||||
|
|
||||||
|
public Message() {
|
||||||
|
super(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LiteralData implements Nested {
|
public static class LiteralData implements Nested {
|
||||||
|
@ -312,7 +325,8 @@ public class MessageMetadata {
|
||||||
public static class CompressedData extends Layer implements Nested {
|
public static class CompressedData extends Layer implements Nested {
|
||||||
protected final CompressionAlgorithm algorithm;
|
protected final CompressionAlgorithm algorithm;
|
||||||
|
|
||||||
public CompressedData(CompressionAlgorithm zip) {
|
public CompressedData(CompressionAlgorithm zip, int depth) {
|
||||||
|
super(depth);
|
||||||
this.algorithm = zip;
|
this.algorithm = zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +346,8 @@ public class MessageMetadata {
|
||||||
protected SessionKey sessionKey;
|
protected SessionKey sessionKey;
|
||||||
protected List<Long> recipients;
|
protected List<Long> recipients;
|
||||||
|
|
||||||
public EncryptedData(SymmetricKeyAlgorithm algorithm) {
|
public EncryptedData(SymmetricKeyAlgorithm algorithm, int depth) {
|
||||||
|
super(depth);
|
||||||
this.algorithm = algorithm;
|
this.algorithm = algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import java.util.List;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
import org.bouncycastle.bcpg.BCPGInputStream;
|
import org.bouncycastle.bcpg.BCPGInputStream;
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedData;
|
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||||
|
@ -56,6 +57,7 @@ import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
import org.pgpainless.policy.Policy;
|
import org.pgpainless.policy.Policy;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.signature.consumer.SignatureValidator;
|
import org.pgpainless.signature.consumer.SignatureValidator;
|
||||||
|
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
import org.pgpainless.util.SessionKey;
|
import org.pgpainless.util.SessionKey;
|
||||||
import org.pgpainless.util.Tuple;
|
import org.pgpainless.util.Tuple;
|
||||||
|
@ -91,7 +93,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
@Nonnull ConsumerOptions options,
|
@Nonnull ConsumerOptions options,
|
||||||
@Nonnull Policy policy)
|
@Nonnull Policy policy)
|
||||||
throws PGPException, IOException {
|
throws PGPException, IOException {
|
||||||
this(inputStream, options, new MessageMetadata.Message(), policy);
|
this(prepareInputStream(inputStream, options), options, new MessageMetadata.Message(), policy);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream,
|
protected OpenPgpMessageInputStream(@Nonnull InputStream inputStream,
|
||||||
|
@ -118,6 +120,26 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
consumePackets();
|
consumePackets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static InputStream prepareInputStream(InputStream inputStream, ConsumerOptions options) throws IOException {
|
||||||
|
OpenPgpInputStream openPgpIn = new OpenPgpInputStream(inputStream);
|
||||||
|
openPgpIn.reset();
|
||||||
|
|
||||||
|
if (openPgpIn.isBinaryOpenPgp()) {
|
||||||
|
return openPgpIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openPgpIn.isAsciiArmored()) {
|
||||||
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(openPgpIn);
|
||||||
|
if (armorIn.isClearText()) {
|
||||||
|
return armorIn;
|
||||||
|
} else {
|
||||||
|
return armorIn;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return openPgpIn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume OpenPGP packets from the current {@link BCPGInputStream}.
|
* Consume OpenPGP packets from the current {@link BCPGInputStream}.
|
||||||
* Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached,
|
* Once an OpenPGP packet with nested data (Literal Data, Compressed Data, Encrypted Data) is reached,
|
||||||
|
@ -219,7 +241,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
signatures.enterNesting();
|
signatures.enterNesting();
|
||||||
PGPCompressedData compressedData = packetInputStream.readCompressedData();
|
PGPCompressedData compressedData = packetInputStream.readCompressedData();
|
||||||
MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData(
|
MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData(
|
||||||
CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
|
CompressionAlgorithm.fromId(compressedData.getAlgorithm()),
|
||||||
|
metadata.depth + 1);
|
||||||
InputStream decompressed = compressedData.getDataStream();
|
InputStream decompressed = compressedData.getDataStream();
|
||||||
nestedInputStream = new OpenPgpMessageInputStream(buffer(decompressed), options, compressionLayer, policy);
|
nestedInputStream = new OpenPgpMessageInputStream(buffer(decompressed), options, compressionLayer, policy);
|
||||||
}
|
}
|
||||||
|
@ -262,7 +285,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
|
|
||||||
SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||||
.getSessionKeyDataDecryptorFactory(sessionKey);
|
.getSessionKeyDataDecryptorFactory(sessionKey);
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(sessionKey.getAlgorithm());
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
||||||
|
sessionKey.getAlgorithm(), metadata.depth + 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: Use BCs new API once shipped
|
// TODO: Use BCs new API once shipped
|
||||||
|
@ -301,7 +325,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
InputStream decrypted = skesk.getDataStream(decryptorFactory);
|
InputStream decrypted = skesk.getDataStream(decryptorFactory);
|
||||||
SessionKey sessionKey = new SessionKey(skesk.getSessionKey(decryptorFactory));
|
SessionKey sessionKey = new SessionKey(skesk.getSessionKey(decryptorFactory));
|
||||||
throwIfUnacceptable(sessionKey.getAlgorithm());
|
throwIfUnacceptable(sessionKey.getAlgorithm());
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(sessionKey.getAlgorithm());
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
||||||
|
sessionKey.getAlgorithm(), metadata.depth + 1);
|
||||||
encryptedData.sessionKey = sessionKey;
|
encryptedData.sessionKey = sessionKey;
|
||||||
IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, skesk, options);
|
IntegrityProtectedInputStream integrityProtected = new IntegrityProtectedInputStream(decrypted, skesk, options);
|
||||||
nestedInputStream = new OpenPgpMessageInputStream(buffer(integrityProtected), options, encryptedData, policy);
|
nestedInputStream = new OpenPgpMessageInputStream(buffer(integrityProtected), options, encryptedData, policy);
|
||||||
|
@ -333,7 +358,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
throwIfUnacceptable(sessionKey.getAlgorithm());
|
throwIfUnacceptable(sessionKey.getAlgorithm());
|
||||||
|
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
||||||
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
|
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
||||||
|
metadata.depth + 1);
|
||||||
encryptedData.decryptionKey = new SubkeyIdentifier(decryptionKeys, decryptionKey.getKeyID());
|
encryptedData.decryptionKey = new SubkeyIdentifier(decryptionKeys, decryptionKey.getKeyID());
|
||||||
encryptedData.sessionKey = sessionKey;
|
encryptedData.sessionKey = sessionKey;
|
||||||
|
|
||||||
|
@ -361,7 +387,8 @@ public class OpenPgpMessageInputStream extends DecryptionStream {
|
||||||
throwIfUnacceptable(sessionKey.getAlgorithm());
|
throwIfUnacceptable(sessionKey.getAlgorithm());
|
||||||
|
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
|
||||||
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
|
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
||||||
|
metadata.depth + 1);
|
||||||
encryptedData.decryptionKey = new SubkeyIdentifier(decryptionKeyCandidate.getA(), privateKey.getKeyID());
|
encryptedData.decryptionKey = new SubkeyIdentifier(decryptionKeyCandidate.getA(), privateKey.getKeyID());
|
||||||
encryptedData.sessionKey = sessionKey;
|
encryptedData.sessionKey = sessionKey;
|
||||||
|
|
||||||
|
|
|
@ -489,7 +489,7 @@ public class AsciiArmorCRCTests {
|
||||||
Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests");
|
Passphrase passphrase = Passphrase.fromPassword("flowcrypt compatibility tests");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidArmorCRCThrowsOnClose() throws PGPException, IOException {
|
public void testInvalidArmorCRCThrowsOnClose() throws IOException {
|
||||||
String message = "-----BEGIN PGP MESSAGE-----\n" +
|
String message = "-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: FlowCrypt 5.0.4 Gmail Encryption flowcrypt.com\n" +
|
"Version: FlowCrypt 5.0.4 Gmail Encryption flowcrypt.com\n" +
|
||||||
"Comment: Seamlessly send, receive and search encrypted email\n" +
|
"Comment: Seamlessly send, receive and search encrypted email\n" +
|
||||||
|
@ -542,14 +542,16 @@ public class AsciiArmorCRCTests {
|
||||||
"-----END PGP MESSAGE-----\n";
|
"-----END PGP MESSAGE-----\n";
|
||||||
|
|
||||||
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(ASCII_KEY);
|
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(ASCII_KEY);
|
||||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
assertThrows(IOException.class, () -> {
|
||||||
.onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
|
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||||
.withOptions(new ConsumerOptions().addDecryptionKey(
|
.onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
|
||||||
key, SecretKeyRingProtector.unlockAnyKeyWith(passphrase)
|
.withOptions(new ConsumerOptions().addDecryptionKey(
|
||||||
));
|
key, SecretKeyRingProtector.unlockAnyKeyWith(passphrase)
|
||||||
|
));
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(decryptionStream, outputStream);
|
Streams.pipeAll(decryptionStream, outputStream);
|
||||||
assertThrows(IOException.class, decryptionStream::close);
|
decryptionStream.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ public class MessageMetadataTest {
|
||||||
// For the sake of testing though, this is okay.
|
// For the sake of testing though, this is okay.
|
||||||
MessageMetadata.Message message = new MessageMetadata.Message();
|
MessageMetadata.Message message = new MessageMetadata.Message();
|
||||||
|
|
||||||
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP);
|
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.depth + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128);
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.depth + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256);
|
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.depth + 1);
|
||||||
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
||||||
|
|
||||||
message.setChild(compressedData);
|
message.setChild(compressedData);
|
||||||
|
|
|
@ -11,12 +11,12 @@ import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
import org.pgpainless.util.TestAllImplementations;
|
import org.pgpainless.util.TestAllImplementations;
|
||||||
|
|
||||||
public class RecursionDepthTest {
|
public class RecursionDepthTest {
|
||||||
|
@ -143,7 +143,7 @@ public class RecursionDepthTest {
|
||||||
"-----END PGP ARMORED FILE-----\n";
|
"-----END PGP ARMORED FILE-----\n";
|
||||||
|
|
||||||
|
|
||||||
assertThrows(PGPException.class, () -> {
|
assertThrows(MalformedOpenPgpMessageException.class, () -> {
|
||||||
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
|
||||||
.onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
|
.onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
|
||||||
.withOptions(new ConsumerOptions().addDecryptionKey(secretKey));
|
.withOptions(new ConsumerOptions().addDecryptionKey(secretKey));
|
||||||
|
|
Loading…
Reference in a new issue