mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 20:37:59 +01:00
WIP: Replace nesting with independent instancing
This commit is contained in:
parent
60d6289c4d
commit
6233ac61e6
13 changed files with 1227 additions and 411 deletions
|
@ -12,41 +12,50 @@ import org.bouncycastle.bcpg.Packet;
|
||||||
import org.bouncycastle.bcpg.PacketTags;
|
import org.bouncycastle.bcpg.PacketTags;
|
||||||
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
|
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
|
||||||
import org.bouncycastle.bcpg.SignaturePacket;
|
import org.bouncycastle.bcpg.SignaturePacket;
|
||||||
import org.bouncycastle.bcpg.SymmetricEncDataPacket;
|
|
||||||
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
|
|
||||||
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
|
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
|
||||||
import org.bouncycastle.bcpg.TrustPacket;
|
|
||||||
import org.bouncycastle.bcpg.UserAttributePacket;
|
|
||||||
import org.bouncycastle.bcpg.UserIDPacket;
|
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
import org.pgpainless.algorithm.OpenPgpPacket;
|
import org.pgpainless.algorithm.OpenPgpPacket;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.InputAlphabet;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.NestingPDA;
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||||
|
import org.pgpainless.exception.MissingDecryptionMethodException;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.SequenceInputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
public class PGPDecryptionStream extends InputStream {
|
public class MessageDecryptionStream extends InputStream {
|
||||||
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
private final ConsumerOptions options;
|
||||||
|
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
// nested streams, outermost at the bottom of the stack
|
// nested streams, outermost at the bottom of the stack
|
||||||
Stack<Layer> packetLayers = new Stack<>();
|
Stack<Layer> packetLayers = new Stack<>();
|
||||||
|
List<PublicKeyEncSessionPacket> pkeskList = new ArrayList<>();
|
||||||
|
List<SymmetricKeyEncSessionPacket> skeskList = new ArrayList<>();
|
||||||
|
|
||||||
public PGPDecryptionStream(InputStream inputStream) throws IOException, PGPException {
|
public MessageDecryptionStream(InputStream inputStream, ConsumerOptions options)
|
||||||
try {
|
throws IOException, PGPException {
|
||||||
|
this.options = options;
|
||||||
packetLayers.push(Layer.initial(inputStream));
|
packetLayers.push(Layer.initial(inputStream));
|
||||||
walkLayer();
|
walkLayer();
|
||||||
} catch (MalformedOpenPgpMessageException e) {
|
|
||||||
throw e.toRuntimeException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void walkLayer() throws PGPException, IOException {
|
private void walkLayer() throws PGPException, IOException {
|
||||||
|
@ -54,6 +63,7 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are currently in the deepest layer
|
||||||
Layer layer = packetLayers.peek();
|
Layer layer = packetLayers.peek();
|
||||||
BCPGInputStream inputStream = (BCPGInputStream) layer.inputStream;
|
BCPGInputStream inputStream = (BCPGInputStream) layer.inputStream;
|
||||||
|
|
||||||
|
@ -65,33 +75,23 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
OpenPgpPacket tag = nextTagOrThrow(inputStream);
|
OpenPgpPacket tag = nextTagOrThrow(inputStream);
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
|
|
||||||
case PKESK:
|
case LIT:
|
||||||
PublicKeyEncSessionPacket pkeskPacket = (PublicKeyEncSessionPacket) inputStream.readPacket();
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
PGPEncryptedDataList encList = null;
|
PGPLiteralData literalData = new PGPLiteralData(inputStream);
|
||||||
break;
|
packetLayers.push(Layer.literalMessage(literalData.getDataStream()));
|
||||||
case SIG:
|
break loop;
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
|
|
||||||
|
case COMP:
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
PGPCompressedData compressedData = new PGPCompressedData(inputStream);
|
||||||
|
inputStream = new BCPGInputStream(compressedData.getDataStream());
|
||||||
|
packetLayers.push(Layer.compressedData(inputStream));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OPS:
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
|
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
|
||||||
while (inputStream.nextPacketTag() == PacketTags.SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
|
|
||||||
Packet packet = inputStream.readPacket();
|
|
||||||
if (packet instanceof SignaturePacket) {
|
|
||||||
SignaturePacket sig = (SignaturePacket) packet;
|
|
||||||
sig.encode(bcpgOut);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PGPSignatureList signatures = (PGPSignatureList) ImplementationFactory.getInstance()
|
|
||||||
.getPGPObjectFactory(buf.toByteArray()).nextObject();
|
|
||||||
break;
|
|
||||||
case SKESK:
|
|
||||||
SymmetricKeyEncSessionPacket skeskPacket = (SymmetricKeyEncSessionPacket) inputStream.readPacket();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case OPS:
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
buf = new ByteArrayOutputStream();
|
|
||||||
bcpgOut = new BCPGOutputStream(buf);
|
|
||||||
while (inputStream.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
|
while (inputStream.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
|
||||||
Packet packet = inputStream.readPacket();
|
Packet packet = inputStream.readPacket();
|
||||||
if (packet instanceof OnePassSignaturePacket) {
|
if (packet instanceof OnePassSignaturePacket) {
|
||||||
|
@ -102,60 +102,103 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) ImplementationFactory.getInstance()
|
PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) ImplementationFactory.getInstance()
|
||||||
.getPGPObjectFactory(buf.toByteArray()).nextObject();
|
.getPGPObjectFactory(buf.toByteArray()).nextObject();
|
||||||
break;
|
break;
|
||||||
case SK:
|
|
||||||
|
case SIG:
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
|
||||||
|
buf = new ByteArrayOutputStream();
|
||||||
|
bcpgOut = new BCPGOutputStream(buf);
|
||||||
|
while (inputStream.nextPacketTag() == PacketTags.SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) {
|
||||||
|
Packet packet = inputStream.readPacket();
|
||||||
|
if (packet instanceof SignaturePacket) {
|
||||||
|
SignaturePacket sig = (SignaturePacket) packet;
|
||||||
|
sig.encode(bcpgOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PGPSignatureList signatures = (PGPSignatureList) ImplementationFactory.getInstance()
|
||||||
|
.getPGPObjectFactory(buf.toByteArray()).nextObject();
|
||||||
break;
|
break;
|
||||||
case PK:
|
|
||||||
|
case PKESK:
|
||||||
|
PublicKeyEncSessionPacket pkeskPacket = (PublicKeyEncSessionPacket) inputStream.readPacket();
|
||||||
|
pkeskList.add(pkeskPacket);
|
||||||
break;
|
break;
|
||||||
case SSK:
|
|
||||||
break;
|
case SKESK:
|
||||||
case COMP:
|
SymmetricKeyEncSessionPacket skeskPacket = (SymmetricKeyEncSessionPacket) inputStream.readPacket();
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
skeskList.add(skeskPacket);
|
||||||
PGPCompressedData compressedData = new PGPCompressedData(inputStream);
|
|
||||||
inputStream = new BCPGInputStream(compressedData.getDataStream());
|
|
||||||
packetLayers.push(Layer.CompressedData(inputStream));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SED:
|
case SED:
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
|
if (!options.isIgnoreMDCErrors()) {
|
||||||
SymmetricEncDataPacket symmetricEncDataPacket = (SymmetricEncDataPacket) inputStream.readPacket();
|
throw new MessageNotIntegrityProtectedException();
|
||||||
break;
|
}
|
||||||
|
// No break; we continue below!
|
||||||
|
case SEIPD:
|
||||||
|
automaton.next(InputAlphabet.EncryptedData);
|
||||||
|
PGPEncryptedDataList encryptedDataList = assembleEncryptedDataList(inputStream);
|
||||||
|
|
||||||
|
for (PGPEncryptedData encData : encryptedDataList) {
|
||||||
|
if (encData instanceof PGPPBEEncryptedData) {
|
||||||
|
PGPPBEEncryptedData skenc = (PGPPBEEncryptedData) encData;
|
||||||
|
for (Passphrase passphrase : options.getDecryptionPassphrases()) {
|
||||||
|
PBEDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||||
|
.getPBEDataDecryptorFactory(passphrase);
|
||||||
|
InputStream decryptedIn = skenc.getDataStream(decryptorFactory);
|
||||||
|
packetLayers.push(Layer.encryptedData(new BCPGInputStream(decryptedIn)));
|
||||||
|
walkLayer();
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new MissingDecryptionMethodException("Cannot decrypt message.");
|
||||||
|
|
||||||
case MARKER:
|
case MARKER:
|
||||||
inputStream.readPacket(); // discard
|
inputStream.readPacket(); // discard
|
||||||
break;
|
break;
|
||||||
case LIT:
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
case SK:
|
||||||
PGPLiteralData literalData = new PGPLiteralData(inputStream);
|
case PK:
|
||||||
packetLayers.push(Layer.LiteralMessage(literalData.getDataStream()));
|
case SSK:
|
||||||
break loop;
|
|
||||||
case TRUST:
|
|
||||||
TrustPacket trustPacket = (TrustPacket) inputStream.readPacket();
|
|
||||||
break;
|
|
||||||
case UID:
|
|
||||||
UserIDPacket userIDPacket = (UserIDPacket) inputStream.readPacket();
|
|
||||||
break;
|
|
||||||
case PSK:
|
case PSK:
|
||||||
break;
|
case TRUST:
|
||||||
|
case UID:
|
||||||
case UATTR:
|
case UATTR:
|
||||||
UserAttributePacket userAttributePacket = (UserAttributePacket) inputStream.readPacket();
|
throw new MalformedOpenPgpMessageException("OpenPGP packet " + tag + " MUST NOT be part of OpenPGP messages.");
|
||||||
break;
|
|
||||||
case SEIPD:
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
|
|
||||||
SymmetricEncIntegrityPacket symmetricEncIntegrityPacket = (SymmetricEncIntegrityPacket) inputStream.readPacket();
|
|
||||||
break;
|
|
||||||
case MOD:
|
case MOD:
|
||||||
ModDetectionCodePacket modDetectionCodePacket = (ModDetectionCodePacket) inputStream.readPacket();
|
ModDetectionCodePacket modDetectionCodePacket = (ModDetectionCodePacket) inputStream.readPacket();
|
||||||
break;
|
break;
|
||||||
case EXP_1:
|
case EXP_1:
|
||||||
break;
|
|
||||||
case EXP_2:
|
case EXP_2:
|
||||||
break;
|
|
||||||
case EXP_3:
|
case EXP_3:
|
||||||
break;
|
|
||||||
case EXP_4:
|
case EXP_4:
|
||||||
break;
|
throw new MalformedOpenPgpMessageException("Experimental packet " + tag + " found inside the message.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PGPEncryptedDataList assembleEncryptedDataList(BCPGInputStream inputStream)
|
||||||
|
throws IOException {
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
|
||||||
|
|
||||||
|
for (SymmetricKeyEncSessionPacket skesk : skeskList) {
|
||||||
|
bcpgOut.write(skesk.getEncoded());
|
||||||
|
}
|
||||||
|
skeskList.clear();
|
||||||
|
for (PublicKeyEncSessionPacket pkesk : pkeskList) {
|
||||||
|
bcpgOut.write(pkesk.getEncoded());
|
||||||
|
}
|
||||||
|
pkeskList.clear();
|
||||||
|
|
||||||
|
SequenceInputStream sqin = new SequenceInputStream(
|
||||||
|
new ByteArrayInputStream(buf.toByteArray()), inputStream);
|
||||||
|
|
||||||
|
PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) ImplementationFactory.getInstance()
|
||||||
|
.getPGPObjectFactory(sqin).nextObject();
|
||||||
|
return encryptedDataList;
|
||||||
|
}
|
||||||
|
|
||||||
private OpenPgpPacket nextTagOrThrow(BCPGInputStream inputStream)
|
private OpenPgpPacket nextTagOrThrow(BCPGInputStream inputStream)
|
||||||
throws IOException, InvalidOpenPgpPacketException {
|
throws IOException, InvalidOpenPgpPacketException {
|
||||||
try {
|
try {
|
||||||
|
@ -167,17 +210,13 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
|
|
||||||
private void popLayer() throws MalformedOpenPgpMessageException {
|
private void popLayer() throws MalformedOpenPgpMessageException {
|
||||||
if (packetLayers.pop().isNested)
|
if (packetLayers.pop().isNested)
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (packetLayers.isEmpty()) {
|
if (packetLayers.isEmpty()) {
|
||||||
try {
|
|
||||||
automaton.assertValid();
|
automaton.assertValid();
|
||||||
} catch (MalformedOpenPgpMessageException e) {
|
|
||||||
throw e.toRuntimeException();
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,13 +226,10 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
}
|
}
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
try {
|
|
||||||
popLayer();
|
popLayer();
|
||||||
|
try {
|
||||||
walkLayer();
|
walkLayer();
|
||||||
} catch (MalformedOpenPgpMessageException e) {
|
} catch (PGPException e) {
|
||||||
throw e.toRuntimeException();
|
|
||||||
}
|
|
||||||
catch (PGPException e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return read();
|
return read();
|
||||||
|
@ -227,11 +263,15 @@ public class PGPDecryptionStream extends InputStream {
|
||||||
return new Layer(bcpgIn, true);
|
return new Layer(bcpgIn, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Layer LiteralMessage(InputStream inputStream) {
|
static Layer literalMessage(InputStream inputStream) {
|
||||||
return new Layer(inputStream, false);
|
return new Layer(inputStream, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Layer CompressedData(InputStream inputStream) {
|
static Layer compressedData(InputStream inputStream) {
|
||||||
|
return new Layer(inputStream, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Layer encryptedData(InputStream inputStream) {
|
||||||
return new Layer(inputStream, true);
|
return new Layer(inputStream, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
|
import com.sun.tools.javac.code.Attribute;
|
||||||
|
import org.bouncycastle.bcpg.BCPGInputStream;
|
||||||
|
import org.bouncycastle.bcpg.BCPGOutputStream;
|
||||||
|
import org.bouncycastle.bcpg.OnePassSignaturePacket;
|
||||||
|
import org.bouncycastle.bcpg.Packet;
|
||||||
|
import org.bouncycastle.bcpg.PacketTags;
|
||||||
|
import org.bouncycastle.bcpg.SignaturePacket;
|
||||||
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPEncryptedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||||
|
import org.bouncycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||||
|
import org.pgpainless.algorithm.OpenPgpPacket;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.InputAlphabet;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.PDA;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
import org.pgpainless.exception.MessageNotIntegrityProtectedException;
|
||||||
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.Tuple;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OpenPgpMessageInputStream extends InputStream {
|
||||||
|
|
||||||
|
protected final PDA automaton = new PDA();
|
||||||
|
protected final ConsumerOptions options;
|
||||||
|
protected final BCPGInputStream bcpgIn;
|
||||||
|
protected InputStream in;
|
||||||
|
|
||||||
|
private List<PGPSignature> signatures = new ArrayList<>();
|
||||||
|
private List<PGPOnePassSignature> onePassSignatures = new ArrayList<>();
|
||||||
|
|
||||||
|
public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options)
|
||||||
|
throws IOException, PGPException {
|
||||||
|
this.options = options;
|
||||||
|
// TODO: Use BCPGInputStream.wrap(inputStream);
|
||||||
|
if (inputStream instanceof BCPGInputStream) {
|
||||||
|
this.bcpgIn = (BCPGInputStream) inputStream;
|
||||||
|
} else {
|
||||||
|
this.bcpgIn = new BCPGInputStream(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
walk();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void walk() throws IOException, PGPException {
|
||||||
|
loop: while (true) {
|
||||||
|
|
||||||
|
int tag = bcpgIn.nextPacketTag();
|
||||||
|
if (tag == -1) {
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag);
|
||||||
|
switch (nextPacket) {
|
||||||
|
case LIT:
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
PGPLiteralData literalData = new PGPLiteralData(bcpgIn);
|
||||||
|
in = literalData.getDataStream();
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case COMP:
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
PGPCompressedData compressedData = new PGPCompressedData(bcpgIn);
|
||||||
|
in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options);
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case OPS:
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
readOnePassSignatures();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SIG:
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
readSignatures();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PKESK:
|
||||||
|
case SKESK:
|
||||||
|
case SED:
|
||||||
|
case SEIPD:
|
||||||
|
automaton.next(InputAlphabet.EncryptedData);
|
||||||
|
PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn);
|
||||||
|
|
||||||
|
// TODO: Replace with !encDataList.isIntegrityProtected()
|
||||||
|
if (!encDataList.get(0).isIntegrityProtected()) {
|
||||||
|
throw new MessageNotIntegrityProtectedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedESKs esks = new SortedESKs(encDataList);
|
||||||
|
|
||||||
|
// TODO: try session keys
|
||||||
|
|
||||||
|
// Try passwords
|
||||||
|
for (PGPPBEEncryptedData skesk : esks.skesks) {
|
||||||
|
for (Passphrase passphrase : options.getDecryptionPassphrases()) {
|
||||||
|
PBEDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||||
|
.getPBEDataDecryptorFactory(passphrase);
|
||||||
|
try {
|
||||||
|
InputStream decrypted = skesk.getDataStream(decryptorFactory);
|
||||||
|
in = new OpenPgpMessageInputStream(decrypted, options);
|
||||||
|
break loop;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
// password mismatch? Try next password
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try (known) secret keys
|
||||||
|
for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) {
|
||||||
|
long keyId = pkesk.getKeyID();
|
||||||
|
PGPSecretKeyRing decryptionKeys = getDecryptionKey(keyId);
|
||||||
|
if (decryptionKeys == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeys);
|
||||||
|
PGPSecretKey decryptionKey = decryptionKeys.getSecretKey(keyId);
|
||||||
|
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, protector);
|
||||||
|
|
||||||
|
PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||||
|
.getPublicKeyDataDecryptorFactory(privateKey);
|
||||||
|
try {
|
||||||
|
InputStream decrypted = pkesk.getDataStream(decryptorFactory);
|
||||||
|
in = new OpenPgpMessageInputStream(decrypted, options);
|
||||||
|
break loop;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
// hm :/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try anonymous secret keys
|
||||||
|
for (PGPPublicKeyEncryptedData pkesk : esks.anonPkesks) {
|
||||||
|
for (Tuple<PGPSecretKeyRing, PGPSecretKey> decryptionKeyCandidate : findPotentialDecryptionKeys(pkesk)) {
|
||||||
|
SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeyCandidate.getA());
|
||||||
|
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKeyCandidate.getB(), protector);
|
||||||
|
PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance()
|
||||||
|
.getPublicKeyDataDecryptorFactory(privateKey);
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream decrypted = pkesk.getDataStream(decryptorFactory);
|
||||||
|
in = new OpenPgpMessageInputStream(decrypted, options);
|
||||||
|
break loop;
|
||||||
|
} catch (PGPException e) {
|
||||||
|
// hm :/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: try interactive password callbacks
|
||||||
|
|
||||||
|
break loop;
|
||||||
|
|
||||||
|
case MARKER:
|
||||||
|
bcpgIn.readPacket(); // skip marker packet
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SK:
|
||||||
|
case PK:
|
||||||
|
case SSK:
|
||||||
|
case PSK:
|
||||||
|
case TRUST:
|
||||||
|
case UID:
|
||||||
|
case UATTR:
|
||||||
|
|
||||||
|
case MOD:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXP_1:
|
||||||
|
case EXP_2:
|
||||||
|
case EXP_3:
|
||||||
|
case EXP_4:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Tuple<PGPSecretKeyRing, PGPSecretKey>> findPotentialDecryptionKeys(PGPPublicKeyEncryptedData pkesk) {
|
||||||
|
int algorithm = pkesk.getAlgorithm();
|
||||||
|
List<Tuple<PGPSecretKeyRing, PGPSecretKey>> decryptionKeyCandidates = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
||||||
|
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
for (PGPPublicKey publicKey : info.getEncryptionSubkeys(EncryptionPurpose.ANY)) {
|
||||||
|
if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) {
|
||||||
|
PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID());
|
||||||
|
decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decryptionKeyCandidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGPSecretKeyRing getDecryptionKey(long keyID) {
|
||||||
|
for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) {
|
||||||
|
PGPSecretKey decryptionKey = secretKeys.getSecretKey(keyID);
|
||||||
|
if (decryptionKey == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return secretKeys;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readOnePassSignatures() throws IOException {
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
|
||||||
|
int tag = bcpgIn.nextPacketTag();
|
||||||
|
while (tag == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) {
|
||||||
|
Packet packet = bcpgIn.readPacket();
|
||||||
|
if (tag == PacketTags.ONE_PASS_SIGNATURE) {
|
||||||
|
OnePassSignaturePacket sigPacket = (OnePassSignaturePacket) packet;
|
||||||
|
sigPacket.encode(bcpgOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bcpgOut.close();
|
||||||
|
|
||||||
|
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(buf.toByteArray());
|
||||||
|
PGPOnePassSignatureList signatureList = (PGPOnePassSignatureList) objectFactory.nextObject();
|
||||||
|
for (PGPOnePassSignature ops : signatureList) {
|
||||||
|
onePassSignatures.add(ops);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readSignatures() throws IOException {
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
BCPGOutputStream bcpgOut = new BCPGOutputStream(buf);
|
||||||
|
int tag = bcpgIn.nextPacketTag();
|
||||||
|
while (tag == PacketTags.SIGNATURE || tag == PacketTags.MARKER) {
|
||||||
|
Packet packet = bcpgIn.readPacket();
|
||||||
|
if (tag == PacketTags.SIGNATURE) {
|
||||||
|
SignaturePacket sigPacket = (SignaturePacket) packet;
|
||||||
|
sigPacket.encode(bcpgOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bcpgOut.close();
|
||||||
|
|
||||||
|
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(buf.toByteArray());
|
||||||
|
PGPSignatureList signatureList = (PGPSignatureList) objectFactory.nextObject();
|
||||||
|
for (PGPSignature signature : signatureList) {
|
||||||
|
signatures.add(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
int r = -1;
|
||||||
|
try {
|
||||||
|
r = in.read();
|
||||||
|
} catch (IOException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
if (r == -1) {
|
||||||
|
if (in instanceof OpenPgpMessageInputStream) {
|
||||||
|
in.close();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
walk();
|
||||||
|
} catch (PGPException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
// Nested streams (except LiteralData) need to be closed.
|
||||||
|
if (automaton.getState() != PDA.State.LiteralMessage) {
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
automaton.assertValid();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SortedESKs {
|
||||||
|
|
||||||
|
private List<PGPPBEEncryptedData> skesks = new ArrayList<>();
|
||||||
|
private List<PGPPublicKeyEncryptedData> pkesks = new ArrayList<>();
|
||||||
|
private List<PGPPublicKeyEncryptedData> anonPkesks = new ArrayList<>();
|
||||||
|
|
||||||
|
SortedESKs(PGPEncryptedDataList esks) {
|
||||||
|
for (PGPEncryptedData esk : esks) {
|
||||||
|
if (esk instanceof PGPPBEEncryptedData) {
|
||||||
|
skesks.add((PGPPBEEncryptedData) esk);
|
||||||
|
} else if (esk instanceof PGPPublicKeyEncryptedData) {
|
||||||
|
PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk;
|
||||||
|
if (pkesk.getKeyID() != 0) {
|
||||||
|
pkesks.add(pkesk);
|
||||||
|
} else {
|
||||||
|
anonPkesks.add(pkesk);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown ESK class type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPCompressedData;
|
||||||
|
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
||||||
|
import org.bouncycastle.openpgp.PGPLiteralData;
|
||||||
|
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
|
|
||||||
|
public enum InputAlphabet {
|
||||||
|
/**
|
||||||
|
* A {@link PGPLiteralData} packet.
|
||||||
|
*/
|
||||||
|
LiteralData,
|
||||||
|
/**
|
||||||
|
* A {@link PGPSignatureList} object.
|
||||||
|
*/
|
||||||
|
Signatures,
|
||||||
|
/**
|
||||||
|
* A {@link PGPOnePassSignatureList} object.
|
||||||
|
*/
|
||||||
|
OnePassSignatures,
|
||||||
|
/**
|
||||||
|
* A {@link PGPCompressedData} packet.
|
||||||
|
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
|
||||||
|
* its nested packet sequence.
|
||||||
|
*/
|
||||||
|
CompressedData,
|
||||||
|
/**
|
||||||
|
* A {@link PGPEncryptedDataList} object.
|
||||||
|
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
|
||||||
|
* (possibly Integrity Protected) Data packet.
|
||||||
|
*/
|
||||||
|
EncryptedData,
|
||||||
|
/**
|
||||||
|
* Marks the end of a (sub-) sequence.
|
||||||
|
* This input is given if the end of an OpenPGP message is reached.
|
||||||
|
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
|
||||||
|
* (e.g. the end of a Compressed Data packet).
|
||||||
|
*/
|
||||||
|
EndOfSequence
|
||||||
|
}
|
|
@ -1,17 +1,12 @@
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData;
|
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedDataList;
|
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData;
|
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import static org.pgpainless.decryption_verification.PushdownAutomaton.StackAlphabet.msg;
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.msg;
|
||||||
import static org.pgpainless.decryption_verification.PushdownAutomaton.StackAlphabet.ops;
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.ops;
|
||||||
import static org.pgpainless.decryption_verification.PushdownAutomaton.StackAlphabet.terminus;
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.terminus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pushdown Automaton to verify the correct syntax of OpenPGP messages during decryption.
|
* Pushdown Automaton to verify the correct syntax of OpenPGP messages during decryption.
|
||||||
|
@ -37,71 +32,18 @@ import static org.pgpainless.decryption_verification.PushdownAutomaton.StackAlph
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">RFC4880 §11.3. OpenPGP Messages</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">RFC4880 §11.3. OpenPGP Messages</a>
|
||||||
*/
|
*/
|
||||||
public class PushdownAutomaton {
|
public class NestingPDA {
|
||||||
|
|
||||||
public enum InputAlphabet {
|
|
||||||
/**
|
|
||||||
* A {@link PGPLiteralData} packet.
|
|
||||||
*/
|
|
||||||
LiteralData,
|
|
||||||
/**
|
|
||||||
* A {@link PGPSignatureList} object.
|
|
||||||
*/
|
|
||||||
Signatures,
|
|
||||||
/**
|
|
||||||
* A {@link PGPOnePassSignatureList} object.
|
|
||||||
*/
|
|
||||||
OnePassSignatures,
|
|
||||||
/**
|
|
||||||
* A {@link PGPCompressedData} packet.
|
|
||||||
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
|
|
||||||
* its nested packet sequence.
|
|
||||||
*/
|
|
||||||
CompressedData,
|
|
||||||
/**
|
|
||||||
* A {@link PGPEncryptedDataList} object.
|
|
||||||
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
|
|
||||||
* (possibly Integrity Protected) Data packet.
|
|
||||||
*/
|
|
||||||
EncryptedData,
|
|
||||||
/**
|
|
||||||
* Marks the end of a (sub-) sequence.
|
|
||||||
* This input is given if the end of an OpenPGP message is reached.
|
|
||||||
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
|
|
||||||
* (e.g. the end of a Compressed Data packet).
|
|
||||||
*/
|
|
||||||
EndOfSequence
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum StackAlphabet {
|
|
||||||
/**
|
|
||||||
* OpenPGP Message.
|
|
||||||
*/
|
|
||||||
msg,
|
|
||||||
/**
|
|
||||||
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
|
|
||||||
*/
|
|
||||||
ops,
|
|
||||||
/**
|
|
||||||
* ESK. Not used, as BC combines encrypted data with their encrypted session keys.
|
|
||||||
*/
|
|
||||||
esk,
|
|
||||||
/**
|
|
||||||
* Special symbol representing the end of the message.
|
|
||||||
*/
|
|
||||||
terminus
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of states of the automaton.
|
* Set of states of the automaton.
|
||||||
* Each state defines its valid transitions in their {@link State#transition(InputAlphabet, PushdownAutomaton)}
|
* Each state defines its valid transitions in their {@link State#transition(InputAlphabet, NestingPDA)}
|
||||||
* method.
|
* method.
|
||||||
*/
|
*/
|
||||||
public enum State {
|
public enum State {
|
||||||
|
|
||||||
OpenPgpMessage {
|
OpenPgpMessage {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
StackAlphabet stackItem = automaton.popStack();
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
if (stackItem != msg) {
|
if (stackItem != msg) {
|
||||||
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
@ -135,7 +77,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
LiteralMessage {
|
LiteralMessage {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
StackAlphabet stackItem = automaton.popStack();
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
switch (input) {
|
switch (input) {
|
||||||
|
|
||||||
|
@ -165,7 +107,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
CompressedMessage {
|
CompressedMessage {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
StackAlphabet stackItem = automaton.popStack();
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
switch (input) {
|
switch (input) {
|
||||||
case Signatures:
|
case Signatures:
|
||||||
|
@ -194,7 +136,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
EncryptedMessage {
|
EncryptedMessage {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
StackAlphabet stackItem = automaton.popStack();
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
switch (input) {
|
switch (input) {
|
||||||
case Signatures:
|
case Signatures:
|
||||||
|
@ -223,7 +165,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
CorrespondingSignature {
|
CorrespondingSignature {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
StackAlphabet stackItem = automaton.popStack();
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
if (stackItem == terminus && input == InputAlphabet.EndOfSequence && automaton.stack.isEmpty()) {
|
if (stackItem == terminus && input == InputAlphabet.EndOfSequence && automaton.stack.isEmpty()) {
|
||||||
return Valid;
|
return Valid;
|
||||||
|
@ -235,7 +177,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
Valid {
|
Valid {
|
||||||
@Override
|
@Override
|
||||||
State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException {
|
State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
throw new MalformedOpenPgpMessageException(this, input, null);
|
throw new MalformedOpenPgpMessageException(this, input, null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -252,15 +194,15 @@ public class PushdownAutomaton {
|
||||||
* @return new state of the automaton
|
* @return new state of the automaton
|
||||||
* @throws MalformedOpenPgpMessageException in case of an illegal input symbol
|
* @throws MalformedOpenPgpMessageException in case of an illegal input symbol
|
||||||
*/
|
*/
|
||||||
abstract State transition(InputAlphabet input, PushdownAutomaton automaton) throws MalformedOpenPgpMessageException;
|
abstract State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Stack<StackAlphabet> stack = new Stack<>();
|
private final Stack<StackAlphabet> stack = new Stack<>();
|
||||||
private State state;
|
private State state;
|
||||||
// Some OpenPGP packets have nested contents (e.g. compressed / encrypted data).
|
// Some OpenPGP packets have nested contents (e.g. compressed / encrypted data).
|
||||||
PushdownAutomaton nestedSequence = null;
|
NestingPDA nestedSequence = null;
|
||||||
|
|
||||||
public PushdownAutomaton() {
|
public NestingPDA() {
|
||||||
state = State.OpenPgpMessage;
|
state = State.OpenPgpMessage;
|
||||||
stack.push(terminus);
|
stack.push(terminus);
|
||||||
stack.push(msg);
|
stack.push(msg);
|
||||||
|
@ -301,7 +243,7 @@ public class PushdownAutomaton {
|
||||||
|
|
||||||
// If the processed packet contains nested sequence, open nested automaton for it
|
// If the processed packet contains nested sequence, open nested automaton for it
|
||||||
if (input == InputAlphabet.CompressedData || input == InputAlphabet.EncryptedData) {
|
if (input == InputAlphabet.CompressedData || input == InputAlphabet.EncryptedData) {
|
||||||
nestedSequence = new PushdownAutomaton();
|
nestedSequence = new NestingPDA();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.msg;
|
||||||
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.ops;
|
||||||
|
import static org.pgpainless.decryption_verification.automaton.StackAlphabet.terminus;
|
||||||
|
|
||||||
|
public class PDA {
|
||||||
|
/**
|
||||||
|
* Set of states of the automaton.
|
||||||
|
* Each state defines its valid transitions in their {@link NestingPDA.State#transition(InputAlphabet, NestingPDA)}
|
||||||
|
* method.
|
||||||
|
*/
|
||||||
|
public enum State {
|
||||||
|
|
||||||
|
OpenPgpMessage {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
|
if (stackItem != msg) {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
switch (input) {
|
||||||
|
|
||||||
|
case LiteralData:
|
||||||
|
return LiteralMessage;
|
||||||
|
|
||||||
|
case Signatures:
|
||||||
|
automaton.pushStack(msg);
|
||||||
|
return OpenPgpMessage;
|
||||||
|
|
||||||
|
case OnePassSignatures:
|
||||||
|
automaton.pushStack(ops);
|
||||||
|
automaton.pushStack(msg);
|
||||||
|
return OpenPgpMessage;
|
||||||
|
|
||||||
|
case CompressedData:
|
||||||
|
return CompressedMessage;
|
||||||
|
|
||||||
|
case EncryptedData:
|
||||||
|
return EncryptedMessage;
|
||||||
|
|
||||||
|
case EndOfSequence:
|
||||||
|
default:
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
LiteralMessage {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
|
switch (input) {
|
||||||
|
|
||||||
|
case Signatures:
|
||||||
|
if (stackItem == ops) {
|
||||||
|
return CorrespondingSignature;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EndOfSequence:
|
||||||
|
if (stackItem == terminus && automaton.stack.isEmpty()) {
|
||||||
|
return Valid;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case LiteralData:
|
||||||
|
case OnePassSignatures:
|
||||||
|
case CompressedData:
|
||||||
|
case EncryptedData:
|
||||||
|
default:
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
CompressedMessage {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
|
switch (input) {
|
||||||
|
case Signatures:
|
||||||
|
if (stackItem == ops) {
|
||||||
|
return CorrespondingSignature;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EndOfSequence:
|
||||||
|
if (stackItem == terminus && automaton.stack.isEmpty()) {
|
||||||
|
return Valid;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case LiteralData:
|
||||||
|
case OnePassSignatures:
|
||||||
|
case CompressedData:
|
||||||
|
case EncryptedData:
|
||||||
|
default:
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
EncryptedMessage {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
|
switch (input) {
|
||||||
|
case Signatures:
|
||||||
|
if (stackItem == ops) {
|
||||||
|
return CorrespondingSignature;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case EndOfSequence:
|
||||||
|
if (stackItem == terminus && automaton.stack.isEmpty()) {
|
||||||
|
return Valid;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
case LiteralData:
|
||||||
|
case OnePassSignatures:
|
||||||
|
case CompressedData:
|
||||||
|
case EncryptedData:
|
||||||
|
default:
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
CorrespondingSignature {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
StackAlphabet stackItem = automaton.popStack();
|
||||||
|
if (stackItem == terminus && input == InputAlphabet.EndOfSequence && automaton.stack.isEmpty()) {
|
||||||
|
return Valid;
|
||||||
|
} else {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, stackItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Valid {
|
||||||
|
@Override
|
||||||
|
State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException {
|
||||||
|
throw new MalformedOpenPgpMessageException(this, input, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop the automatons stack and transition to another state.
|
||||||
|
* If no valid transition from the current state is available given the popped stack item and input symbol,
|
||||||
|
* a {@link MalformedOpenPgpMessageException} is thrown.
|
||||||
|
* Otherwise, the stack is manipulated according to the valid transition and the new state is returned.
|
||||||
|
*
|
||||||
|
* @param input input symbol
|
||||||
|
* @param automaton automaton
|
||||||
|
* @return new state of the automaton
|
||||||
|
* @throws MalformedOpenPgpMessageException in case of an illegal input symbol
|
||||||
|
*/
|
||||||
|
abstract State transition(InputAlphabet input, PDA automaton) throws MalformedOpenPgpMessageException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Stack<StackAlphabet> stack = new Stack<>();
|
||||||
|
private State state;
|
||||||
|
|
||||||
|
public PDA() {
|
||||||
|
state = State.OpenPgpMessage;
|
||||||
|
stack.push(terminus);
|
||||||
|
stack.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void next(InputAlphabet input) throws MalformedOpenPgpMessageException {
|
||||||
|
State old = state;
|
||||||
|
StackAlphabet stackItem = stack.isEmpty() ? null : stack.peek();
|
||||||
|
state = state.transition(input, this);
|
||||||
|
System.out.println("Transition from " + old + " to " + state + " via " + input + " with stack " + stackItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current state of the PDA.
|
||||||
|
*
|
||||||
|
* @return state
|
||||||
|
*/
|
||||||
|
public State getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true, if the PDA is in a valid state (the OpenPGP message is valid).
|
||||||
|
*
|
||||||
|
* @return true if valid, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return getState() == State.Valid && stack.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertValid() throws MalformedOpenPgpMessageException {
|
||||||
|
if (!isValid()) {
|
||||||
|
throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop an item from the stack.
|
||||||
|
*
|
||||||
|
* @return stack item
|
||||||
|
*/
|
||||||
|
private StackAlphabet popStack() {
|
||||||
|
return stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an item onto the stack.
|
||||||
|
*
|
||||||
|
* @param item item
|
||||||
|
*/
|
||||||
|
private void pushStack(StackAlphabet item) {
|
||||||
|
stack.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "State: " + state + " Stack: " + stack;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
|
public enum StackAlphabet {
|
||||||
|
/**
|
||||||
|
* OpenPGP Message.
|
||||||
|
*/
|
||||||
|
msg,
|
||||||
|
/**
|
||||||
|
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
|
||||||
|
*/
|
||||||
|
ops,
|
||||||
|
/**
|
||||||
|
* ESK. Not used, as BC combines encrypted data with their encrypted session keys.
|
||||||
|
*/
|
||||||
|
esk,
|
||||||
|
/**
|
||||||
|
* Special symbol representing the end of the message.
|
||||||
|
*/
|
||||||
|
terminus
|
||||||
|
}
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
package org.pgpainless.exception;
|
package org.pgpainless.exception;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.pgpainless.decryption_verification.automaton.InputAlphabet;
|
||||||
import org.pgpainless.decryption_verification.PushdownAutomaton;
|
import org.pgpainless.decryption_verification.automaton.NestingPDA;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.PDA;
|
||||||
|
import org.pgpainless.decryption_verification.automaton.StackAlphabet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception that gets thrown if the OpenPGP message is malformed.
|
* Exception that gets thrown if the OpenPGP message is malformed.
|
||||||
|
@ -13,7 +15,7 @@ import org.pgpainless.decryption_verification.PushdownAutomaton;
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">RFC4880 §11.3. OpenPGP Messages</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-11.3">RFC4880 §11.3. OpenPGP Messages</a>
|
||||||
*/
|
*/
|
||||||
public class MalformedOpenPgpMessageException extends PGPException {
|
public class MalformedOpenPgpMessageException extends RuntimeException {
|
||||||
|
|
||||||
public MalformedOpenPgpMessageException(String message) {
|
public MalformedOpenPgpMessageException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
@ -23,20 +25,17 @@ public class MalformedOpenPgpMessageException extends PGPException {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MalformedOpenPgpMessageException(PushdownAutomaton.State state,
|
public MalformedOpenPgpMessageException(NestingPDA.State state,
|
||||||
PushdownAutomaton.InputAlphabet input,
|
InputAlphabet input,
|
||||||
PushdownAutomaton.StackAlphabet stackItem) {
|
StackAlphabet stackItem) {
|
||||||
this("Invalid input: There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack.");
|
this("Invalid input: There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public RTE toRuntimeException() {
|
public MalformedOpenPgpMessageException(PDA.State state, InputAlphabet input, StackAlphabet stackItem) {
|
||||||
return new RTE(this);
|
this("Invalid input: There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RTE extends RuntimeException {
|
public MalformedOpenPgpMessageException(String message, PDA automaton) {
|
||||||
|
super(message + automaton.toString());
|
||||||
public RTE(MalformedOpenPgpMessageException e) {
|
|
||||||
super(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1100,10 +1100,17 @@ public class KeyRingInfo {
|
||||||
|
|
||||||
List<PGPPublicKey> signingKeys = getSigningSubkeys();
|
List<PGPPublicKey> signingKeys = getSigningSubkeys();
|
||||||
for (PGPPublicKey pk : signingKeys) {
|
for (PGPPublicKey pk : signingKeys) {
|
||||||
PGPSecretKey sk = getSecretKey(pk.getKeyID());
|
return isSecretKeyAvailable(pk.getKeyID());
|
||||||
|
}
|
||||||
|
// No usable secret key found
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSecretKeyAvailable(long keyId) {
|
||||||
|
PGPSecretKey sk = getSecretKey(keyId);
|
||||||
if (sk == null) {
|
if (sk == null) {
|
||||||
// Missing secret key
|
// Missing secret key
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
S2K s2K = sk.getS2K();
|
S2K s2K = sk.getS2K();
|
||||||
// Unencrypted key
|
// Unencrypted key
|
||||||
|
@ -1114,14 +1121,11 @@ public class KeyRingInfo {
|
||||||
// Secret key on smart-card
|
// Secret key on smart-card
|
||||||
int s2kType = s2K.getType();
|
int s2kType = s2K.getType();
|
||||||
if (s2kType >= 100 && s2kType <= 110) {
|
if (s2kType >= 100 && s2kType <= 110) {
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
// protected secret key
|
// protected secret key
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// No usable secret key found
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) {
|
private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) {
|
||||||
if (getPublicKey(keyID) == null) {
|
if (getPublicKey(keyID) == null) {
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.ArmoredInputStream;
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.util.io.Streams;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP_COMP_LIT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP_LIT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.LIT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.LIT_LIT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.PASSPHRASE;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.PLAINTEXT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.SENC_LIT;
|
||||||
|
import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.SIG_LIT;
|
||||||
|
|
||||||
|
public class OpenPgpMessageInputStreamTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessLIT() throws IOException, PGPException {
|
||||||
|
String plain = process(LIT, ConsumerOptions.get());
|
||||||
|
assertEquals(PLAINTEXT, plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessLIT_LIT_fails() {
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> process(LIT_LIT, ConsumerOptions.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessCOMP_LIT() throws PGPException, IOException {
|
||||||
|
String plain = process(COMP_LIT, ConsumerOptions.get());
|
||||||
|
assertEquals(PLAINTEXT, plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessCOMP_fails() {
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> process(COMP, ConsumerOptions.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessCOMP_COMP_LIT() throws PGPException, IOException {
|
||||||
|
String plain = process(COMP_COMP_LIT, ConsumerOptions.get());
|
||||||
|
assertEquals(PLAINTEXT, plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessSIG_LIT() throws PGPException, IOException {
|
||||||
|
String plain = process(SIG_LIT, ConsumerOptions.get());
|
||||||
|
assertEquals(PLAINTEXT, plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessSENC_LIT() throws PGPException, IOException {
|
||||||
|
String plain = process(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||||
|
assertEquals(PLAINTEXT, plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String process(String armoredMessage, ConsumerOptions options) throws PGPException, IOException {
|
||||||
|
OpenPgpMessageInputStream in = get(armoredMessage, options);
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Streams.pipeAll(in, out);
|
||||||
|
in.close();
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenPgpMessageInputStream get(String armored, ConsumerOptions options) throws IOException, PGPException {
|
||||||
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8));
|
||||||
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
|
OpenPgpMessageInputStream pgpIn = new OpenPgpMessageInputStream(armorIn, options);
|
||||||
|
return pgpIn;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
|
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||||
|
@ -19,6 +21,7 @@ import org.pgpainless.encryption_signing.SigningOptions;
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
@ -33,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
public class PGPDecryptionStreamTest {
|
public class PGPDecryptionStreamTest {
|
||||||
|
|
||||||
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
public static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
"Version: PGPainless\n" +
|
"Version: PGPainless\n" +
|
||||||
"Comment: DA05 848F 37D4 68E6 F982 C889 7A70 1FC6 904D 3F4C\n" +
|
"Comment: DA05 848F 37D4 68E6 F982 C889 7A70 1FC6 904D 3F4C\n" +
|
||||||
"Comment: Alice <alice@pgpainless.org>\n" +
|
"Comment: Alice <alice@pgpainless.org>\n" +
|
||||||
|
@ -58,9 +61,10 @@ public class PGPDecryptionStreamTest {
|
||||||
"=THgv\n" +
|
"=THgv\n" +
|
||||||
"-----END PGP PRIVATE KEY BLOCK-----";
|
"-----END PGP PRIVATE KEY BLOCK-----";
|
||||||
|
|
||||||
private static final String PLAINTEXT = "Hello, World!\n";
|
public static final String PLAINTEXT = "Hello, World!\n";
|
||||||
|
public static final String PASSPHRASE = "sw0rdf1sh";
|
||||||
|
|
||||||
private static final String LIT = "" +
|
public static final String LIT = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: PGPainless\n" +
|
"Version: PGPainless\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -68,7 +72,7 @@ public class PGPDecryptionStreamTest {
|
||||||
"=WGju\n" +
|
"=WGju\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
private static final String LIT_LIT = "" +
|
public static final String LIT_LIT = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.71\n" +
|
"Version: BCPG v1.71\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -76,7 +80,7 @@ public class PGPDecryptionStreamTest {
|
||||||
"=A91Q\n" +
|
"=A91Q\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
private static final String COMP_LIT = "" +
|
public static final String COMP_LIT = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.71\n" +
|
"Version: BCPG v1.71\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -84,7 +88,7 @@ public class PGPDecryptionStreamTest {
|
||||||
"=ZYDg\n" +
|
"=ZYDg\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
private static final String COMP = "" +
|
public static final String COMP = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.71\n" +
|
"Version: BCPG v1.71\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -92,7 +96,7 @@ public class PGPDecryptionStreamTest {
|
||||||
"=MDzg\n" +
|
"=MDzg\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
private static final String COMP_COMP_LIT = "" +
|
public static final String COMP_COMP_LIT = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.71\n" +
|
"Version: BCPG v1.71\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -101,7 +105,7 @@ public class PGPDecryptionStreamTest {
|
||||||
"=K9Zl\n" +
|
"=K9Zl\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
private static final String SIG_LIT = "" +
|
public static final String SIG_LIT = "" +
|
||||||
"-----BEGIN PGP MESSAGE-----\n" +
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
"Version: BCPG v1.71\n" +
|
"Version: BCPG v1.71\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -111,6 +115,15 @@ public class PGPDecryptionStreamTest {
|
||||||
"=WKPn\n" +
|
"=WKPn\n" +
|
||||||
"-----END PGP MESSAGE-----";
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
|
public static final String SENC_LIT = "" +
|
||||||
|
"-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"jA0ECQMCuZ0qHNXWnGhg0j8Bdm1cxV65sYb7jDgb4rRMtdNpQ1dC4UpSYuk9YWS2\n" +
|
||||||
|
"DpNEijbX8b/P1UOK2kJczNDADMRegZuLEI+dNsBnJjk=\n" +
|
||||||
|
"=i4Y0\n" +
|
||||||
|
"-----END PGP MESSAGE-----";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void genLIT() throws IOException {
|
public void genLIT() throws IOException {
|
||||||
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
|
||||||
|
@ -125,7 +138,7 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processLIT() throws IOException, PGPException {
|
public void processLIT() throws IOException, PGPException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(decIn, out);
|
Streams.pipeAll(decIn, out);
|
||||||
|
@ -152,10 +165,10 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processLIT_LIT() throws IOException, PGPException {
|
public void processLIT_LIT() throws IOException, PGPException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT_LIT.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT_LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
assertThrows(MalformedOpenPgpMessageException.RTE.class, () -> Streams.pipeAll(decIn, out));
|
assertThrows(MalformedOpenPgpMessageException.class, () -> Streams.pipeAll(decIn, out));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -175,7 +188,7 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processCOMP_LIT() throws IOException, PGPException {
|
public void processCOMP_LIT() throws IOException, PGPException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_LIT.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(decIn, out);
|
Streams.pipeAll(decIn, out);
|
||||||
|
@ -198,8 +211,8 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processCOMP() throws IOException {
|
public void processCOMP() throws IOException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
assertThrows(MalformedOpenPgpMessageException.RTE.class, () -> {
|
assertThrows(MalformedOpenPgpMessageException.class, () -> {
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
Streams.drain(decIn);
|
Streams.drain(decIn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -228,7 +241,7 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processCOMP_COMP_LIT() throws PGPException, IOException {
|
public void processCOMP_COMP_LIT() throws PGPException, IOException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_COMP_LIT.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_COMP_LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(decIn, out);
|
Streams.pipeAll(decIn, out);
|
||||||
|
@ -279,7 +292,35 @@ public class PGPDecryptionStreamTest {
|
||||||
public void processSIG_LIT() throws IOException, PGPException {
|
public void processSIG_LIT() throws IOException, PGPException {
|
||||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(SIG_LIT.getBytes(StandardCharsets.UTF_8));
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(SIG_LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn);
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get());
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
Streams.pipeAll(decIn, out);
|
||||||
|
decIn.close();
|
||||||
|
|
||||||
|
System.out.println(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void genSENC_LIT() throws PGPException, IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
EncryptionStream enc = PGPainless.encryptAndOrSign()
|
||||||
|
.onOutputStream(out)
|
||||||
|
.withOptions(ProducerOptions.encrypt(EncryptionOptions.get()
|
||||||
|
.addPassphrase(Passphrase.fromPassword(PASSPHRASE)))
|
||||||
|
.overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED));
|
||||||
|
enc.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8));
|
||||||
|
enc.close();
|
||||||
|
|
||||||
|
System.out.println(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processSENC_LIT() throws IOException, PGPException {
|
||||||
|
ByteArrayInputStream bytesIn = new ByteArrayInputStream(SENC_LIT.getBytes(StandardCharsets.UTF_8));
|
||||||
|
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||||
|
MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()
|
||||||
|
.addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
Streams.pipeAll(decIn, out);
|
Streams.pipeAll(decIn, out);
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
public class PushDownAutomatonTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MSG is valid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS MSG SIG is valid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SIG MSG is valid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS COMP(MSG) SIG is valid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS ENC(COMP(COMP(MSG))) SIG is valid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOpsSignedEncryptedCompressedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MSG SIG is invalid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testLiteralPlusSigsFails() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.Signatures));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MSG MSG is invalid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.LiteralData));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS COMP(MSG MSG) SIG is invalid (two literal packets are illegal).
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOPSSignedMessageWithTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.LiteralData));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS COMP(MSG) MSG SIG is invalid.
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOPSSignedMessageWithTwoLiteralDataPacketsFails2() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.LiteralData));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OPS COMP(MSG SIG) is invalid (MSG SIG does not form valid nested message).
|
|
||||||
*
|
|
||||||
* @throws MalformedOpenPgpMessageException fail
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testCorrespondingSignaturesOfOpsSignedMessageAreLayerFurtherDownFails() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.Signatures));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty COMP is invalid.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testEmptyCompressedDataIsInvalid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
assertThrows(MalformedOpenPgpMessageException.class,
|
|
||||||
() -> automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOPSSignedEncryptedCompressedOPSSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
|
||||||
PushdownAutomaton automaton = new PushdownAutomaton();
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.CompressedData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.LiteralData);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.Signatures);
|
|
||||||
automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence);
|
|
||||||
|
|
||||||
assertTrue(automaton.isValid());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class NestingPDATest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MSG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS MSG SIG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SIG MSG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS COMP(MSG) SIG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS ENC(COMP(COMP(MSG))) SIG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOpsSignedEncryptedCompressedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.EncryptedData);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MSG SIG is invalid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testLiteralPlusSigsFails() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.Signatures));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MSG MSG is invalid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.LiteralData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS COMP(MSG MSG) SIG is invalid (two literal packets are illegal).
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOPSSignedMessageWithTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.LiteralData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS COMP(MSG) MSG SIG is invalid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOPSSignedMessageWithTwoLiteralDataPacketsFails2() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.LiteralData));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS COMP(MSG SIG) is invalid (MSG SIG does not form valid nested message).
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCorrespondingSignaturesOfOpsSignedMessageAreLayerFurtherDownFails() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.Signatures));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty COMP is invalid.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testEmptyCompressedDataIsInvalid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
assertThrows(MalformedOpenPgpMessageException.class,
|
||||||
|
() -> automaton.next(InputAlphabet.EndOfSequence));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOPSSignedEncryptedCompressedOPSSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
NestingPDA automaton = new NestingPDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.EncryptedData);
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.pgpainless.decryption_verification.automaton;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class PDATest {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MSG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
PDA automaton = new PDA();
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS MSG SIG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
PDA automaton = new PDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SIG MSG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
PDA automaton = new PDA();
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.LiteralData);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OPS COMP(MSG) SIG is valid.
|
||||||
|
*
|
||||||
|
* @throws MalformedOpenPgpMessageException fail
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException {
|
||||||
|
PDA automaton = new PDA();
|
||||||
|
automaton.next(InputAlphabet.OnePassSignatures);
|
||||||
|
automaton.next(InputAlphabet.CompressedData);
|
||||||
|
// Here would be a nested PDA for the LiteralData packet
|
||||||
|
automaton.next(InputAlphabet.Signatures);
|
||||||
|
automaton.next(InputAlphabet.EndOfSequence);
|
||||||
|
|
||||||
|
assertTrue(automaton.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue