1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-12-22 19:08:00 +01:00

Add convenience methods to MessageMetadata

This commit is contained in:
Paul Schaub 2022-11-22 15:19:39 +01:00
parent 3ae2afcfa0
commit 39f8f89fe0

View file

@ -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<EncryptedData> 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<EncryptedData> getEncryptionLayers() {
return new LayerIterator<EncryptedData>(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<SymmetricKeyAlgorithm> 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<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
return new LayerIterator<SymmetricKeyAlgorithm>(message) {
return map(getEncryptionLayers(), encryptedData -> encryptedData.algorithm);
}
public @Nonnull Iterator<CompressedData> getCompressionLayers() {
return new LayerIterator<CompressedData>(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<CompressionAlgorithm> 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<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;
}
};
return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm);
}
/**
@ -154,11 +168,7 @@ public class MessageMetadata {
* @return session key of the message
*/
public @Nullable SessionKey getSessionKey() {
Iterator<SessionKey> 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<SessionKey> getSessionKeys() {
return new LayerIterator<SessionKey>(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<SignatureVerification> 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 <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
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 <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
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 <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
*/
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<SubkeyIdentifier> iterator = new LayerIterator<SubkeyIdentifier>(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 <A,B> Iterator<B> map(Iterator<A> from, Function<A, B> mapping) {
return new Iterator<B>() {
@Override
public boolean hasNext() {
return from.hasNext();
}
@Override
public B next() {
return mapping.apply(from.next());
}
};
}
private static @Nullable <A> A firstOrNull(Iterator<A> iterator) {
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
private static @Nonnull <A> A firstOr(Iterator<A> iterator, A item) {
if (iterator.hasNext()) {
return iterator.next();
}
return item;
}
}