mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-14 16:32:06 +01:00
Kotlin conversion: MessageMetadata
This commit is contained in:
parent
9c77d7da88
commit
4ef2c8ee1a
5 changed files with 549 additions and 888 deletions
|
@ -1,869 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
import org.pgpainless.authentication.CertificateAuthenticity;
|
|
||||||
import org.pgpainless.authentication.CertificateAuthority;
|
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException;
|
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
|
||||||
import org.pgpainless.util.SessionKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View for extracting metadata about a {@link Message}.
|
|
||||||
*/
|
|
||||||
public class MessageMetadata {
|
|
||||||
|
|
||||||
protected Message message;
|
|
||||||
|
|
||||||
public MessageMetadata(@Nonnull Message message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUsingCleartextSignatureFramework() {
|
|
||||||
return message.isCleartextSigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId.
|
|
||||||
*
|
|
||||||
* @param userId userId
|
|
||||||
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
|
|
||||||
* @param certificateAuthority certificate authority
|
|
||||||
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
|
||||||
*/
|
|
||||||
public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority) {
|
|
||||||
return isAuthenticatablySignedBy(userId, email, certificateAuthority, 120);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if the message was verifiably signed by a certificate for which we can authenticate a binding to the given userId.
|
|
||||||
*
|
|
||||||
* @param userId userId
|
|
||||||
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
|
|
||||||
* @param certificateAuthority certificate authority
|
|
||||||
* @param targetAmount target trust amount
|
|
||||||
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
|
||||||
*/
|
|
||||||
public boolean isAuthenticatablySignedBy(String userId, boolean email, CertificateAuthority certificateAuthority, int targetAmount) {
|
|
||||||
for (SignatureVerification verification : getVerifiedSignatures()) {
|
|
||||||
CertificateAuthenticity authenticity = certificateAuthority.authenticateBinding(
|
|
||||||
verification.getSigningKey().getFingerprint(), userId, email,
|
|
||||||
verification.getSignature().getCreationTime(), targetAmount);
|
|
||||||
if (authenticity.isAuthenticated()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list containing all recipient keyIDs.
|
|
||||||
*
|
|
||||||
* @return list of recipients
|
|
||||||
*/
|
|
||||||
public List<Long> getRecipientKeyIds() {
|
|
||||||
List<Long> keyIds = new ArrayList<>();
|
|
||||||
Iterator<EncryptedData> encLayers = getEncryptionLayers();
|
|
||||||
while (encLayers.hasNext()) {
|
|
||||||
EncryptedData layer = encLayers.next();
|
|
||||||
keyIds.addAll(layer.getRecipients());
|
|
||||||
}
|
|
||||||
return keyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nonnull Iterator<EncryptedData> getEncryptionLayers() {
|
|
||||||
return new LayerIterator<EncryptedData>(message) {
|
|
||||||
@Override
|
|
||||||
public boolean matches(Packet 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.
|
|
||||||
*
|
|
||||||
* @return encryption algorithm
|
|
||||||
*/
|
|
||||||
public @Nullable SymmetricKeyAlgorithm getEncryptionAlgorithm() {
|
|
||||||
return firstOrNull(getEncryptionAlgorithms());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<SymmetricKeyAlgorithm> getEncryptionAlgorithms() {
|
|
||||||
return map(getEncryptionLayers(), encryptedData -> encryptedData.algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nonnull Iterator<CompressedData> getCompressionLayers() {
|
|
||||||
return new LayerIterator<CompressedData>(message) {
|
|
||||||
@Override
|
|
||||||
boolean matches(Packet layer) {
|
|
||||||
return layer instanceof CompressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
CompressedData getProperty(Layer last) {
|
|
||||||
return (CompressedData) last;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() {
|
|
||||||
return firstOrNull(getCompressionAlgorithms());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<CompressionAlgorithm> getCompressionAlgorithms() {
|
|
||||||
return map(getCompressionLayers(), compressionLayer -> compressionLayer.algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link SessionKey} of the outermost encrypted data packet.
|
|
||||||
* If the message was unencrypted, this method returns <pre>null</pre>.
|
|
||||||
*
|
|
||||||
* @return session key of the message
|
|
||||||
*/
|
|
||||||
public @Nullable SessionKey getSessionKey() {
|
|
||||||
return firstOrNull(getSessionKeys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<SessionKey> getSessionKeys() {
|
|
||||||
return map(getEncryptionLayers(), encryptedData -> encryptedData.sessionKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVerifiedSignedBy(@Nonnull PGPKeyRing keys) {
|
|
||||||
return isVerifiedInlineSignedBy(keys) || isVerifiedDetachedSignedBy(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if the message was verifiable signed by a certificate that either has the given fingerprint
|
|
||||||
* as primary key, or as the signing subkey.
|
|
||||||
*
|
|
||||||
* @param fingerprint fingerprint
|
|
||||||
* @return true if message was signed by a cert identified by the given fingerprint
|
|
||||||
*/
|
|
||||||
public boolean isVerifiedSignedBy(@Nonnull OpenPgpFingerprint fingerprint) {
|
|
||||||
List<SignatureVerification> verifications = getVerifiedSignatures();
|
|
||||||
for (SignatureVerification verification : verifications) {
|
|
||||||
if (verification.getSigningKey() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fingerprint.equals(verification.getSigningKey().getPrimaryKeyFingerprint()) ||
|
|
||||||
fingerprint.equals(verification.getSigningKey().getSubkeyFingerprint())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SignatureVerification> getVerifiedSignatures() {
|
|
||||||
List<SignatureVerification> allVerifiedSignatures = getVerifiedInlineSignatures();
|
|
||||||
allVerifiedSignatures.addAll(getVerifiedDetachedSignatures());
|
|
||||||
return allVerifiedSignatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVerifiedDetachedSignedBy(@Nonnull PGPKeyRing keys) {
|
|
||||||
return containsSignatureBy(getVerifiedDetachedSignatures(), keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all verified detached signatures.
|
|
||||||
* This list contains all acceptable, correct detached signatures.
|
|
||||||
*
|
|
||||||
* @return verified detached signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull List<SignatureVerification> getVerifiedDetachedSignatures() {
|
|
||||||
return message.getVerifiedDetachedSignatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all rejected detached signatures.
|
|
||||||
*
|
|
||||||
* @return rejected detached signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull List<SignatureVerification.Failure> getRejectedDetachedSignatures() {
|
|
||||||
return message.getRejectedDetachedSignatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all rejected signatures.
|
|
||||||
*
|
|
||||||
* @return rejected signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull List<SignatureVerification.Failure> getRejectedSignatures() {
|
|
||||||
List<SignatureVerification.Failure> rejected = new ArrayList<>();
|
|
||||||
rejected.addAll(getRejectedInlineSignatures());
|
|
||||||
rejected.addAll(getRejectedDetachedSignatures());
|
|
||||||
return rejected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasRejectedSignatures() {
|
|
||||||
return !getRejectedSignatures().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if the message contains any (verified or rejected) signature.
|
|
||||||
* @return true if message has signature
|
|
||||||
*/
|
|
||||||
public boolean hasSignature() {
|
|
||||||
return isVerifiedSigned() || hasRejectedSignatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @return verified inline signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull List<SignatureVerification> getVerifiedInlineSignatures() {
|
|
||||||
List<SignatureVerification> verifications = new ArrayList<>();
|
|
||||||
Iterator<List<SignatureVerification>> verificationsByLayer = getVerifiedInlineSignaturesByLayer();
|
|
||||||
while (verificationsByLayer.hasNext()) {
|
|
||||||
verifications.addAll(verificationsByLayer.next());
|
|
||||||
}
|
|
||||||
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<List<SignatureVerification>> getVerifiedInlineSignaturesByLayer() {
|
|
||||||
return new LayerIterator<List<SignatureVerification>>(message) {
|
|
||||||
@Override
|
|
||||||
boolean matches(Packet layer) {
|
|
||||||
return layer instanceof Layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
List<SignatureVerification> getProperty(Layer last) {
|
|
||||||
List<SignatureVerification> list = new ArrayList<>();
|
|
||||||
list.addAll(last.getVerifiedOnePassSignatures());
|
|
||||||
list.addAll(last.getVerifiedPrependedSignatures());
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all rejected inline-signatures of the message.
|
|
||||||
*
|
|
||||||
* @return list of rejected inline-signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull List<SignatureVerification.Failure> getRejectedInlineSignatures() {
|
|
||||||
List<SignatureVerification.Failure> rejected = new ArrayList<>();
|
|
||||||
Iterator<List<SignatureVerification.Failure>> rejectedByLayer = getRejectedInlineSignaturesByLayer();
|
|
||||||
while (rejectedByLayer.hasNext()) {
|
|
||||||
rejected.addAll(rejectedByLayer.next());
|
|
||||||
}
|
|
||||||
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<List<SignatureVerification.Failure>> getRejectedInlineSignaturesByLayer() {
|
|
||||||
return new LayerIterator<List<SignatureVerification.Failure>>(message) {
|
|
||||||
@Override
|
|
||||||
boolean matches(Packet layer) {
|
|
||||||
return layer instanceof Layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
List<SignatureVerification.Failure> getProperty(Layer last) {
|
|
||||||
List<SignatureVerification.Failure> list = new ArrayList<>();
|
|
||||||
list.addAll(last.getRejectedOnePassSignatures());
|
|
||||||
list.addAll(last.getRejectedPrependedSignatures());
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
* but since this field is not necessarily part of the signed data of a message, usage of this field is
|
|
||||||
* discouraged.
|
|
||||||
*
|
|
||||||
* @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() {
|
|
||||||
LiteralData literalData = findLiteralData();
|
|
||||||
if (literalData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
* but since this field is not necessarily part of the signed data, its use is discouraged.
|
|
||||||
*
|
|
||||||
* @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() {
|
|
||||||
LiteralData literalData = findLiteralData();
|
|
||||||
if (literalData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return literalData.getModificationDate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
|
||||||
*/
|
|
||||||
public @Nullable StreamEncoding getLiteralDataEncoding() {
|
|
||||||
LiteralData literalData = findLiteralData();
|
|
||||||
if (literalData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return literalData.getFormat();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 <pre>null</pre>.
|
|
||||||
*
|
|
||||||
* @return literal data
|
|
||||||
*/
|
|
||||||
private @Nullable LiteralData findLiteralData() {
|
|
||||||
Nested nested = message.getChild();
|
|
||||||
if (nested == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (nested != null && nested.hasNestedChild()) {
|
|
||||||
Layer layer = (Layer) nested;
|
|
||||||
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 <pre>null</pre>.
|
|
||||||
*
|
|
||||||
* @return decryption key
|
|
||||||
*/
|
|
||||||
public SubkeyIdentifier getDecryptionKey() {
|
|
||||||
return firstOrNull(map(getEncryptionLayers(), encryptedData -> encryptedData.decryptionKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isVerifiedSigned() {
|
|
||||||
return !getVerifiedSignatures().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Packet {
|
|
||||||
|
|
||||||
}
|
|
||||||
public abstract static class Layer implements Packet {
|
|
||||||
public static final int MAX_LAYER_DEPTH = 16;
|
|
||||||
protected final int depth;
|
|
||||||
protected final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
|
|
||||||
protected final List<SignatureVerification.Failure> rejectedDetachedSignatures = new ArrayList<>();
|
|
||||||
protected final List<SignatureVerification> verifiedOnePassSignatures = new ArrayList<>();
|
|
||||||
protected final List<SignatureVerification.Failure> rejectedOnePassSignatures = new ArrayList<>();
|
|
||||||
protected final List<SignatureVerification> verifiedPrependedSignatures = new ArrayList<>();
|
|
||||||
protected final List<SignatureVerification.Failure> rejectedPrependedSignatures = new ArrayList<>();
|
|
||||||
protected Nested child;
|
|
||||||
|
|
||||||
public Layer(int depth) {
|
|
||||||
this.depth = depth;
|
|
||||||
if (depth > MAX_LAYER_DEPTH) {
|
|
||||||
throw new MalformedOpenPgpMessageException("Maximum packet nesting depth (" + MAX_LAYER_DEPTH + ") exceeded.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the nested child element of this layer.
|
|
||||||
* Might return <pre>null</pre>, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<SignatureVerification> 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<SignatureVerification.Failure> 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<SignatureVerification> 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<SignatureVerification.Failure> 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<SignatureVerification> 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<SignatureVerification.Failure> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Nested extends Packet {
|
|
||||||
boolean hasNestedChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Message extends Layer {
|
|
||||||
|
|
||||||
protected boolean cleartextSigned;
|
|
||||||
|
|
||||||
public Message() {
|
|
||||||
super(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true, is the message is a signed message using the cleartext signature framework.
|
|
||||||
*
|
|
||||||
* @return <pre>true</pre> if message is cleartext-signed, <pre>false</pre> otherwise
|
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a>
|
|
||||||
*/
|
|
||||||
public boolean isCleartextSigned() {
|
|
||||||
return cleartextSigned;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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(@Nonnull String fileName,
|
|
||||||
@Nonnull Date modificationDate,
|
|
||||||
@Nonnull StreamEncoding format) {
|
|
||||||
this.fileName = fileName;
|
|
||||||
this.modificationDate = modificationDate;
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the value of the filename field.
|
|
||||||
* An empty String <pre>""</pre> indicates no filename.
|
|
||||||
*
|
|
||||||
* @return filename
|
|
||||||
*/
|
|
||||||
public @Nonnull String getFileName() {
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the value of the modification date field.
|
|
||||||
* A special date <pre>{@code new Date(0L)}</pre> indicates no modification date.
|
|
||||||
*
|
|
||||||
* @return modification date
|
|
||||||
*/
|
|
||||||
public @Nonnull Date getModificationDate() {
|
|
||||||
return modificationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CompressedData extends Layer implements Nested {
|
|
||||||
protected final CompressionAlgorithm algorithm;
|
|
||||||
|
|
||||||
public CompressedData(@Nonnull CompressionAlgorithm zip, int depth) {
|
|
||||||
super(depth);
|
|
||||||
this.algorithm = zip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class EncryptedData extends Layer implements Nested {
|
|
||||||
protected final SymmetricKeyAlgorithm algorithm;
|
|
||||||
protected SubkeyIdentifier decryptionKey;
|
|
||||||
protected SessionKey sessionKey;
|
|
||||||
protected List<Long> recipients;
|
|
||||||
|
|
||||||
public EncryptedData(@Nonnull SymmetricKeyAlgorithm algorithm, int depth) {
|
|
||||||
super(depth);
|
|
||||||
this.algorithm = algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link SymmetricKeyAlgorithm} used to encrypt the packet.
|
|
||||||
* @return symmetric encryption algorithm
|
|
||||||
*/
|
|
||||||
public @Nonnull SymmetricKeyAlgorithm getAlgorithm() {
|
|
||||||
return algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link SessionKey} used to decrypt the packet.
|
|
||||||
*
|
|
||||||
* @return session key
|
|
||||||
*/
|
|
||||||
public @Nonnull SessionKey getSessionKey() {
|
|
||||||
return sessionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of all recipient key ids to which the packet was encrypted for.
|
|
||||||
*
|
|
||||||
* @return recipients
|
|
||||||
*/
|
|
||||||
public @Nonnull List<Long> getRecipients() {
|
|
||||||
if (recipients == null) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
return new ArrayList<>(recipients);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNestedChild() {
|
|
||||||
// An encrypted data packet MUST have a child element
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private abstract static class LayerIterator<O> implements Iterator<O> {
|
|
||||||
private Nested current;
|
|
||||||
Layer last = null;
|
|
||||||
Message parent;
|
|
||||||
|
|
||||||
LayerIterator(@Nonnull Message message) {
|
|
||||||
super();
|
|
||||||
this.parent = message;
|
|
||||||
this.current = message.getChild();
|
|
||||||
if (matches(current)) {
|
|
||||||
last = (Layer) current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
if (parent != null && matches(parent)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (last == null) {
|
|
||||||
findNext();
|
|
||||||
}
|
|
||||||
return last != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public O next() {
|
|
||||||
if (parent != null && matches(parent)) {
|
|
||||||
O property = getProperty(parent);
|
|
||||||
parent = null;
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
if (last == null) {
|
|
||||||
findNext();
|
|
||||||
}
|
|
||||||
if (last != null) {
|
|
||||||
O property = getProperty(last);
|
|
||||||
last = null;
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findNext() {
|
|
||||||
while (current != null && current instanceof Layer) {
|
|
||||||
current = ((Layer) current).getChild();
|
|
||||||
if (matches(current)) {
|
|
||||||
last = (Layer) current;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract boolean matches(Packet layer);
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Function<A, B> {
|
|
||||||
B apply(A item);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,528 @@
|
||||||
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
|
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||||
|
import org.pgpainless.algorithm.StreamEncoding
|
||||||
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
|
import org.pgpainless.authentication.CertificateAuthority
|
||||||
|
import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
import org.pgpainless.util.SessionKey
|
||||||
|
import java.util.*
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View for extracting metadata about a [Message].
|
||||||
|
*/
|
||||||
|
class MessageMetadata(
|
||||||
|
val message: Message
|
||||||
|
) {
|
||||||
|
|
||||||
|
// ################################################################################################################
|
||||||
|
// ### Encryption ###
|
||||||
|
// ################################################################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted.
|
||||||
|
*/
|
||||||
|
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
||||||
|
get() = encryptionAlgorithms.let {
|
||||||
|
if (it.hasNext()) it.next() else null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Iterator] of each [SymmetricKeyAlgorithm] 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.
|
||||||
|
*/
|
||||||
|
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
||||||
|
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
||||||
|
|
||||||
|
val isEncrypted: Boolean
|
||||||
|
get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
||||||
|
|
||||||
|
fun isEncryptedFor(keys: PGPKeyRing): Boolean {
|
||||||
|
return encryptionLayers.asSequence().any {
|
||||||
|
it.recipients.any { keyId ->
|
||||||
|
keys.getPublicKey(keyId) != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [SessionKey] of the outermost encrypted data packet.
|
||||||
|
* If the message was unencrypted, this method returns `null`.
|
||||||
|
*/
|
||||||
|
val sessionKey: SessionKey?
|
||||||
|
get() = sessionKeys.asSequence().firstOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Iterator] of each [SessionKey] 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.
|
||||||
|
*/
|
||||||
|
val sessionKeys: Iterator<SessionKey>
|
||||||
|
get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption
|
||||||
|
* layer.
|
||||||
|
* If the message was unencrypted or was decrypted using a passphrase, this field might be `null`.
|
||||||
|
*/
|
||||||
|
val decryptionKey: SubkeyIdentifier?
|
||||||
|
get() = encryptionLayers.asSequence()
|
||||||
|
.mapNotNull { it.decryptionKey }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List containing all recipient keyIDs.
|
||||||
|
*/
|
||||||
|
val recipientKeyIds: List<Long>
|
||||||
|
get() = encryptionLayers.asSequence()
|
||||||
|
.map { it.recipients.toMutableList() }
|
||||||
|
.reduce { all, keyIds -> all.addAll(keyIds); all }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val encryptionLayers: Iterator<EncryptedData>
|
||||||
|
get() = object : LayerIterator<EncryptedData>(message) {
|
||||||
|
override fun matches(layer: Packet) = layer is EncryptedData
|
||||||
|
override fun getProperty(last: Layer) = last as EncryptedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################################################################################
|
||||||
|
// ### Compression ###
|
||||||
|
// ################################################################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message
|
||||||
|
* does not contain any compressed data packets.
|
||||||
|
*/
|
||||||
|
val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Iterator] of each [CompressionAlgorithm] 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.
|
||||||
|
*/
|
||||||
|
val compressionAlgorithms: Iterator<CompressionAlgorithm>
|
||||||
|
get() = compressionLayers.asSequence().map { it.algorithm }.iterator()
|
||||||
|
|
||||||
|
val compressionLayers: Iterator<CompressedData>
|
||||||
|
get() = object : LayerIterator<CompressedData>(message) {
|
||||||
|
override fun matches(layer: Packet) = layer is CompressedData
|
||||||
|
override fun getProperty(last: Layer) = last as CompressedData
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################################################################################
|
||||||
|
// ### Signatures ###
|
||||||
|
// ################################################################################################################
|
||||||
|
|
||||||
|
val isUsingCleartextSignatureFramework: Boolean
|
||||||
|
get() = message.cleartextSigned
|
||||||
|
|
||||||
|
val verifiedSignatures: List<SignatureVerification>
|
||||||
|
get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all rejected signatures.
|
||||||
|
*/
|
||||||
|
val rejectedSignatures: List<SignatureVerification.Failure>
|
||||||
|
get() = mutableListOf<SignatureVerification.Failure>()
|
||||||
|
.plus(rejectedInlineSignatures)
|
||||||
|
.plus(rejectedDetachedSignatures)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all verified inline-signatures.
|
||||||
|
* This list contains all acceptable, correct signatures that were part of the message itself.
|
||||||
|
*/
|
||||||
|
val verifiedInlineSignatures: List<SignatureVerification> = verifiedInlineSignaturesByLayer
|
||||||
|
.asSequence()
|
||||||
|
.map { it.toMutableList() }
|
||||||
|
.reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Iterator] of each [List] of verified inline-signatures of the message, separated by layer.
|
||||||
|
* 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 [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.
|
||||||
|
*/
|
||||||
|
val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>>
|
||||||
|
get() = object : LayerIterator<List<SignatureVerification>>(message) {
|
||||||
|
override fun matches(layer: Packet) = layer is Layer
|
||||||
|
|
||||||
|
override fun getProperty(last: Layer): List<SignatureVerification> {
|
||||||
|
return listOf<SignatureVerification>()
|
||||||
|
.plus(last.verifiedOnePassSignatures)
|
||||||
|
.plus(last.verifiedPrependedSignatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all rejected inline-signatures of the message.
|
||||||
|
*/
|
||||||
|
val rejectedInlineSignatures: List<SignatureVerification.Failure> = rejectedInlineSignaturesByLayer
|
||||||
|
.asSequence()
|
||||||
|
.map { it.toMutableList() }
|
||||||
|
.reduce { acc, failures -> acc.addAll(failures); acc}
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures
|
||||||
|
* of the message, but organized by layer.
|
||||||
|
*/
|
||||||
|
val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>>
|
||||||
|
get() = object : LayerIterator<List<SignatureVerification.Failure>>(message) {
|
||||||
|
override fun matches(layer: Packet) = layer is Layer
|
||||||
|
|
||||||
|
override fun getProperty(last: Layer): List<SignatureVerification.Failure> =
|
||||||
|
mutableListOf<SignatureVerification.Failure>()
|
||||||
|
.plus(last.rejectedOnePassSignatures)
|
||||||
|
.plus(last.rejectedPrependedSignatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all verified detached signatures.
|
||||||
|
* This list contains all acceptable, correct detached signatures.
|
||||||
|
*/
|
||||||
|
val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all rejected detached signatures.
|
||||||
|
*/
|
||||||
|
val rejectedDetachedSignatures: List<SignatureVerification.Failure> = message.rejectedDetachedSignatures
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True, if the message contains any (verified or rejected) signature, false if no signatures are present.
|
||||||
|
*/
|
||||||
|
val hasSignature: Boolean
|
||||||
|
get() = isVerifiedSigned() || hasRejectedSignatures()
|
||||||
|
|
||||||
|
fun isVerifiedSigned(): Boolean = verifiedSignatures.isNotEmpty()
|
||||||
|
|
||||||
|
fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId.
|
||||||
|
*
|
||||||
|
* @param userId userId
|
||||||
|
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
|
||||||
|
* @param certificateAuthority certificate authority
|
||||||
|
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated.
|
||||||
|
* defaults to 120.
|
||||||
|
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean {
|
||||||
|
return verifiedSignatures.any {
|
||||||
|
certificateAuthority.authenticateBinding(
|
||||||
|
it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount
|
||||||
|
).authenticated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint
|
||||||
|
* as primary key, or as the signing subkey.
|
||||||
|
*
|
||||||
|
* @param fingerprint fingerprint
|
||||||
|
* @return true if message was signed by a cert identified by the given fingerprint
|
||||||
|
*/
|
||||||
|
fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedSignatures.any {
|
||||||
|
it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVerifiedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedSignatures, keys)
|
||||||
|
|
||||||
|
fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedDetachedSignatures.any {
|
||||||
|
it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedDetachedSignatures, keys)
|
||||||
|
|
||||||
|
fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) = verifiedInlineSignatures.any {
|
||||||
|
it.signingKey.primaryKeyFingerprint == fingerprint || it.signingKey.subkeyFingerprint == fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isVerifiedInlineSignedBy(keys: PGPKeyRing) = containsSignatureBy(verifiedInlineSignatures, keys)
|
||||||
|
|
||||||
|
private fun containsSignatureBy(signatures: List<SignatureVerification>, keys: PGPKeyRing) =
|
||||||
|
signatures.any {
|
||||||
|
// Match certificate by primary key id
|
||||||
|
keys.publicKey.keyID == it.signingKey.primaryKeyId &&
|
||||||
|
// match signing subkey
|
||||||
|
keys.getPublicKey(it.signingKey.subkeyId) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################################################################################
|
||||||
|
// ### Literal Data ###
|
||||||
|
// ################################################################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||||
|
*/
|
||||||
|
val filename: String? = findLiteralData()?.fileName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True, if the sender signals an increased degree of confidentiality by setting the filename of the literal
|
||||||
|
* data packet to a special value that indicates that the data is intended for your eyes only.
|
||||||
|
*/
|
||||||
|
@Deprecated("Reliance on this signaling mechanism is discouraged.")
|
||||||
|
val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||||
|
*/
|
||||||
|
val modificationDate: Date? = findLiteralData()?.modificationDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||||
|
*/
|
||||||
|
val literalDataEncoding: StreamEncoding? = findLiteralData()?.format
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the [LiteralData] layer of an OpenPGP message.
|
||||||
|
* This method might return null, for example for a cleartext signed message without OpenPGP packets.
|
||||||
|
*
|
||||||
|
* @return literal data
|
||||||
|
*/
|
||||||
|
private fun findLiteralData(): LiteralData? {
|
||||||
|
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message,
|
||||||
|
// we might not have a Literal Data packet.
|
||||||
|
var nested = message.child ?: return null
|
||||||
|
|
||||||
|
while (nested.hasNestedChild()) {
|
||||||
|
val layer = nested as Layer
|
||||||
|
nested = checkNotNull(layer.child) {
|
||||||
|
// Otherwise, we MUST find a Literal Data packet, or else the message is malformed
|
||||||
|
"Malformed OpenPGP message. Cannot find Literal Data Packet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nested as LiteralData
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################################################################################
|
||||||
|
// ### Message Structure ###
|
||||||
|
// ################################################################################################################
|
||||||
|
|
||||||
|
interface Packet
|
||||||
|
|
||||||
|
interface Nested : Packet {
|
||||||
|
fun hasNestedChild(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Layer(
|
||||||
|
val depth: Int
|
||||||
|
) : Packet {
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (depth > MAX_LAYER_DEPTH) {
|
||||||
|
throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val verifiedDetachedSignatures: List<SignatureVerification> = mutableListOf()
|
||||||
|
val rejectedDetachedSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
||||||
|
val verifiedOnePassSignatures: List<SignatureVerification> = mutableListOf()
|
||||||
|
val rejectedOnePassSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
||||||
|
val verifiedPrependedSignatures: List<SignatureVerification> = mutableListOf()
|
||||||
|
val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nested child element of this layer.
|
||||||
|
* Might be `null`, if this layer does not have a child element
|
||||||
|
* (e.g. if this is a [LiteralData] packet).
|
||||||
|
*/
|
||||||
|
var child: Nested? = null
|
||||||
|
|
||||||
|
fun addVerifiedDetachedSignature(signature: SignatureVerification) = apply {
|
||||||
|
(verifiedDetachedSignatures as MutableList).add(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addRejectedDetachedSignature(failure: SignatureVerification.Failure) = apply {
|
||||||
|
(rejectedDetachedSignatures as MutableList).add(failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addVerifiedOnePassSignature(signature: SignatureVerification) = apply {
|
||||||
|
(verifiedOnePassSignatures as MutableList).add(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addRejectedOnePassSignature(failure: SignatureVerification.Failure) = apply {
|
||||||
|
(rejectedOnePassSignatures as MutableList).add(failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addVerifiedPrependedSignature(signature: SignatureVerification) = apply {
|
||||||
|
(verifiedPrependedSignatures as MutableList).add(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addRejectedPrependedSignature(failure: SignatureVerification.Failure) = apply {
|
||||||
|
(rejectedPrependedSignatures as MutableList).add(failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_LAYER_DEPTH = 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outermost OpenPGP Message structure.
|
||||||
|
*
|
||||||
|
* @param cleartextSigned whether the message is using the Cleartext Signature Framework
|
||||||
|
*
|
||||||
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a>
|
||||||
|
*/
|
||||||
|
class Message(var cleartextSigned: Boolean = false) : Layer(0) {
|
||||||
|
fun setCleartextSigned() = apply { cleartextSigned = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Literal Data Packet.
|
||||||
|
*
|
||||||
|
* @param fileName value of the filename field. An empty String represents no filename.
|
||||||
|
* @param modificationDate value of the modification date field. The special value `Date(0)` indicates no
|
||||||
|
* modification date.
|
||||||
|
* @param format value of the format field.
|
||||||
|
*/
|
||||||
|
class LiteralData(
|
||||||
|
val fileName: String = "",
|
||||||
|
val modificationDate: Date = Date(0L),
|
||||||
|
val format: StreamEncoding = StreamEncoding.BINARY
|
||||||
|
) : Nested {
|
||||||
|
|
||||||
|
// A literal data packet MUST NOT have a child element, as its content is the plaintext
|
||||||
|
override fun hasNestedChild() = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compressed Data Packet.
|
||||||
|
*
|
||||||
|
* @param algorithm [CompressionAlgorithm] used to compress the packet.
|
||||||
|
* @param depth nesting depth at which this packet was encountered.
|
||||||
|
*/
|
||||||
|
class CompressedData(
|
||||||
|
val algorithm: CompressionAlgorithm,
|
||||||
|
depth: Int) : Layer(depth), Nested {
|
||||||
|
|
||||||
|
// A compressed data packet MUST have a child element
|
||||||
|
override fun hasNestedChild() = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypted Data.
|
||||||
|
*
|
||||||
|
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
||||||
|
* @param depth nesting depth at which this packet was encountered.
|
||||||
|
*/
|
||||||
|
class EncryptedData(
|
||||||
|
val algorithm: SymmetricKeyAlgorithm,
|
||||||
|
depth: Int
|
||||||
|
) : Layer(depth), Nested {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [SessionKey] used to decrypt the packet.
|
||||||
|
*/
|
||||||
|
var sessionKey: SessionKey? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of all recipient key ids to which the packet was encrypted for.
|
||||||
|
*/
|
||||||
|
val recipients: List<Long> = mutableListOf()
|
||||||
|
|
||||||
|
fun addRecipients(keyIds: List<Long>) = apply {
|
||||||
|
(recipients as MutableList).addAll(keyIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet).
|
||||||
|
*/
|
||||||
|
var decryptionKey: SubkeyIdentifier? = null
|
||||||
|
|
||||||
|
// An encrypted data packet MUST have a child element
|
||||||
|
override fun hasNestedChild() = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of
|
||||||
|
* a transformation ([getProperty]) on those packets that match ([matches]) a given criterion.
|
||||||
|
*
|
||||||
|
* @param message outermost structure object
|
||||||
|
*/
|
||||||
|
private abstract class LayerIterator<O>(@Nonnull message: Message) : Iterator<O> {
|
||||||
|
private var current: Nested?
|
||||||
|
var last: Layer? = null
|
||||||
|
var parent: Message?
|
||||||
|
|
||||||
|
init {
|
||||||
|
parent = message
|
||||||
|
current = message.child
|
||||||
|
current?.let {
|
||||||
|
if (matches(it)) {
|
||||||
|
last = current as Layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
parent?.let {
|
||||||
|
if (matches(it)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last == null) {
|
||||||
|
findNext()
|
||||||
|
}
|
||||||
|
return last != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): O {
|
||||||
|
parent?.let {
|
||||||
|
if (matches(it)) {
|
||||||
|
return getProperty(it).also { parent = null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last == null) {
|
||||||
|
findNext()
|
||||||
|
}
|
||||||
|
last?.let {
|
||||||
|
return getProperty(it).also { last = null }
|
||||||
|
}
|
||||||
|
throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findNext() {
|
||||||
|
while (current != null && current is Layer) {
|
||||||
|
current = (current as Layer).child
|
||||||
|
if (current != null && matches(current!!)) {
|
||||||
|
last = current as Layer
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun matches(layer: Packet): Boolean
|
||||||
|
abstract fun getProperty(last: Layer): O
|
||||||
|
}
|
||||||
|
}
|
|
@ -156,9 +156,9 @@ class OpenPgpMessageInputStream(
|
||||||
val literalData = packetInputStream!!.readLiteralData()
|
val literalData = packetInputStream!!.readLiteralData()
|
||||||
|
|
||||||
// Extract Metadata
|
// Extract Metadata
|
||||||
layerMetadata.setChild(LiteralData(
|
layerMetadata.child = LiteralData(
|
||||||
literalData.fileName, literalData.modificationTime,
|
literalData.fileName, literalData.modificationTime,
|
||||||
StreamEncoding.requireFromCode(literalData.format)))
|
StreamEncoding.requireFromCode(literalData.format))
|
||||||
|
|
||||||
nestedInputStream = literalData.inputStream
|
nestedInputStream = literalData.inputStream
|
||||||
}
|
}
|
||||||
|
@ -394,7 +394,7 @@ class OpenPgpMessageInputStream(
|
||||||
throwIfUnacceptable(sessionKey.algorithm)
|
throwIfUnacceptable(sessionKey.algorithm)
|
||||||
val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1)
|
val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1)
|
||||||
encryptedData.sessionKey = sessionKey
|
encryptedData.sessionKey = sessionKey
|
||||||
encryptedData.recipients = esks.pkesks.map { it.keyID }
|
encryptedData.addRecipients(esks.pkesks.map { it.keyID })
|
||||||
LOGGER.debug("Successfully decrypted data with passphrase")
|
LOGGER.debug("Successfully decrypted data with passphrase")
|
||||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
|
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
|
||||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||||
|
@ -421,7 +421,7 @@ class OpenPgpMessageInputStream(
|
||||||
layerMetadata.depth + 1)
|
layerMetadata.depth + 1)
|
||||||
encryptedData.decryptionKey = decryptionKeyId
|
encryptedData.decryptionKey = decryptionKeyId
|
||||||
encryptedData.sessionKey = sessionKey
|
encryptedData.sessionKey = sessionKey
|
||||||
encryptedData.recipients = esks.pkesks.plus(esks.anonPkesks).map { it.keyID }
|
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID })
|
||||||
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
|
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
|
||||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
|
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
|
||||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||||
|
@ -522,7 +522,7 @@ class OpenPgpMessageInputStream(
|
||||||
private fun collectMetadata() {
|
private fun collectMetadata() {
|
||||||
if (nestedInputStream is OpenPgpMessageInputStream) {
|
if (nestedInputStream is OpenPgpMessageInputStream) {
|
||||||
val child = nestedInputStream as OpenPgpMessageInputStream
|
val child = nestedInputStream as OpenPgpMessageInputStream
|
||||||
layerMetadata.setChild(child.layerMetadata as Nested)
|
layerMetadata.child = (child.layerMetadata as Nested)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,8 +620,7 @@ class OpenPgpMessageInputStream(
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
detachedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
detachedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||||
SignatureVerification(signature, null),
|
signature, null, SignatureValidationException("Missing verification key.")))
|
||||||
SignatureValidationException("Missing verification key.")))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,8 +632,7 @@ class OpenPgpMessageInputStream(
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
prependedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
prependedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||||
SignatureVerification(signature, null),
|
signature, null, SignatureValidationException("Missing verification key")
|
||||||
SignatureValidationException("Missing verification key")
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -695,8 +693,7 @@ class OpenPgpMessageInputStream(
|
||||||
if (!found) {
|
if (!found) {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
inbandSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
inbandSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||||
SignatureVerification(signature, null),
|
signature, null, SignatureValidationException("Missing verification key.")
|
||||||
SignatureValidationException("Missing verification key.")
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -890,7 +887,7 @@ class OpenPgpMessageInputStream(
|
||||||
return if (openPgpIn.isAsciiArmored) {
|
return if (openPgpIn.isAsciiArmored) {
|
||||||
val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
|
val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
|
||||||
if (armorIn.isClearText) {
|
if (armorIn.isClearText) {
|
||||||
(metadata as Message).cleartextSigned = true
|
(metadata as Message).setCleartextSigned()
|
||||||
OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy)
|
OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy)
|
||||||
} else {
|
} else {
|
||||||
// Simply consume dearmored OpenPGP message
|
// Simply consume dearmored OpenPGP message
|
||||||
|
|
|
@ -22,12 +22,12 @@ import org.pgpainless.signature.SignatureUtils
|
||||||
*/
|
*/
|
||||||
data class SignatureVerification(
|
data class SignatureVerification(
|
||||||
val signature: PGPSignature,
|
val signature: PGPSignature,
|
||||||
val signingKey: SubkeyIdentifier?
|
val signingKey: SubkeyIdentifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
||||||
" Key: ${signingKey?.toString() ?: "null"};"
|
" Key: $signingKey;"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,11 +38,16 @@ data class SignatureVerification(
|
||||||
* @param validationException exception that caused the verification to fail
|
* @param validationException exception that caused the verification to fail
|
||||||
*/
|
*/
|
||||||
data class Failure(
|
data class Failure(
|
||||||
val signatureVerification: SignatureVerification,
|
val signature: PGPSignature,
|
||||||
|
val signingKey: SubkeyIdentifier?,
|
||||||
val validationException: SignatureValidationException
|
val validationException: SignatureValidationException
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
constructor(verification: SignatureVerification, validationException: SignatureValidationException):
|
||||||
|
this(verification.signature, verification.signingKey, validationException)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$signatureVerification Failure: ${validationException.message}"
|
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,9 +28,9 @@ public class MessageMetadataTest {
|
||||||
// For the sake of testing though, this is okay.
|
// For the sake of testing though, this is okay.
|
||||||
MessageMetadata.Message message = new MessageMetadata.Message();
|
MessageMetadata.Message message = new MessageMetadata.Message();
|
||||||
|
|
||||||
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.depth + 1);
|
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.depth + 1);
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.getDepth() + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.depth + 1);
|
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.getDepth() + 1);
|
||||||
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
||||||
|
|
||||||
message.setChild(compressedData);
|
message.setChild(compressedData);
|
||||||
|
|
Loading…
Reference in a new issue