mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 11:27:57 +01:00
Add convenience methods to MessageMetadata
This commit is contained in:
parent
3ae2afcfa0
commit
39f8f89fe0
1 changed files with 139 additions and 82 deletions
|
@ -4,6 +4,18 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification;
|
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.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
@ -11,14 +23,6 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.util.SessionKey;
|
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}.
|
* View for extracting metadata about a {@link Message}.
|
||||||
*/
|
*/
|
||||||
|
@ -40,18 +44,9 @@ public class MessageMetadata {
|
||||||
public @Nonnull OpenPgpMetadata toLegacyMetadata() {
|
public @Nonnull OpenPgpMetadata toLegacyMetadata() {
|
||||||
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||||
resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm());
|
resultBuilder.setCompressionAlgorithm(getCompressionAlgorithm());
|
||||||
Date modDate = getModificationDate();
|
resultBuilder.setModificationDate(getModificationDate());
|
||||||
if (modDate != null) {
|
resultBuilder.setFileName(getFilename());
|
||||||
resultBuilder.setModificationDate(modDate);
|
resultBuilder.setFileEncoding(getLiteralDataEncoding());
|
||||||
}
|
|
||||||
String fileName = getFilename();
|
|
||||||
if (fileName != null) {
|
|
||||||
resultBuilder.setFileName(fileName);
|
|
||||||
}
|
|
||||||
StreamEncoding encoding = getFormat();
|
|
||||||
if (encoding != null) {
|
|
||||||
resultBuilder.setFileEncoding(encoding);
|
|
||||||
}
|
|
||||||
resultBuilder.setSessionKey(getSessionKey());
|
resultBuilder.setSessionKey(getSessionKey());
|
||||||
resultBuilder.setDecryptionKey(getDecryptionKey());
|
resultBuilder.setDecryptionKey(getDecryptionKey());
|
||||||
|
|
||||||
|
@ -75,6 +70,39 @@ public class MessageMetadata {
|
||||||
return resultBuilder.build();
|
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
|
* Return the {@link SymmetricKeyAlgorithm} of the outermost encrypted data packet, or null if message is
|
||||||
* unencrypted.
|
* unencrypted.
|
||||||
|
@ -82,11 +110,7 @@ public class MessageMetadata {
|
||||||
* @return encryption algorithm
|
* @return encryption algorithm
|
||||||
*/
|
*/
|
||||||
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
|
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
|
||||||
Iterator<SymmetricKeyAlgorithm> algorithms = getEncryptionAlgorithms();
|
return firstOrNull(getEncryptionAlgorithms());
|
||||||
if (algorithms.hasNext()) {
|
|
||||||
return algorithms.next();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,15 +122,19 @@ public class MessageMetadata {
|
||||||
* @return iterator of symmetric encryption algorithms
|
* @return iterator of symmetric encryption algorithms
|
||||||
*/
|
*/
|
||||||
public @Nonnull Iterator<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
|
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
|
@Override
|
||||||
public boolean matches(Nested layer) {
|
boolean matches(Layer layer) {
|
||||||
return layer instanceof EncryptedData;
|
return layer instanceof CompressedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SymmetricKeyAlgorithm getProperty(Layer last) {
|
CompressedData getProperty(Layer last) {
|
||||||
return ((EncryptedData) last).algorithm;
|
return (CompressedData) last;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -118,11 +146,7 @@ public class MessageMetadata {
|
||||||
* @return compression algorithm
|
* @return compression algorithm
|
||||||
*/
|
*/
|
||||||
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
|
public @Nullable CompressionAlgorithm getCompressionAlgorithm() {
|
||||||
Iterator<CompressionAlgorithm> algorithms = getCompressionAlgorithms();
|
return firstOrNull(getCompressionAlgorithms());
|
||||||
if (algorithms.hasNext()) {
|
|
||||||
return algorithms.next();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,17 +158,7 @@ public class MessageMetadata {
|
||||||
* @return iterator of compression algorithms
|
* @return iterator of compression algorithms
|
||||||
*/
|
*/
|
||||||
public @Nonnull Iterator<CompressionAlgorithm> getCompressionAlgorithms() {
|
public @Nonnull Iterator<CompressionAlgorithm> getCompressionAlgorithms() {
|
||||||
return new LayerIterator<CompressionAlgorithm>(message) {
|
return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm);
|
||||||
@Override
|
|
||||||
public boolean matches(Nested layer) {
|
|
||||||
return layer instanceof CompressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompressionAlgorithm getProperty(Layer last) {
|
|
||||||
return ((CompressedData) last).algorithm;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,11 +168,7 @@ public class MessageMetadata {
|
||||||
* @return session key of the message
|
* @return session key of the message
|
||||||
*/
|
*/
|
||||||
public @Nullable SessionKey getSessionKey() {
|
public @Nullable SessionKey getSessionKey() {
|
||||||
Iterator<SessionKey> sessionKeys = getSessionKeys();
|
return firstOrNull(getSessionKeys());
|
||||||
if (sessionKeys.hasNext()) {
|
|
||||||
return sessionKeys.next();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,17 +180,15 @@ public class MessageMetadata {
|
||||||
* @return iterator of session keys
|
* @return iterator of session keys
|
||||||
*/
|
*/
|
||||||
public @Nonnull Iterator<SessionKey> getSessionKeys() {
|
public @Nonnull Iterator<SessionKey> getSessionKeys() {
|
||||||
return new LayerIterator<SessionKey>(message) {
|
return map(getEncryptionLayers(), encryptedData -> encryptedData.sessionKey);
|
||||||
@Override
|
}
|
||||||
boolean matches(Nested layer) {
|
|
||||||
return layer instanceof EncryptedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public boolean isVerifiedSignedBy(@Nonnull PGPKeyRing keys) {
|
||||||
SessionKey getProperty(Layer last) {
|
return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys);
|
||||||
return ((EncryptedData) last).getSessionKey();
|
}
|
||||||
}
|
|
||||||
};
|
public boolean isVerifiedDetachedSignedBy(@Nonnull PGPKeyRing keys) {
|
||||||
|
return containsSignatureBy(getVerifiedDetachedSignatures(), keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,6 +210,10 @@ public class MessageMetadata {
|
||||||
return message.getRejectedDetachedSignatures();
|
return message.getRejectedDetachedSignatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isVerifiedInlineSignedBy(@Nonnull PGPKeyRing keys) {
|
||||||
|
return containsSignatureBy(getVerifiedInlineSignatures(), keys);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a list of all verified inline-signatures.
|
* Return a list of all verified inline-signatures.
|
||||||
* This list contains all acceptable, correct signatures that were part of the message itself.
|
* 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.
|
* 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,
|
* This value can be used to store a decrypted file under its original filename,
|
||||||
|
@ -300,14 +334,23 @@ public class MessageMetadata {
|
||||||
* @return filename
|
* @return filename
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @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();
|
LiteralData literalData = findLiteralData();
|
||||||
if (literalData == null) {
|
if (literalData == null) {
|
||||||
return null;
|
throw new NoSuchElementException("No Literal Data Packet found.");
|
||||||
}
|
}
|
||||||
return literalData.getFileName();
|
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.
|
* 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,
|
* This value can be used to restore the modification date of a decrypted file,
|
||||||
|
@ -316,10 +359,10 @@ public class MessageMetadata {
|
||||||
* @return modification date
|
* @return modification date
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @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();
|
LiteralData literalData = findLiteralData();
|
||||||
if (literalData == null) {
|
if (literalData == null) {
|
||||||
return null;
|
throw new NoSuchElementException("No Literal Data Packet found.");
|
||||||
}
|
}
|
||||||
return literalData.getModificationDate();
|
return literalData.getModificationDate();
|
||||||
}
|
}
|
||||||
|
@ -332,10 +375,10 @@ public class MessageMetadata {
|
||||||
* @return format
|
* @return format
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @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();
|
LiteralData literalData = findLiteralData();
|
||||||
if (literalData == null) {
|
if (literalData == null) {
|
||||||
return null;
|
throw new NoSuchElementException("No Literal Data Packet found.");
|
||||||
}
|
}
|
||||||
return literalData.getFormat();
|
return literalData.getFormat();
|
||||||
}
|
}
|
||||||
|
@ -368,21 +411,7 @@ public class MessageMetadata {
|
||||||
* @return decryption key
|
* @return decryption key
|
||||||
*/
|
*/
|
||||||
public SubkeyIdentifier getDecryptionKey() {
|
public SubkeyIdentifier getDecryptionKey() {
|
||||||
Iterator<SubkeyIdentifier> iterator = new LayerIterator<SubkeyIdentifier>(message) {
|
return firstOrNull(map(getEncryptionLayers(), encryptedData -> encryptedData.decryptionKey));
|
||||||
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract static class Layer {
|
public abstract static class Layer {
|
||||||
|
@ -744,4 +773,32 @@ public class MessageMetadata {
|
||||||
|
|
||||||
abstract O getProperty(Layer last);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue