diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java index 26439b40..1bf22b47 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java @@ -19,6 +19,9 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +/** + * View for extracting metadata about a {@link Message}. + */ public class MessageMetadata { protected Message message; @@ -27,6 +30,57 @@ public class MessageMetadata { this.message = message; } + /** + * Convert this {@link MessageMetadata} object into a legacy {@link OpenPgpMetadata} object. + * This method is intended to be used for a transition period between the 1.3 / 1.4+ branches. + * TODO: Remove in 1.5.X + * + * @return converted {@link OpenPgpMetadata} object + */ + public @Nonnull OpenPgpMetadata toLegacyMetadata() { + OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); + resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm()); + Date modDate = getModificationDate(); + if (modDate != null) { + resultBuilder.setModificationDate(modDate); + } + String fileName = getFilename(); + if (fileName != null) { + resultBuilder.setFileName(fileName); + } + StreamEncoding encoding = getFormat(); + if (encoding != null) { + resultBuilder.setFileEncoding(encoding); + } + resultBuilder.setSessionKey(getSessionKey()); + resultBuilder.setDecryptionKey(getDecryptionKey()); + + for (SignatureVerification accepted : getVerifiedDetachedSignatures()) { + resultBuilder.addVerifiedDetachedSignature(accepted); + } + for (SignatureVerification.Failure rejected : getRejectedDetachedSignatures()) { + resultBuilder.addInvalidDetachedSignature(rejected.getSignatureVerification(), rejected.getValidationException()); + } + + for (SignatureVerification accepted : getVerifiedInlineSignatures()) { + resultBuilder.addVerifiedInbandSignature(accepted); + } + for (SignatureVerification.Failure rejected : getRejectedInlineSignatures()) { + resultBuilder.addInvalidInbandSignature(rejected.getSignatureVerification(), rejected.getValidationException()); + } + if (message.isCleartextSigned()) { + resultBuilder.setCleartextSigned(); + } + + return resultBuilder.build(); + } + + /** + * Return the {@link SymmetricKeyAlgorithm} of the outermost encrypted data packet, or null if message is + * unencrypted. + * + * @return encryption algorithm + */ public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() { Iterator algorithms = getEncryptionAlgorithms(); if (algorithms.hasNext()) { @@ -35,6 +89,14 @@ public class MessageMetadata { return null; } + /** + * Return an {@link Iterator} of {@link SymmetricKeyAlgorithm SymmetricKeyAlgorithms} encountered in the message. + * The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item + * that of the next nested encrypted data packet and so on. + * The iterator might also be empty, in case of an unencrypted message. + * + * @return iterator of symmetric encryption algorithms + */ public @Nonnull Iterator getEncryptionAlgorithms() { return new LayerIterator(message) { @Override @@ -49,6 +111,12 @@ public class MessageMetadata { }; } + /** + * Return the {@link CompressionAlgorithm} of the outermost compressed data packet, or null, if the message + * does not contain any compressed data packets. + * + * @return compression algorithm + */ public @Nullable CompressionAlgorithm getCompressionAlgorithm() { Iterator algorithms = getCompressionAlgorithms(); if (algorithms.hasNext()) { @@ -57,6 +125,14 @@ public class MessageMetadata { return null; } + /** + * Return an {@link Iterator} of {@link CompressionAlgorithm CompressionAlgorithms} encountered in the message. + * The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next + * item that of the next nested compressed data packet and so on. + * The iterator might also be empty, in case of a message without any compressed data packets. + * + * @return iterator of compression algorithms + */ public @Nonnull Iterator getCompressionAlgorithms() { return new LayerIterator(message) { @Override @@ -71,6 +147,12 @@ public class MessageMetadata { }; } + /** + * Return the {@link SessionKey} of the outermost encrypted data packet. + * If the message was unencrypted, this method returns
null
. + * + * @return session key of the message + */ public @Nullable SessionKey getSessionKey() { Iterator sessionKeys = getSessionKeys(); if (sessionKeys.hasNext()) { @@ -79,6 +161,14 @@ public class MessageMetadata { return null; } + /** + * Return an {@link Iterator} of {@link SessionKey SessionKeys} for all encrypted data packets in the message. + * The first item returned by the iterator is the session key of the outermost encrypted data packet, + * the next item that of the next nested encrypted data packet and so on. + * The iterator might also be empty, in case of an unencrypted message. + * + * @return iterator of session keys + */ public @Nonnull Iterator getSessionKeys() { return new LayerIterator(message) { @Override @@ -93,14 +183,31 @@ public class MessageMetadata { }; } - public List getVerifiedDetachedSignatures() { - return new ArrayList<>(message.verifiedDetachedSignatures); + /** + * Return a list of all verified detached signatures. + * This list contains all acceptable, correct detached signatures. + * + * @return verified detached signatures + */ + public @Nonnull List getVerifiedDetachedSignatures() { + return message.getVerifiedDetachedSignatures(); } - public List getRejectedDetachedSignatures() { - return new ArrayList<>(message.rejectedDetachedSignatures); + /** + * Return a list of all rejected detached signatures. + * + * @return rejected detached signatures + */ + public @Nonnull List getRejectedDetachedSignatures() { + return message.getRejectedDetachedSignatures(); } + /** + * Return a list of all verified inline-signatures. + * This list contains all acceptable, correct signatures that were part of the message itself. + * + * @return verified inline signatures + */ public @Nonnull List getVerifiedInlineSignatures() { List verifications = new ArrayList<>(); Iterator> verificationsByLayer = getVerifiedInlineSignaturesByLayer(); @@ -110,6 +217,16 @@ public class MessageMetadata { return verifications; } + /** + * Return an {@link Iterator} of {@link List Lists} of verified inline-signatures of the message. + * Since signatures might occur in different layers within a message, this method can be used to gain more detailed + * insights into what signatures were encountered at what layers of the message structure. + * Each item of the {@link Iterator} represents a layer of the message and contains only signatures from + * this layer. + * An empty list means no (or no acceptable) signatures were encountered in that layer. + * + * @return iterator of lists of signatures by-layer. + */ public @Nonnull Iterator> getVerifiedInlineSignaturesByLayer() { return new LayerIterator>(message) { @Override @@ -132,6 +249,11 @@ public class MessageMetadata { }; } + /** + * Return a list of all rejected inline-signatures of the message. + * + * @return list of rejected inline-signatures + */ public @Nonnull List getRejectedInlineSignatures() { List rejected = new ArrayList<>(); Iterator> rejectedByLayer = getRejectedInlineSignaturesByLayer(); @@ -141,6 +263,12 @@ public class MessageMetadata { return rejected; } + /** + * Similar to {@link #getVerifiedInlineSignaturesByLayer()}, this method returns all rejected inline-signatures + * of the message, but organized by layer. + * + * @return rejected inline-signatures by-layer + */ public @Nonnull Iterator> getRejectedInlineSignaturesByLayer() { return new LayerIterator>(message) { @Override @@ -163,7 +291,16 @@ public class MessageMetadata { }; } - public String getFilename() { + /** + * Return the value of the literal data packet's filename field. + * This value can be used to store a decrypted file under its original filename, + * but since this field is not necessarily part of the signed data of a message, usage of this field is + * discouraged. + * + * @return filename + * @see RFC4880 §5.9. Literal Data Packet + */ + public @Nullable String getFilename() { LiteralData literalData = findLiteralData(); if (literalData == null) { return null; @@ -171,7 +308,15 @@ public class MessageMetadata { return literalData.getFileName(); } - public Date getModificationDate() { + /** + * Return the value of the literal data packets modification date field. + * This value can be used to restore the modification date of a decrypted file, + * but since this field is not necessarily part of the signed data, its use is discouraged. + * + * @return modification date + * @see RFC4880 §5.9. Literal Data Packet + */ + public @Nullable Date getModificationDate() { LiteralData literalData = findLiteralData(); if (literalData == null) { return null; @@ -179,7 +324,15 @@ public class MessageMetadata { return literalData.getModificationDate(); } - public StreamEncoding getFormat() { + /** + * Return the value of the format field of the literal data packet. + * This value indicates what format (text, binary data, ...) the data has. + * Since this field is not necessarily part of the signed data of a message, its usage is discouraged. + * + * @return format + * @see RFC4880 §5.9. Literal Data Packet + */ + public @Nullable StreamEncoding getFormat() { LiteralData literalData = findLiteralData(); if (literalData == null) { return null; @@ -187,19 +340,33 @@ public class MessageMetadata { return literalData.getFormat(); } - private LiteralData findLiteralData() { - Nested nested = message.child; + /** + * Find the {@link LiteralData} layer of an OpenPGP message. + * Usually, every message has a literal data packet, but for malformed messages this method might still + * return
null
. + * + * @return literal data + */ + private @Nullable LiteralData findLiteralData() { + Nested nested = message.getChild(); if (nested == null) { return null; } - while (nested.hasNestedChild()) { + while (nested != null && nested.hasNestedChild()) { Layer layer = (Layer) nested; - nested = layer.child; + nested = layer.getChild(); } return (LiteralData) nested; } + /** + * Return the {@link SubkeyIdentifier} of the decryption key that was used to decrypt the outermost encryption + * layer. + * If the message was unencrypted, this might return
null
. + * + * @return decryption key + */ public SubkeyIdentifier getDecryptionKey() { Iterator iterator = new LayerIterator(message) { @Override @@ -236,58 +403,130 @@ public class MessageMetadata { } } - public Nested getChild() { + /** + * Return the nested child element of this layer. + * Might return
null
, if this layer does not have a child element + * (e.g. if this is a {@link LiteralData} packet). + * + * @return child element + */ + public @Nullable Nested getChild() { return child; } - public void setChild(Nested child) { + /** + * Set the nested child element for this layer. + * + * @param child child element + */ + void setChild(Nested child) { this.child = child; } + /** + * Return a list of all verified detached signatures of this layer. + * + * @return all verified detached signatures of this layer + */ public List getVerifiedDetachedSignatures() { return new ArrayList<>(verifiedDetachedSignatures); } + /** + * Return a list of all rejected detached signatures of this layer. + * + * @return all rejected detached signatures of this layer + */ public List getRejectedDetachedSignatures() { return new ArrayList<>(rejectedDetachedSignatures); } + /** + * Add a verified detached signature for this layer. + * + * @param signatureVerification verified detached signature + */ void addVerifiedDetachedSignature(SignatureVerification signatureVerification) { verifiedDetachedSignatures.add(signatureVerification); } + /** + * Add a rejected detached signature for this layer. + * + * @param failure rejected detached signature + */ void addRejectedDetachedSignature(SignatureVerification.Failure failure) { rejectedDetachedSignatures.add(failure); } + /** + * Return a list of all verified one-pass-signatures of this layer. + * + * @return all verified one-pass-signatures of this layer + */ public List getVerifiedOnePassSignatures() { return new ArrayList<>(verifiedOnePassSignatures); } + /** + * Return a list of all rejected one-pass-signatures of this layer. + * + * @return all rejected one-pass-signatures of this layer + */ public List getRejectedOnePassSignatures() { return new ArrayList<>(rejectedOnePassSignatures); } + /** + * Add a verified one-pass-signature for this layer. + * + * @param verifiedOnePassSignature verified one-pass-signature for this layer + */ void addVerifiedOnePassSignature(SignatureVerification verifiedOnePassSignature) { this.verifiedOnePassSignatures.add(verifiedOnePassSignature); } + /** + * Add a rejected one-pass-signature for this layer. + * + * @param rejected rejected one-pass-signature for this layer + */ void addRejectedOnePassSignature(SignatureVerification.Failure rejected) { this.rejectedOnePassSignatures.add(rejected); } + /** + * Return a list of all verified prepended signatures of this layer. + * + * @return all verified prepended signatures of this layer + */ public List getVerifiedPrependedSignatures() { return new ArrayList<>(verifiedPrependedSignatures); } + /** + * Return a list of all rejected prepended signatures of this layer. + * + * @return all rejected prepended signatures of this layer + */ public List getRejectedPrependedSignatures() { return new ArrayList<>(rejectedPrependedSignatures); } + /** + * Add a verified prepended signature for this layer. + * + * @param verified verified prepended signature + */ void addVerifiedPrependedSignature(SignatureVerification verified) { this.verifiedPrependedSignatures.add(verified); } + /** + * Add a rejected prepended signature for this layer. + * + * @param rejected rejected prepended signature + */ void addRejectedPrependedSignature(SignatureVerification.Failure rejected) { this.rejectedPrependedSignatures.add(rejected); } @@ -306,6 +545,12 @@ public class MessageMetadata { super(0); } + /** + * Returns true, is the message is a signed message using the cleartext signature framework. + * + * @return
true
if message is cleartext-signed,
false
otherwise + * @see RFC4880 §7. Cleartext Signature Framework + */ public boolean isCleartextSigned() { return cleartextSigned; } @@ -321,26 +566,46 @@ public class MessageMetadata { this("", new Date(0L), StreamEncoding.BINARY); } - public LiteralData(String fileName, Date modificationDate, StreamEncoding format) { + public LiteralData(@Nonnull String fileName, + @Nonnull Date modificationDate, + @Nonnull StreamEncoding format) { this.fileName = fileName; this.modificationDate = modificationDate; this.format = format; } - public String getFileName() { + /** + * Return the value of the filename field. + * An empty String
""
indicates no filename. + * + * @return filename + */ + public @Nonnull String getFileName() { return fileName; } - public Date getModificationDate() { + /** + * Return the value of the modification date field. + * A special date
{@code new Date(0L)}
indicates no modification date. + * + * @return modification date + */ + public @Nonnull Date getModificationDate() { return modificationDate; } - public StreamEncoding getFormat() { + /** + * Return the value of the format field. + * + * @return format + */ + public @Nonnull StreamEncoding getFormat() { return format; } @Override public boolean hasNestedChild() { + // A literal data packet MUST NOT have a child element, as its content is the plaintext return false; } } @@ -348,17 +613,22 @@ public class MessageMetadata { public static class CompressedData extends Layer implements Nested { protected final CompressionAlgorithm algorithm; - public CompressedData(CompressionAlgorithm zip, int depth) { + public CompressedData(@Nonnull CompressionAlgorithm zip, int depth) { super(depth); this.algorithm = zip; } - public CompressionAlgorithm getAlgorithm() { + /** + * Return the {@link CompressionAlgorithm} used to compress the packet. + * @return compression algorithm + */ + public @Nonnull CompressionAlgorithm getAlgorithm() { return algorithm; } @Override public boolean hasNestedChild() { + // A compressed data packet MUST have a child element return true; } } @@ -369,25 +639,40 @@ public class MessageMetadata { protected SessionKey sessionKey; protected List recipients; - public EncryptedData(SymmetricKeyAlgorithm algorithm, int depth) { + public EncryptedData(@Nonnull SymmetricKeyAlgorithm algorithm, int depth) { super(depth); this.algorithm = algorithm; } - public SymmetricKeyAlgorithm getAlgorithm() { + /** + * Return the {@link SymmetricKeyAlgorithm} used to encrypt the packet. + * @return symmetric encryption algorithm + */ + public @Nonnull SymmetricKeyAlgorithm getAlgorithm() { return algorithm; } - public SessionKey getSessionKey() { + /** + * Return the {@link SessionKey} used to decrypt the packet. + * + * @return session key + */ + public @Nonnull SessionKey getSessionKey() { return sessionKey; } - public List getRecipients() { + /** + * Return a list of all recipient key ids to which the packet was encrypted for. + * + * @return recipients + */ + public @Nonnull List getRecipients() { return new ArrayList<>(recipients); } @Override public boolean hasNestedChild() { + // An encrypted data packet MUST have a child element return true; } } @@ -398,10 +683,10 @@ public class MessageMetadata { Layer last = null; Message parent; - LayerIterator(Message message) { + LayerIterator(@Nonnull Message message) { super(); this.parent = message; - this.current = message.child; + this.current = message.getChild(); if (matches(current)) { last = (Layer) current; } @@ -437,8 +722,8 @@ public class MessageMetadata { } private void findNext() { - while (current instanceof Layer) { - current = ((Layer) current).child; + while (current != null && current instanceof Layer) { + current = ((Layer) current).getChild(); if (matches(current)) { last = (Layer) current; break; @@ -459,33 +744,4 @@ public class MessageMetadata { abstract O getProperty(Layer last); } - - public OpenPgpMetadata toLegacyMetadata() { - OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder(); - resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm()); - resultBuilder.setModificationDate(getModificationDate()); - resultBuilder.setFileName(getFilename()); - resultBuilder.setFileEncoding(getFormat()); - resultBuilder.setSessionKey(getSessionKey()); - resultBuilder.setDecryptionKey(getDecryptionKey()); - - for (SignatureVerification accepted : getVerifiedDetachedSignatures()) { - resultBuilder.addVerifiedDetachedSignature(accepted); - } - for (SignatureVerification.Failure rejected : getRejectedDetachedSignatures()) { - resultBuilder.addInvalidDetachedSignature(rejected.getSignatureVerification(), rejected.getValidationException()); - } - - for (SignatureVerification accepted : getVerifiedInlineSignatures()) { - resultBuilder.addVerifiedInbandSignature(accepted); - } - for (SignatureVerification.Failure rejected : getRejectedInlineSignatures()) { - resultBuilder.addInvalidInbandSignature(rejected.getSignatureVerification(), rejected.getValidationException()); - } - if (message.isCleartextSigned()) { - resultBuilder.setCleartextSigned(); - } - - return resultBuilder.build(); - } }