mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-17 18:02:05 +01:00
Wip: Introduce MessageMetadata class
This commit is contained in:
parent
efdf2bca0d
commit
5c93eb3705
5 changed files with 498 additions and 128 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -31,11 +31,11 @@ import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
|||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||
import org.bouncycastle.pqc.crypto.rainbow.Layer;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||
import org.pgpainless.algorithm.OpenPgpPacket;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.decryption_verification.automaton.InputAlphabet;
|
||||
import org.pgpainless.decryption_verification.automaton.PDA;
|
||||
|
@ -69,14 +69,14 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
private boolean closed = false;
|
||||
|
||||
private Signatures signatures;
|
||||
private LayerMetadata layerMetadata;
|
||||
private MessageMetadata.Layer metadata;
|
||||
|
||||
public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options)
|
||||
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 {
|
||||
// TODO: Use BCPGInputStream.wrap(inputStream);
|
||||
if (inputStream instanceof BCPGInputStream) {
|
||||
|
@ -86,33 +86,12 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
}
|
||||
|
||||
this.options = options;
|
||||
this.metadata = metadata;
|
||||
this.resultBuilder = OpenPgpMetadata.getBuilder();
|
||||
this.signatures = new Signatures(options);
|
||||
this.signatures.addDetachedSignatures(options.getDetachedSignatures());
|
||||
|
||||
consumePackets();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
consumePackets(); // nom nom nom
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,27 +106,21 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
*/
|
||||
private void consumePackets()
|
||||
throws IOException, PGPException {
|
||||
System.out.println("Walk " + automaton);
|
||||
int tag;
|
||||
loop: while ((tag = nextTag()) != -1) {
|
||||
OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag);
|
||||
System.out.println(nextPacket);
|
||||
switch (nextPacket) {
|
||||
|
||||
// Literal Data - the literal data content is the new input stream
|
||||
case LIT:
|
||||
automaton.next(InputAlphabet.LiteralData);
|
||||
PGPLiteralData literalData = new PGPLiteralData(bcpgIn);
|
||||
in = literalData.getDataStream();
|
||||
processLiteralData();
|
||||
break loop;
|
||||
|
||||
// Compressed Data - the content contains another OpenPGP message
|
||||
case COMP:
|
||||
automaton.next(InputAlphabet.CompressedData);
|
||||
PGPCompressedData compressedData = new PGPCompressedData(bcpgIn);
|
||||
LayerMetadata compressionLayer = new LayerMetadata();
|
||||
compressionLayer.setCompressionAlgorithm(CompressionAlgorithm.fromId(compressedData.getAlgorithm()));
|
||||
in = new OpenPgpMessageInputStream(compressedData.getDataStream(), options, compressionLayer);
|
||||
processCompressedData();
|
||||
break loop;
|
||||
|
||||
// One Pass Signatures
|
||||
|
@ -160,12 +133,7 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
case SIG:
|
||||
boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops;
|
||||
automaton.next(InputAlphabet.Signatures);
|
||||
PGPSignatureList signatureList = readSignatures();
|
||||
if (isSigForOPS) {
|
||||
signatures.addOnePassCorrespondingSignatures(signatureList);
|
||||
} else {
|
||||
signatures.addPrependedSignatures(signatureList);
|
||||
}
|
||||
processSignature(isSigForOPS);
|
||||
break;
|
||||
|
||||
// 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 {
|
||||
PGPEncryptedDataList encDataList = new PGPEncryptedDataList(bcpgIn);
|
||||
|
||||
|
@ -227,13 +218,14 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
// TODO: Replace with encDataList.addSessionKeyDecryptionMethod(sessionKey)
|
||||
PGPEncryptedData esk = esks.all().get(0);
|
||||
try {
|
||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(options.getSessionKey().getAlgorithm());
|
||||
if (esk instanceof PGPPBEEncryptedData) {
|
||||
PGPPBEEncryptedData skesk = (PGPPBEEncryptedData) esk;
|
||||
in = skesk.getDataStream(decryptorFactory);
|
||||
in = new OpenPgpMessageInputStream(skesk.getDataStream(decryptorFactory), options, encryptedData);
|
||||
return true;
|
||||
} else if (esk instanceof PGPPublicKeyEncryptedData) {
|
||||
PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) esk;
|
||||
in = pkesk.getDataStream(decryptorFactory);
|
||||
in = new OpenPgpMessageInputStream(pkesk.getDataStream(decryptorFactory), options, encryptedData);
|
||||
return true;
|
||||
} else {
|
||||
throw new RuntimeException("Unknown ESK class type: " + esk.getClass().getName());
|
||||
|
@ -250,7 +242,9 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
.getPBEDataDecryptorFactory(passphrase);
|
||||
try {
|
||||
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;
|
||||
} catch (PGPException e) {
|
||||
// password mismatch? Try next password
|
||||
|
@ -274,7 +268,9 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
.getPublicKeyDataDecryptorFactory(privateKey);
|
||||
try {
|
||||
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;
|
||||
} catch (PGPException e) {
|
||||
// hm :/
|
||||
|
@ -291,7 +287,9 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
|
||||
try {
|
||||
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;
|
||||
} catch (PGPException e) {
|
||||
// hm :/
|
||||
|
@ -402,6 +400,7 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
signatures.update(b);
|
||||
} else {
|
||||
in.close();
|
||||
collectMetadata();
|
||||
in = null;
|
||||
|
||||
try {
|
||||
|
@ -426,6 +425,7 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
int r = in.read(b, off, len);
|
||||
if (r == -1) {
|
||||
in.close();
|
||||
collectMetadata();
|
||||
in = null;
|
||||
|
||||
try {
|
||||
|
@ -447,6 +447,7 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
|
||||
if (in != null) {
|
||||
in.close();
|
||||
collectMetadata();
|
||||
in = null;
|
||||
}
|
||||
|
||||
|
@ -461,6 +462,21 @@ public class OpenPgpMessageInputStream extends InputStream {
|
|||
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 List<PGPPBEEncryptedData> skesks = new ArrayList<>();
|
||||
|
|
|
@ -187,10 +187,7 @@ public class PDA {
|
|||
}
|
||||
|
||||
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(id + ": Transition from " + old + " to " + state + " via " + input + " with stack " + stackItem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,21 @@
|
|||
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.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
|
||||
|
@ -11,9 +27,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
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.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.StreamEncoding;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
|
@ -23,17 +44,7 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
|||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
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;
|
||||
import org.pgpainless.util.Tuple;
|
||||
|
||||
public class OpenPgpMessageInputStreamTest {
|
||||
|
||||
|
@ -304,107 +315,119 @@ public class OpenPgpMessageInputStreamTest {
|
|||
System.out.println(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessLIT() throws IOException, PGPException {
|
||||
String plain = processReadBuffered(LIT, ConsumerOptions.get());
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(LIT, ConsumerOptions.get());
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
interface Processor {
|
||||
Tuple<String, MessageMetadata> process(String armoredMessage, ConsumerOptions options) throws PGPException, IOException;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessLIT_LIT_fails() {
|
||||
private static Stream<Arguments> provideMessageProcessors() {
|
||||
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,
|
||||
() -> 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,
|
||||
() -> processReadSequential(LIT_LIT, ConsumerOptions.get()));
|
||||
() -> processor.process(COMP, ConsumerOptions.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCOMP_LIT() throws PGPException, IOException {
|
||||
String plain = processReadBuffered(COMP_LIT, ConsumerOptions.get());
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(COMP_LIT, ConsumerOptions.get());
|
||||
@ParameterizedTest(name = "Process COMP(COMP(LIT)) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessCOMP_COMP_LIT(Processor processor) throws PGPException, IOException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(COMP_COMP_LIT, ConsumerOptions.get());
|
||||
String plain = result.getA();
|
||||
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
|
||||
public void testProcessCOMP_fails() {
|
||||
assertThrows(MalformedOpenPgpMessageException.class,
|
||||
() -> 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 {
|
||||
@ParameterizedTest(name = "Process SIG LIT using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessSIG_LIT(Processor processor) throws PGPException, IOException {
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(
|
||||
PGPainless.readKeyRing().secretKeyRing(KEY));
|
||||
|
||||
String plain = processReadBuffered(SIG_LIT, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(SIG_LIT, ConsumerOptions.get()
|
||||
Tuple<String, MessageMetadata> result = processor.process(SIG_LIT, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessSENC_LIT() throws PGPException, IOException {
|
||||
String plain = processReadBuffered(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||
@ParameterizedTest(name = "Process SENC(LIT) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessSENC_LIT(Processor processor) throws PGPException, IOException {
|
||||
Tuple<String, MessageMetadata> result = processor.process(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE)));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessPENC_COMP_LIT() throws IOException, PGPException {
|
||||
@ParameterizedTest(name = "Process PENC(LIT) using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessPENC_COMP_LIT(Processor processor) throws IOException, PGPException {
|
||||
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
|
||||
String plain = processReadBuffered(PENC_COMP_LIT, ConsumerOptions.get()
|
||||
.addDecryptionKey(secretKeys));
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(PENC_COMP_LIT, ConsumerOptions.get()
|
||||
Tuple<String, MessageMetadata> result = processor.process(PENC_COMP_LIT, ConsumerOptions.get()
|
||||
.addDecryptionKey(secretKeys));
|
||||
String plain = result.getA();
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessOPS_LIT_SIG() throws IOException, PGPException {
|
||||
@ParameterizedTest(name = "Process OPS LIT SIG using {0}")
|
||||
@MethodSource("provideMessageProcessors")
|
||||
public void testProcessOPS_LIT_SIG(Processor processor) throws IOException, PGPException {
|
||||
PGPPublicKeyRing cert = PGPainless.extractCertificate(PGPainless.readKeyRing().secretKeyRing(KEY));
|
||||
String plain = processReadBuffered(OPS_LIT_SIG, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
assertEquals(PLAINTEXT, plain);
|
||||
|
||||
plain = processReadSequential(OPS_LIT_SIG, ConsumerOptions.get()
|
||||
Tuple<String, MessageMetadata> result = processor.process(OPS_LIT_SIG, ConsumerOptions.get()
|
||||
.addVerificationCert(cert));
|
||||
String plain = result.getA();
|
||||
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);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Streams.pipeAll(in, out);
|
||||
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);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
|
@ -414,10 +437,11 @@ public class OpenPgpMessageInputStreamTest {
|
|||
}
|
||||
|
||||
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));
|
||||
ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn);
|
||||
OpenPgpMessageInputStream pgpIn = new OpenPgpMessageInputStream(armorIn, options);
|
||||
|
|
Loading…
Reference in a new issue