From 39f8f89fe0bb117f91655ef21440042b7f36a8f1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 22 Nov 2022 15:19:39 +0100 Subject: [PATCH] Add convenience methods to MessageMetadata --- .../MessageMetadata.java | 221 +++++++++++------- 1 file changed, 139 insertions(+), 82 deletions(-) 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 3af2a4d3..5318fcb1 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 @@ -4,6 +4,18 @@ package org.pgpainless.decryption_verification; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPPublicKey; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; @@ -11,14 +23,6 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException; import org.pgpainless.key.SubkeyIdentifier; 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; - /** * View for extracting metadata about a {@link Message}. */ @@ -40,18 +44,9 @@ public class MessageMetadata { 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.setModificationDate(getModificationDate()); + resultBuilder.setFileName(getFilename()); + resultBuilder.setFileEncoding(getLiteralDataEncoding()); resultBuilder.setSessionKey(getSessionKey()); resultBuilder.setDecryptionKey(getDecryptionKey()); @@ -75,6 +70,39 @@ public class MessageMetadata { return resultBuilder.build(); } + public boolean isEncrypted() { + SymmetricKeyAlgorithm algorithm = getEncryptionAlgorithm(); + return algorithm != null && algorithm != SymmetricKeyAlgorithm.NULL; + } + + public boolean isEncryptedFor(@Nonnull PGPKeyRing keys) { + Iterator encryptionLayers = getEncryptionLayers(); + while (encryptionLayers.hasNext()) { + EncryptedData encryptedData = encryptionLayers.next(); + for (long recipient : encryptedData.getRecipients()) { + PGPPublicKey key = keys.getPublicKey(recipient); + if (key != null) { + return true; + } + } + } + return false; + } + + public @Nonnull Iterator getEncryptionLayers() { + return new LayerIterator(message) { + @Override + public boolean matches(Nested layer) { + return layer instanceof EncryptedData; + } + + @Override + public EncryptedData getProperty(Layer last) { + return (EncryptedData) last; + } + }; + } + /** * Return the {@link SymmetricKeyAlgorithm} of the outermost encrypted data packet, or null if message is * unencrypted. @@ -82,11 +110,7 @@ public class MessageMetadata { * @return encryption algorithm */ public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() { - Iterator algorithms = getEncryptionAlgorithms(); - if (algorithms.hasNext()) { - return algorithms.next(); - } - return null; + return firstOrNull(getEncryptionAlgorithms()); } /** @@ -98,15 +122,19 @@ public class MessageMetadata { * @return iterator of symmetric encryption algorithms */ public @Nonnull Iterator getEncryptionAlgorithms() { - return new LayerIterator(message) { + return map(getEncryptionLayers(), encryptedData -> encryptedData.algorithm); + } + + public @Nonnull Iterator getCompressionLayers() { + return new LayerIterator(message) { @Override - public boolean matches(Nested layer) { - return layer instanceof EncryptedData; + boolean matches(Layer layer) { + return layer instanceof CompressedData; } @Override - public SymmetricKeyAlgorithm getProperty(Layer last) { - return ((EncryptedData) last).algorithm; + CompressedData getProperty(Layer last) { + return (CompressedData) last; } }; } @@ -118,11 +146,7 @@ public class MessageMetadata { * @return compression algorithm */ public @Nullable CompressionAlgorithm getCompressionAlgorithm() { - Iterator algorithms = getCompressionAlgorithms(); - if (algorithms.hasNext()) { - return algorithms.next(); - } - return null; + return firstOrNull(getCompressionAlgorithms()); } /** @@ -134,17 +158,7 @@ public class MessageMetadata { * @return iterator of compression algorithms */ public @Nonnull Iterator getCompressionAlgorithms() { - return new LayerIterator(message) { - @Override - public boolean matches(Nested layer) { - return layer instanceof CompressedData; - } - - @Override - public CompressionAlgorithm getProperty(Layer last) { - return ((CompressedData) last).algorithm; - } - }; + return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm); } /** @@ -154,11 +168,7 @@ public class MessageMetadata { * @return session key of the message */ public @Nullable SessionKey getSessionKey() { - Iterator sessionKeys = getSessionKeys(); - if (sessionKeys.hasNext()) { - return sessionKeys.next(); - } - return null; + return firstOrNull(getSessionKeys()); } /** @@ -170,17 +180,15 @@ public class MessageMetadata { * @return iterator of session keys */ public @Nonnull Iterator getSessionKeys() { - return new LayerIterator(message) { - @Override - boolean matches(Nested layer) { - return layer instanceof EncryptedData; - } + return map(getEncryptionLayers(), encryptedData -> encryptedData.sessionKey); + } - @Override - SessionKey getProperty(Layer last) { - return ((EncryptedData) last).getSessionKey(); - } - }; + public boolean isVerifiedSignedBy(@Nonnull PGPKeyRing keys) { + return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys); + } + + public boolean isVerifiedDetachedSignedBy(@Nonnull PGPKeyRing keys) { + return containsSignatureBy(getVerifiedDetachedSignatures(), keys); } /** @@ -202,6 +210,10 @@ public class MessageMetadata { return message.getRejectedDetachedSignatures(); } + public boolean isVerifiedInlineSignedBy(@Nonnull PGPKeyRing keys) { + return containsSignatureBy(getVerifiedInlineSignatures(), keys); + } + /** * Return a list of all verified inline-signatures. * This list contains all acceptable, correct signatures that were part of the message itself. @@ -291,6 +303,28 @@ public class MessageMetadata { }; } + private static boolean containsSignatureBy(@Nonnull List verifications, + @Nonnull PGPKeyRing keys) { + for (SignatureVerification verification : verifications) { + SubkeyIdentifier issuer = verification.getSigningKey(); + if (issuer == null) { + // No issuer, shouldn't happen, but better be safe and skip... + continue; + } + + if (keys.getPublicKey().getKeyID() != issuer.getPrimaryKeyId()) { + // Wrong cert + continue; + } + + if (keys.getPublicKey(issuer.getSubkeyId()) != null) { + // Matching cert and signing key + return true; + } + } + return false; + } + /** * 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, @@ -300,14 +334,23 @@ public class MessageMetadata { * @return filename * @see RFC4880 §5.9. Literal Data Packet */ - public @Nullable String getFilename() { + public @Nonnull String getFilename() { LiteralData literalData = findLiteralData(); if (literalData == null) { - return null; + throw new NoSuchElementException("No Literal Data Packet found."); } return literalData.getFileName(); } + /** + * Returns true, if the filename of the literal data packet indicates that the data is intended for your eyes only. + * + * @return isForYourEyesOnly + */ + public boolean isForYourEyesOnly() { + return PGPLiteralData.CONSOLE.equals(getFilename()); + } + /** * 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, @@ -316,10 +359,10 @@ public class MessageMetadata { * @return modification date * @see RFC4880 §5.9. Literal Data Packet */ - public @Nullable Date getModificationDate() { + public @Nonnull Date getModificationDate() { LiteralData literalData = findLiteralData(); if (literalData == null) { - return null; + throw new NoSuchElementException("No Literal Data Packet found."); } return literalData.getModificationDate(); } @@ -332,10 +375,10 @@ public class MessageMetadata { * @return format * @see RFC4880 §5.9. Literal Data Packet */ - public @Nullable StreamEncoding getFormat() { + public @Nonnull StreamEncoding getLiteralDataEncoding() { LiteralData literalData = findLiteralData(); if (literalData == null) { - return null; + throw new NoSuchElementException("No Literal Data Packet found."); } return literalData.getFormat(); } @@ -368,21 +411,7 @@ public class MessageMetadata { * @return decryption key */ public SubkeyIdentifier getDecryptionKey() { - Iterator iterator = new LayerIterator(message) { - @Override - public boolean matches(Nested layer) { - return layer instanceof EncryptedData; - } - - @Override - public SubkeyIdentifier getProperty(Layer last) { - return ((EncryptedData) last).decryptionKey; - } - }; - if (iterator.hasNext()) { - return iterator.next(); - } - return null; + return firstOrNull(map(getEncryptionLayers(), encryptedData -> encryptedData.decryptionKey)); } public abstract static class Layer { @@ -744,4 +773,32 @@ public class MessageMetadata { abstract O getProperty(Layer last); } + + private static Iterator map(Iterator from, Function mapping) { + return new Iterator() { + @Override + public boolean hasNext() { + return from.hasNext(); + } + + @Override + public B next() { + return mapping.apply(from.next()); + } + }; + } + + private static @Nullable A firstOrNull(Iterator iterator) { + if (iterator.hasNext()) { + return iterator.next(); + } + return null; + } + + private static @Nonnull A firstOr(Iterator iterator, A item) { + if (iterator.hasNext()) { + return iterator.next(); + } + return item; + } }