// SPDX-FileCopyrightText: 2018 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.decryption_verification; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.exception.SignatureValidationException; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.SubkeyIdentifier; public class OpenPgpMetadata { private final Set recipientKeyIds; private final SubkeyIdentifier decryptionKey; private final List verifiedInbandSignatures; private final List invalidInbandSignatures; private final List verifiedDetachedSignatures; private final List invalidDetachedSignatures; private final SymmetricKeyAlgorithm symmetricKeyAlgorithm; private final CompressionAlgorithm compressionAlgorithm; private final String fileName; private final Date modificationDate; private final StreamEncoding fileEncoding; public OpenPgpMetadata(Set recipientKeyIds, SubkeyIdentifier decryptionKey, SymmetricKeyAlgorithm symmetricKeyAlgorithm, CompressionAlgorithm algorithm, List verifiedInbandSignatures, List invalidInbandSignatures, List verifiedDetachedSignatures, List invalidDetachedSignatures, String fileName, Date modificationDate, StreamEncoding fileEncoding) { this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds); this.decryptionKey = decryptionKey; this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; this.compressionAlgorithm = algorithm; this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures); this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures); this.verifiedDetachedSignatures = Collections.unmodifiableList(verifiedDetachedSignatures); this.invalidDetachedSignatures = Collections.unmodifiableList(invalidDetachedSignatures); this.fileName = fileName; this.modificationDate = modificationDate; this.fileEncoding = fileEncoding; } /** * Return a set of key-ids the messages was encrypted for. * * @return recipient ids */ public @Nonnull Set getRecipientKeyIds() { return recipientKeyIds; } /** * Return true, if the message was encrypted. * * @return true if encrypted, false otherwise */ public boolean isEncrypted() { return symmetricKeyAlgorithm != SymmetricKeyAlgorithm.NULL && !getRecipientKeyIds().isEmpty(); } /** * Return the {@link SubkeyIdentifier} of the key that was used to decrypt the message. * This can be null if the message was decrypted using a {@link org.pgpainless.util.Passphrase}, or if it was not * encrypted at all (eg. signed only). * * @return subkey identifier of decryption key */ public @Nullable SubkeyIdentifier getDecryptionKey() { return decryptionKey; } /** * Return the algorithm that was used to symmetrically encrypt the message. * * @return encryption algorithm */ public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() { return symmetricKeyAlgorithm; } /** * Return the {@link CompressionAlgorithm} that was used to compress the message. * * @return compression algorithm */ public @Nullable CompressionAlgorithm getCompressionAlgorithm() { return compressionAlgorithm; } /** * Return a set of all signatures on the message. * Note: This method returns just the signatures. There is no guarantee that the signatures are verified or even correct. * * Use {@link #getVerifiedSignatures()} instead to get all verified signatures. * @return unverified and verified signatures */ public @Nonnull Set getSignatures() { Set signatures = new HashSet<>(); for (SignatureVerification v : getVerifiedDetachedSignatures()) { signatures.add(v.getSignature()); } for (SignatureVerification v : getVerifiedInbandSignatures()) { signatures.add(v.getSignature()); } for (SignatureVerification.Failure f : getInvalidDetachedSignatures()) { signatures.add(f.getSignatureVerification().getSignature()); } for (SignatureVerification.Failure f : getInvalidInbandSignatures()) { signatures.add(f.getSignatureVerification().getSignature()); } return signatures; } /** * Return true if the message contained at least one signature. * * Note: This method does not reflect, whether the signature on the message is correct. * Use {@link #isVerified()} instead to determine, if the message carries a verifiable signature. * * @return true if message contains at least one unverified or verified signature, false otherwise. */ public boolean isSigned() { return !getSignatures().isEmpty(); } /** * Return a map of all verified signatures on the message. * The map contains verified signatures as value, with the {@link SubkeyIdentifier} of the key that was used to verify * the signature as the maps keys. * * @return verified detached and one-pass signatures */ public Map getVerifiedSignatures() { Map verifiedSignatures = new ConcurrentHashMap<>(); for (SignatureVerification detachedSignature : getVerifiedDetachedSignatures()) { verifiedSignatures.put(detachedSignature.getSigningKey(), detachedSignature.getSignature()); } for (SignatureVerification inbandSignatures : verifiedInbandSignatures) { verifiedSignatures.put(inbandSignatures.getSigningKey(), inbandSignatures.getSignature()); } return verifiedSignatures; } public List getVerifiedInbandSignatures() { return verifiedInbandSignatures; } public List getVerifiedDetachedSignatures() { return verifiedDetachedSignatures; } public List getInvalidInbandSignatures() { return invalidInbandSignatures; } public List getInvalidDetachedSignatures() { return invalidDetachedSignatures; } /** * Return true, if the message is signed and at least one signature on the message was verified successfully. * * @return true if message is verified, false otherwise */ public boolean isVerified() { return !getVerifiedSignatures().isEmpty(); } /** * Return true, if the message contains at least one verified signature made by a key in the * given certificate. * * @param certificate certificate * @return true if message was signed by the certificate (and the signature is valid), false otherwise */ public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing certificate) { for (PGPPublicKey key : certificate) { OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(key); if (containsVerifiedSignatureFrom(fingerprint)) { return true; } } return false; } /** * Return true, if the message contains at least one valid signature made by the key with the given * fingerprint, false otherwise. * * The fingerprint might be of the signing subkey, or the primary key of the signing certificate. * * @param fingerprint fingerprint of primary key or signing subkey * @return true if validly signed, false otherwise */ public boolean containsVerifiedSignatureFrom(OpenPgpV4Fingerprint fingerprint) { for (SubkeyIdentifier verifiedSigningKey : getVerifiedSignatures().keySet()) { if (verifiedSigningKey.getPrimaryKeyFingerprint().equals(fingerprint) || verifiedSigningKey.getSubkeyFingerprint().equals(fingerprint)) { return true; } } return false; } /** * Return the name of the encrypted / signed file. * * @return file name */ public String getFileName() { return fileName; } /** * Return true, if the encrypted data is intended for your eyes only. * * @return true if for-your-eyes-only */ public boolean isForYourEyesOnly() { return PGPLiteralData.CONSOLE.equals(getFileName()); } /** * Return the modification date of the encrypted / signed file. * * @return modification date */ public Date getModificationDate() { return modificationDate; } /** * Return the encoding format of the encrypted / signed file. * * @return encoding */ public StreamEncoding getFileEncoding() { return fileEncoding; } public static Builder getBuilder() { return new Builder(); } public static class Builder { private final Set recipientFingerprints = new HashSet<>(); private SubkeyIdentifier decryptionKey; private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL; private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; private String fileName; private StreamEncoding fileEncoding; private Date modificationDate; private final List verifiedInbandSignatures = new ArrayList<>(); private final List verifiedDetachedSignatures = new ArrayList<>(); private final List invalidInbandSignatures = new ArrayList<>(); private final List invalidDetachedSignatures = new ArrayList<>(); public Builder addRecipientKeyId(Long keyId) { this.recipientFingerprints.add(keyId); return this; } public Builder setDecryptionKey(SubkeyIdentifier decryptionKey) { this.decryptionKey = decryptionKey; return this; } public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) { this.compressionAlgorithm = algorithm; return this; } public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) { this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; return this; } public Builder setFileName(@Nonnull String fileName) { this.fileName = fileName; return this; } public Builder setModificationDate(Date modificationDate) { this.modificationDate = modificationDate; return this; } public Builder setFileEncoding(StreamEncoding encoding) { this.fileEncoding = encoding; return this; } public OpenPgpMetadata build() { return new OpenPgpMetadata( recipientFingerprints, decryptionKey, symmetricKeyAlgorithm, compressionAlgorithm, verifiedInbandSignatures, invalidInbandSignatures, verifiedDetachedSignatures, invalidDetachedSignatures, fileName, modificationDate, fileEncoding); } public void addVerifiedInbandSignature(SignatureVerification signatureVerification) { this.verifiedInbandSignatures.add(signatureVerification); } public void addVerifiedDetachedSignature(SignatureVerification signatureVerification) { this.verifiedDetachedSignatures.add(signatureVerification); } public void addInvalidInbandSignature(SignatureVerification signatureVerification, SignatureValidationException e) { this.invalidInbandSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); } public void addInvalidDetachedSignature(SignatureVerification signatureVerification, SignatureValidationException e) { this.invalidDetachedSignatures.add(new SignatureVerification.Failure(signatureVerification, e)); } } }