1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-14 00:12:06 +01:00

Wip: Introduce MessageMetadata class

This commit is contained in:
Paul Schaub 2022-09-26 18:21:06 +02:00
parent efdf2bca0d
commit 5c93eb3705
5 changed files with 498 additions and 128 deletions

View file

@ -0,0 +1,249 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.SessionKey;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MessageMetadata {
protected Message message;
public MessageMetadata(@Nonnull Message message) {
this.message = message;
}
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
Iterator<SymmetricKeyAlgorithm> algorithms = getEncryptionAlgorithms();
if (algorithms.hasNext()) {
return algorithms.next();
}
return null;
}
public @Nonnull Iterator<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
return new LayerIterator<SymmetricKeyAlgorithm>(message) {
@Override
public boolean matches(Nested layer) {
return layer instanceof EncryptedData;
}
@Override
public SymmetricKeyAlgorithm getProperty(Layer last) {
return ((EncryptedData) last).algorithm;
}
};
}
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
Iterator<CompressionAlgorithm> algorithms = getCompressionAlgorithms();
if (algorithms.hasNext()) {
return algorithms.next();
}
return null;
}
public @Nonnull Iterator<CompressionAlgorithm> getCompressionAlgorithms() {
return new LayerIterator<CompressionAlgorithm>(message) {
@Override
public boolean matches(Nested layer) {
return layer instanceof CompressedData;
}
@Override
public CompressionAlgorithm getProperty(Layer last) {
return ((CompressedData) last).algorithm;
}
};
}
public String getFilename() {
return findLiteralData().getFileName();
}
public Date getModificationDate() {
return findLiteralData().getModificationDate();
}
public StreamEncoding getFormat() {
return findLiteralData().getFormat();
}
private LiteralData findLiteralData() {
Nested nested = message.child;
while (nested.hasNestedChild()) {
Layer layer = (Layer) nested;
nested = layer.child;
}
return (LiteralData) nested;
}
public static abstract class Layer {
protected final List<SignatureVerification> verifiedSignatures = new ArrayList<>();
protected final List<SignatureVerification.Failure> failedSignatures = new ArrayList<>();
protected Nested child;
public Nested getChild() {
return child;
}
public void setChild(Nested child) {
this.child = child;
}
public List<SignatureVerification> getVerifiedSignatures() {
return new ArrayList<>(verifiedSignatures);
}
public List<SignatureVerification.Failure> getFailedSignatures() {
return new ArrayList<>(failedSignatures);
}
}
public interface Nested {
boolean hasNestedChild();
}
public static class Message extends Layer {
}
public static class LiteralData implements Nested {
protected String fileName;
protected Date modificationDate;
protected StreamEncoding format;
public LiteralData() {
this("", new Date(0L), StreamEncoding.BINARY);
}
public LiteralData(String fileName, Date modificationDate, StreamEncoding format) {
this.fileName = fileName;
this.modificationDate = modificationDate;
this.format = format;
}
public String getFileName() {
return fileName;
}
public Date getModificationDate() {
return modificationDate;
}
public StreamEncoding getFormat() {
return format;
}
@Override
public boolean hasNestedChild() {
return false;
}
}
public static class CompressedData extends Layer implements Nested {
protected final CompressionAlgorithm algorithm;
public CompressedData(CompressionAlgorithm zip) {
this.algorithm = zip;
}
public CompressionAlgorithm getAlgorithm() {
return algorithm;
}
@Override
public boolean hasNestedChild() {
return true;
}
}
public static class EncryptedData extends Layer implements Nested {
protected final SymmetricKeyAlgorithm algorithm;
protected SessionKey sessionKey;
protected List<Long> recipients;
public EncryptedData(SymmetricKeyAlgorithm algorithm) {
this.algorithm = algorithm;
}
public SymmetricKeyAlgorithm getAlgorithm() {
return algorithm;
}
public SessionKey getSessionKey() {
return sessionKey;
}
public List<Long> getRecipients() {
return new ArrayList<>(recipients);
}
@Override
public boolean hasNestedChild() {
return true;
}
}
private static abstract class LayerIterator<O> implements Iterator<O> {
private Nested current;
Layer last = null;
public LayerIterator(Message message) {
super();
this.current = message.child;
if (matches(current)) {
last = (Layer) current;
}
}
@Override
public boolean hasNext() {
if (last == null) {
findNext();
}
return last != null;
}
@Override
public O next() {
if (last == null) {
findNext();
}
if (last != null) {
O property = getProperty(last);
last = null;
return property;
}
throw new NoSuchElementException();
}
private void findNext() {
while (current instanceof Layer) {
current = ((Layer) current).child;
if (matches(current)) {
last = (Layer) current;
break;
}
}
}
abstract boolean matches(Nested layer);
abstract O getProperty(Layer last);
}
}

View file

@ -31,11 +31,11 @@ import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.pqc.crypto.rainbow.Layer;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.EncryptionPurpose; import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.OpenPgpPacket; import org.pgpainless.algorithm.OpenPgpPacket;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.automaton.InputAlphabet; import org.pgpainless.decryption_verification.automaton.InputAlphabet;
import org.pgpainless.decryption_verification.automaton.PDA; import org.pgpainless.decryption_verification.automaton.PDA;
@ -69,14 +69,14 @@ public class OpenPgpMessageInputStream extends InputStream {
private boolean closed = false; private boolean closed = false;
private Signatures signatures; private Signatures signatures;
private LayerMetadata layerMetadata; private MessageMetadata.Layer metadata;
public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options) public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options)
throws IOException, PGPException { throws IOException, PGPException {
this(inputStream, options, null); this(inputStream, options, new MessageMetadata.Message());
} }
OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options, LayerMetadata layerMetadata) OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options, MessageMetadata.Layer metadata)
throws PGPException, IOException { throws PGPException, IOException {
// TODO: Use BCPGInputStream.wrap(inputStream); // TODO: Use BCPGInputStream.wrap(inputStream);
if (inputStream instanceof BCPGInputStream) { if (inputStream instanceof BCPGInputStream) {
@ -86,33 +86,12 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
this.options = options; this.options = options;
this.metadata = metadata;
this.resultBuilder = OpenPgpMetadata.getBuilder(); this.resultBuilder = OpenPgpMetadata.getBuilder();
this.signatures = new Signatures(options); this.signatures = new Signatures(options);
this.signatures.addDetachedSignatures(options.getDetachedSignatures()); this.signatures.addDetachedSignatures(options.getDetachedSignatures());
consumePackets(); consumePackets(); // nom nom nom
}
static class LayerMetadata {
private CompressionAlgorithm compressionAlgorithm;
private SymmetricKeyAlgorithm symmetricKeyAlgorithm;
private LayerMetadata child;
public LayerMetadata setCompressionAlgorithm(CompressionAlgorithm algorithm) {
this.compressionAlgorithm = algorithm;
return this;
}
public LayerMetadata setSymmetricEncryptionAlgorithm(SymmetricKeyAlgorithm algorithm) {
this.symmetricKeyAlgorithm = algorithm;
return this;
}
public LayerMetadata setChild(LayerMetadata child) {
this.child = child;
return this;
}
} }
/** /**
@ -127,27 +106,21 @@ public class OpenPgpMessageInputStream extends InputStream {
*/ */
private void consumePackets() private void consumePackets()
throws IOException, PGPException { throws IOException, PGPException {
System.out.println("Walk " + automaton);
int tag; int tag;
loop: while ((tag = nextTag()) != -1) { loop: while ((tag = nextTag()) != -1) {
OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag); OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag);
System.out.println(nextPacket);
switch (nextPacket) { switch (nextPacket) {
// Literal Data - the literal data content is the new input stream // Literal Data - the literal data content is the new input stream
case LIT: case LIT:
automaton.next(InputAlphabet.LiteralData); automaton.next(InputAlphabet.LiteralData);
PGPLiteralData literalData = new PGPLiteralData(bcpgIn); processLiteralData();
in = literalData.getDataStream();
break loop; break loop;
// Compressed Data - the content contains another OpenPGP message // Compressed Data - the content contains another OpenPGP message
case COMP: case COMP:
automaton.next(InputAlphabet.CompressedData); automaton.next(InputAlphabet.CompressedData);
PGPCompressedData compressedData = new PGPCompressedData(bcpgIn); processCompressedData();
LayerMetadata compressionLayer = new LayerMetadata();
compressionLayer.setCompressionAlgorithm(CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer);
break loop; break loop;
// One Pass Signatures // One Pass Signatures
@ -160,12 +133,7 @@ public class OpenPgpMessageInputStream extends InputStream {
case SIG: case SIG:
boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops; boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops;
automaton.next(InputAlphabet.Signatures); automaton.next(InputAlphabet.Signatures);
PGPSignatureList signatureList = readSignatures(); processSignature(isSigForOPS);
if (isSigForOPS) {
signatures.addOnePassCorrespondingSignatures(signatureList);
} else {
signatures.addPrependedSignatures(signatureList);
}
break; break;
// Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC) // Encrypted Data (ESKs and SED/SEIPD are parsed the same by BC)
@ -210,6 +178,29 @@ public class OpenPgpMessageInputStream extends InputStream {
} }
} }
private void processSignature(boolean isSigForOPS) throws IOException {
PGPSignatureList signatureList = readSignatures();
if (isSigForOPS) {
signatures.addOnePassCorrespondingSignatures(signatureList);
} else {
signatures.addPrependedSignatures(signatureList);
}
}
private void processCompressedData() throws IOException, PGPException {
PGPCompressedData compressedData = new PGPCompressedData(bcpgIn);
MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData(
CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer);
}
private void processLiteralData() throws IOException {
PGPLiteralData literalData = new PGPLiteralData(bcpgIn);
this.metadata.setChild(new MessageMetadata.LiteralData(literalData.getFileName(), literalData.getModificationTime(),
StreamEncoding.requireFromCode(literalData.getFormat())));
in = literalData.getDataStream();
}
private boolean processEncryptedData() throws IOException, PGPException { private boolean processEncryptedData() throws IOException, PGPException {
PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn); PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn);
@ -227,13 +218,14 @@ public class OpenPgpMessageInputStream extends InputStream {
// TODO: Replace with encDataList.addSessionKeyDecryptionMethod(sessionKey) // TODO: Replace with encDataList.addSessionKeyDecryptionMethod(sessionKey)
PGPEncryptedData esk = esks.all().get(0); PGPEncryptedData esk = esks.all().get(0);
try { try {
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(options.getSessionKey().getAlgorithm());
if (esk instanceof PGPPBEEncryptedData) { if (esk instanceof PGPPBEEncryptedData) {
PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk; PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk;
in = skesk.getDataStream(decryptorFactory); in = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData);
return true; return true;
} else if (esk instanceof PGPPublicKeyEncryptedData) { } else if (esk instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk; PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk;
in = pkesk.getDataStream(decryptorFactory); in = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData);
return true; return true;
} else { } else {
throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName()); throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName());
@ -250,7 +242,9 @@ public class OpenPgpMessageInputStream extends InputStream {
.getPBEDataDecryptorFactory(passphrase); .getPBEDataDecryptorFactory(passphrase);
try { try {
InputStream decrypted = skesk.getDataStream(decryptorFactory); InputStream decrypted = skesk.getDataStream(decryptorFactory);
in = new OpenPgpMessageInputStream(decrypted, options); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(skesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// password mismatch? Try next password // password mismatch? Try next password
@ -274,7 +268,9 @@ public class OpenPgpMessageInputStream extends InputStream {
.getPublicKeyDataDecryptorFactory(privateKey); .getPublicKeyDataDecryptorFactory(privateKey);
try { try {
InputStream decrypted = pkesk.getDataStream(decryptorFactory); InputStream decrypted = pkesk.getDataStream(decryptorFactory);
in = new OpenPgpMessageInputStream(decrypted, options); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// hm :/ // hm :/
@ -291,7 +287,9 @@ public class OpenPgpMessageInputStream extends InputStream {
try { try {
InputStream decrypted = pkesk.getDataStream(decryptorFactory); InputStream decrypted = pkesk.getDataStream(decryptorFactory);
in = new OpenPgpMessageInputStream(decrypted, options); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)));
in = new OpenPgpMessageInputStream(decrypted, options, encryptedData);
return true; return true;
} catch (PGPException e) { } catch (PGPException e) {
// hm :/ // hm :/
@ -402,6 +400,7 @@ public class OpenPgpMessageInputStream extends InputStream {
signatures.update(b); signatures.update(b);
} else { } else {
in.close(); in.close();
collectMetadata();
in = null; in = null;
try { try {
@ -426,6 +425,7 @@ public class OpenPgpMessageInputStream extends InputStream {
int r = in.read(b, off, len); int r = in.read(b, off, len);
if (r == -1) { if (r == -1) {
in.close(); in.close();
collectMetadata();
in = null; in = null;
try { try {
@ -447,6 +447,7 @@ public class OpenPgpMessageInputStream extends InputStream {
if (in != null) { if (in != null) {
in.close(); in.close();
collectMetadata();
in = null; in = null;
} }
@ -461,6 +462,21 @@ public class OpenPgpMessageInputStream extends InputStream {
closed = true; closed = true;
} }
private void collectMetadata() {
if (in instanceof OpenPgpMessageInputStream) {
OpenPgpMessageInputStream child = (OpenPgpMessageInputStream) in;
MessageMetadata.Layer childLayer = child.metadata;
this.metadata.setChild((MessageMetadata.Nested) childLayer);
}
}
public MessageMetadata getMetadata() {
if (!closed) {
throw new IllegalStateException("Stream must be closed before access to metadata can be granted.");
}
return new MessageMetadata((MessageMetadata.Message) metadata);
}
private static class SortedESKs { private static class SortedESKs {
private List<PGPPBEEncryptedData> skesks = new ArrayList<>(); private List<PGPPBEEncryptedData> skesks = new ArrayList<>();

View file

@ -187,10 +187,7 @@ public class PDA {
} }
public void next(InputAlphabet input) throws MalformedOpenPgpMessageException { public void next(InputAlphabet input) throws MalformedOpenPgpMessageException {
State old = state;
StackAlphabet stackItem = stack.isEmpty() ? null : stack.peek();
state = state.transition(input, this); state = state.transition(input, this);
System.out.println(id + ": Transition from " + old + " to " + state + " via " + input + " with stack " + stackItem);
} }
/** /**

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.junit.JUtils;
import org.junit.jupiter.api.Test;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.DateUtil;
import java.util.Date;
import java.util.Iterator;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MessageMetadataTest {
@Test
public void processTestMessage_COMP_ENC_ENC_LIT() {
// Note: COMP of ENC does not make sense, since ENC is indistinguishable from randomness
// and randomness cannot be encrypted.
// For the sake of testing though, this is okay.
MessageMetadata.Message message = new MessageMetadata.Message();
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP);
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128);
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256);
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
message.setChild(compressedData);
compressedData.setChild(encryptedData);
encryptedData.setChild(encryptedData1);
encryptedData1.setChild(literalData);
MessageMetadata metadata = new MessageMetadata(message);
// Check encryption algs
assertEquals(SymmetricKeyAlgorithm.AES_128, metadata.getEncryptionAlgorithm(), "getEncryptionAlgorithm() returns alg of outermost EncryptedData");
Iterator<SymmetricKeyAlgorithm> encryptionAlgs = metadata.getEncryptionAlgorithms();
assertTrue(encryptionAlgs.hasNext(), "There is at least one EncryptedData child");
assertTrue(encryptionAlgs.hasNext(), "The child is still there");
assertEquals(SymmetricKeyAlgorithm.AES_128, encryptionAlgs.next(), "The first algo is AES128");
assertTrue(encryptionAlgs.hasNext(), "There is another EncryptedData");
assertTrue(encryptionAlgs.hasNext(), "There is *still* another EncryptedData");
assertEquals(SymmetricKeyAlgorithm.AES_256, encryptionAlgs.next(), "The second algo is AES256");
assertFalse(encryptionAlgs.hasNext(), "There is no more EncryptedData");
assertFalse(encryptionAlgs.hasNext(), "There *still* is no more EncryptedData");
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm(), "getCompressionAlgorithm() returns alg of outermost CompressedData");
Iterator<CompressionAlgorithm> compAlgs = metadata.getCompressionAlgorithms();
assertTrue(compAlgs.hasNext());
assertTrue(compAlgs.hasNext());
assertEquals(CompressionAlgorithm.ZIP, compAlgs.next());
assertFalse(compAlgs.hasNext());
assertFalse(compAlgs.hasNext());
assertEquals("", metadata.getFilename());
JUtils.assertDateEquals(new Date(0L), metadata.getModificationDate());
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
}
@Test
public void testProcessLiteralDataMessage() {
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData(
"collateral_murder.zip",
DateUtil.parseUTCDate("2010-04-05 10:12:03 UTC"),
StreamEncoding.BINARY);
MessageMetadata.Message message = new MessageMetadata.Message();
message.setChild(literalData);
MessageMetadata metadata = new MessageMetadata(message);
assertNull(metadata.getCompressionAlgorithm());
assertNull(metadata.getEncryptionAlgorithm());
assertEquals("collateral_murder.zip", metadata.getFilename());
assertEquals(DateUtil.parseUTCDate("2010-04-05 10:12:03 UTC"), metadata.getModificationDate());
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
}
}

View file

@ -1,5 +1,21 @@
package org.pgpainless.decryption_verification; package org.pgpainless.decryption_verification;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.Iterator;
import java.util.stream.Stream;
import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.CompressionAlgorithmTags;
@ -11,9 +27,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature; 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.JUtils;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.encryption_signing.EncryptionOptions; 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;
@ -23,17 +44,7 @@ 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 org.pgpainless.util.Passphrase;
import org.pgpainless.util.Tuple;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class OpenPgpMessageInputStreamTest { public class OpenPgpMessageInputStreamTest {
@ -304,107 +315,119 @@ public class OpenPgpMessageInputStreamTest {
System.out.println(out); System.out.println(out);
} }
@Test interface Processor {
public void testProcessLIT() throws IOException, PGPException { Tuple<String, MessageMetadata> process(String armoredMessage, ConsumerOptions options) throws PGPException, IOException;
String plain = processReadBuffered(LIT, ConsumerOptions.get());
assertEquals(PLAINTEXT, plain);
plain = processReadSequential(LIT, ConsumerOptions.get());
assertEquals(PLAINTEXT, plain);
} }
@Test private static Stream<Arguments> provideMessageProcessors() {
public void testProcessLIT_LIT_fails() { return Stream.of(
Arguments.of(Named.of("read(buf,off,len)", (Processor) OpenPgpMessageInputStreamTest::processReadBuffered)),
Arguments.of(Named.of("read()", (Processor) OpenPgpMessageInputStreamTest::processReadSequential)));
}
@ParameterizedTest(name = "Process LIT using {0}")
@MethodSource("provideMessageProcessors")
public void testProcessLIT(Processor processor) throws IOException, PGPException {
Tuple<String, MessageMetadata> result = processor.process(LIT, ConsumerOptions.get());
String plain = result.getA();
assertEquals(PLAINTEXT, plain);
MessageMetadata metadata = result.getB();
assertNull(metadata.getCompressionAlgorithm());
assertNull(metadata.getEncryptionAlgorithm());
assertEquals("", metadata.getFilename());
JUtils.assertDateEquals(new Date(0L), metadata.getModificationDate());
assertEquals(StreamEncoding.BINARY, metadata.getFormat());
}
@ParameterizedTest(name = "Process LIT LIT using {0}")
@MethodSource("provideMessageProcessors")
public void testProcessLIT_LIT_fails(Processor processor) {
assertThrows(MalformedOpenPgpMessageException.class, assertThrows(MalformedOpenPgpMessageException.class,
() -> processReadBuffered(LIT_LIT, ConsumerOptions.get())); () -> processor.process(LIT_LIT, ConsumerOptions.get()));
}
@ParameterizedTest(name = "Process COMP(LIT) using {0}")
@MethodSource("provideMessageProcessors")
public void testProcessCOMP_LIT(Processor processor) throws PGPException, IOException {
Tuple<String, MessageMetadata> result = processor.process(COMP_LIT, ConsumerOptions.get());
String plain = result.getA();
assertEquals(PLAINTEXT, plain);
MessageMetadata metadata = result.getB();
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm());
}
@ParameterizedTest(name = "Process COMP using {0}")
@MethodSource("provideMessageProcessors")
public void testProcessCOMP_fails(Processor processor) {
assertThrows(MalformedOpenPgpMessageException.class, assertThrows(MalformedOpenPgpMessageException.class,
() -> processReadSequential(LIT_LIT, ConsumerOptions.get())); () -> processor.process(COMP, ConsumerOptions.get()));
} }
@Test @ParameterizedTest(name = "Process COMP(COMP(LIT)) using {0}")
public void testProcessCOMP_LIT() throws PGPException, IOException { @MethodSource("provideMessageProcessors")
String plain = processReadBuffered(COMP_LIT, ConsumerOptions.get()); public void testProcessCOMP_COMP_LIT(Processor processor) throws PGPException, IOException {
assertEquals(PLAINTEXT, plain); Tuple<String, MessageMetadata> result = processor.process(COMP_COMP_LIT, ConsumerOptions.get());
String plain = result.getA();
plain = processReadSequential(COMP_LIT, ConsumerOptions.get());
assertEquals(PLAINTEXT, plain); assertEquals(PLAINTEXT, plain);
MessageMetadata metadata = result.getB();
assertEquals(CompressionAlgorithm.ZIP, metadata.getCompressionAlgorithm());
Iterator<CompressionAlgorithm> compressionAlgorithms = metadata.getCompressionAlgorithms();
assertEquals(CompressionAlgorithm.ZIP, compressionAlgorithms.next());
assertEquals(CompressionAlgorithm.BZIP2, compressionAlgorithms.next());
assertFalse(compressionAlgorithms.hasNext());
} }
@Test @ParameterizedTest(name = "Process SIG LIT using {0}")
public void testProcessCOMP_fails() { @MethodSource("provideMessageProcessors")
assertThrows(MalformedOpenPgpMessageException.class, public void testProcessSIG_LIT(Processor processor) throws PGPException, IOException {
() -> processReadBuffered(COMP, ConsumerOptions.get()));
assertThrows(MalformedOpenPgpMessageException.class,
() -> processReadSequential(COMP, ConsumerOptions.get()));
}
@Test
public void testProcessCOMP_COMP_LIT() throws PGPException, IOException {
String plain = processReadBuffered(COMP_COMP_LIT, ConsumerOptions.get());
assertEquals(PLAINTEXT, plain);
plain = processReadSequential(COMP_COMP_LIT, ConsumerOptions.get());
assertEquals(PLAINTEXT, plain);
}
@Test
public void testProcessSIG_LIT() throws PGPException, IOException {
PGPPublicKeyRing cert = PGPainless.extractCertificate( PGPPublicKeyRing cert = PGPainless.extractCertificate(
PGPainless.readKeyRing().secretKeyRing(KEY)); PGPainless.readKeyRing().secretKeyRing(KEY));
String plain = processReadBuffered(SIG_LIT, ConsumerOptions.get() Tuple<String, MessageMetadata> result = processor.process(SIG_LIT, ConsumerOptions.get()
.addVerificationCert(cert));
assertEquals(PLAINTEXT, plain);
plain = processReadSequential(SIG_LIT, ConsumerOptions.get()
.addVerificationCert(cert)); .addVerificationCert(cert));
String plain = result.getA();
assertEquals(PLAINTEXT, plain); assertEquals(PLAINTEXT, plain);
} }
@Test @ParameterizedTest(name = "Process SENC(LIT) using {0}")
public void testProcessSENC_LIT() throws PGPException, IOException { @MethodSource("provideMessageProcessors")
String plain = processReadBuffered(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); public void testProcessSENC_LIT(Processor processor) throws PGPException, IOException {
assertEquals(PLAINTEXT, plain); Tuple<String, MessageMetadata> result = processor.process(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
String plain = result.getA();
plain = processReadSequential(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
assertEquals(PLAINTEXT, plain); assertEquals(PLAINTEXT, plain);
} }
@Test @ParameterizedTest(name = "Process PENC(LIT) using {0}")
public void testProcessPENC_COMP_LIT() throws IOException, PGPException { @MethodSource("provideMessageProcessors")
public void testProcessPENC_COMP_LIT(Processor processor) throws IOException, PGPException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
String plain = processReadBuffered(PENC_COMP_LIT, ConsumerOptions.get() Tuple<String, MessageMetadata> result = processor.process(PENC_COMP_LIT, ConsumerOptions.get()
.addDecryptionKey(secretKeys));
assertEquals(PLAINTEXT, plain);
plain = processReadSequential(PENC_COMP_LIT, ConsumerOptions.get()
.addDecryptionKey(secretKeys)); .addDecryptionKey(secretKeys));
String plain = result.getA();
assertEquals(PLAINTEXT, plain); assertEquals(PLAINTEXT, plain);
} }
@Test @ParameterizedTest(name = "Process OPS LIT SIG using {0}")
public void testProcessOPS_LIT_SIG() throws IOException, PGPException { @MethodSource("provideMessageProcessors")
public void testProcessOPS_LIT_SIG(Processor processor) throws IOException, PGPException {
PGPPublicKeyRing cert = PGPainless.extractCertificate(PGPainless.readKeyRing().secretKeyRing(KEY)); PGPPublicKeyRing cert = PGPainless.extractCertificate(PGPainless.readKeyRing().secretKeyRing(KEY));
String plain = processReadBuffered(OPS_LIT_SIG, ConsumerOptions.get() Tuple<String, MessageMetadata> result = processor.process(OPS_LIT_SIG, ConsumerOptions.get()
.addVerificationCert(cert));
assertEquals(PLAINTEXT, plain);
plain = processReadSequential(OPS_LIT_SIG, ConsumerOptions.get()
.addVerificationCert(cert)); .addVerificationCert(cert));
String plain = result.getA();
assertEquals(PLAINTEXT, plain); assertEquals(PLAINTEXT, plain);
} }
private String processReadBuffered(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { private static Tuple<String, MessageMetadata> processReadBuffered(String armoredMessage, ConsumerOptions options) throws PGPException, IOException {
OpenPgpMessageInputStream in = get(armoredMessage, options); OpenPgpMessageInputStream in = get(armoredMessage, options);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
Streams.pipeAll(in, out); Streams.pipeAll(in, out);
in.close(); in.close();
return out.toString(); MessageMetadata metadata = in.getMetadata();
return new Tuple<>(out.toString(), metadata);
} }
private String processReadSequential(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { private static Tuple<String, MessageMetadata> processReadSequential(String armoredMessage, ConsumerOptions options) throws PGPException, IOException {
OpenPgpMessageInputStream in = get(armoredMessage, options); OpenPgpMessageInputStream in = get(armoredMessage, options);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -414,10 +437,11 @@ public class OpenPgpMessageInputStreamTest {
} }
in.close(); in.close();
return out.toString(); MessageMetadata metadata = in.getMetadata();
return new Tuple<>(out.toString(), metadata);
} }
private OpenPgpMessageInputStream get(String armored, ConsumerOptions options) throws IOException, PGPException { private static OpenPgpMessageInputStream get(String armored, ConsumerOptions options) throws IOException, PGPException {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)); ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8));
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
OpenPgpMessageInputStream pgpIn = new OpenPgpMessageInputStream(armorIn, options); OpenPgpMessageInputStream pgpIn = new OpenPgpMessageInputStream(armorIn, options);