2021-10-07 15:48:52 +02:00
|
|
|
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
package org.pgpainless.decryption_verification;
|
|
|
|
|
2021-06-16 15:38:02 +02:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
2021-06-15 17:08:40 +02:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.HashSet;
|
2021-06-16 15:38:02 +02:00
|
|
|
import java.util.List;
|
2021-06-15 17:08:40 +02:00
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Set;
|
|
|
|
import javax.annotation.Nonnull;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
|
2021-06-16 15:38:02 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPException;
|
2021-06-15 17:08:40 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
|
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
2021-06-16 15:38:02 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
2021-06-15 17:08:40 +02:00
|
|
|
import org.bouncycastle.openpgp.PGPSignature;
|
2021-11-02 12:12:29 +01:00
|
|
|
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
|
|
|
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
2021-06-15 17:08:40 +02:00
|
|
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
2021-06-16 15:49:43 +02:00
|
|
|
import org.pgpainless.signature.SignatureUtils;
|
2021-06-15 17:08:40 +02:00
|
|
|
import org.pgpainless.util.Passphrase;
|
2021-10-15 14:58:17 +02:00
|
|
|
import org.pgpainless.util.SessionKey;
|
2021-06-15 17:08:40 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Options for decryption and signature verification.
|
|
|
|
*/
|
|
|
|
public class ConsumerOptions {
|
|
|
|
|
2021-10-18 16:01:19 +02:00
|
|
|
|
2021-10-01 13:54:51 +02:00
|
|
|
private boolean ignoreMDCErrors = false;
|
2022-04-22 21:33:13 +02:00
|
|
|
private boolean forceNonOpenPgpData = false;
|
2021-10-01 13:54:51 +02:00
|
|
|
|
2021-08-17 14:47:07 +02:00
|
|
|
private Date verifyNotBefore = null;
|
|
|
|
private Date verifyNotAfter = new Date();
|
2021-06-15 17:08:40 +02:00
|
|
|
|
|
|
|
// Set of verification keys
|
2021-06-15 17:56:36 +02:00
|
|
|
private final Set<PGPPublicKeyRing> certificates = new HashSet<>();
|
|
|
|
private final Set<PGPSignature> detachedSignatures = new HashSet<>();
|
2021-06-15 17:08:40 +02:00
|
|
|
private MissingPublicKeyCallback missingCertificateCallback = null;
|
|
|
|
|
|
|
|
// Session key for decryption without passphrase/key
|
2021-10-15 14:58:17 +02:00
|
|
|
private SessionKey sessionKey = null;
|
2021-06-15 17:08:40 +02:00
|
|
|
|
|
|
|
private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
|
|
|
|
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
|
2021-10-18 16:01:19 +02:00
|
|
|
private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE;
|
2021-06-15 17:08:40 +02:00
|
|
|
|
2021-11-02 12:12:29 +01:00
|
|
|
private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy();
|
2021-06-15 17:08:40 +02:00
|
|
|
|
|
|
|
/**
|
2021-08-17 14:47:07 +02:00
|
|
|
* Consider signatures on the message made before the given timestamp invalid.
|
|
|
|
* Null means no limitation.
|
2021-06-16 15:49:43 +02:00
|
|
|
*
|
2021-06-15 17:08:40 +02:00
|
|
|
* @param timestamp timestamp
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions verifyNotBefore(Date timestamp) {
|
|
|
|
this.verifyNotBefore = timestamp;
|
2021-08-17 14:47:07 +02:00
|
|
|
return this;
|
2021-06-15 17:08:40 +02:00
|
|
|
}
|
|
|
|
|
2021-08-17 14:47:07 +02:00
|
|
|
/**
|
|
|
|
* Return the earliest creation date on which signatures on the message are considered valid.
|
|
|
|
* Signatures made earlier than this date are considered invalid.
|
|
|
|
*
|
|
|
|
* @return earliest allowed signature creation date or null
|
|
|
|
*/
|
|
|
|
public @Nullable Date getVerifyNotBefore() {
|
2021-06-15 17:56:36 +02:00
|
|
|
return verifyNotBefore;
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
/**
|
2021-08-17 14:47:07 +02:00
|
|
|
* Consider signatures on the message made after the given timestamp invalid.
|
|
|
|
* Null means no limitation.
|
2021-06-16 15:49:43 +02:00
|
|
|
*
|
2021-06-15 17:08:40 +02:00
|
|
|
* @param timestamp timestamp
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions verifyNotAfter(Date timestamp) {
|
|
|
|
this.verifyNotAfter = timestamp;
|
2021-08-17 14:47:07 +02:00
|
|
|
return this;
|
2021-06-15 17:08:40 +02:00
|
|
|
}
|
|
|
|
|
2021-08-17 14:47:07 +02:00
|
|
|
/**
|
|
|
|
* Return the latest possible creation date on which signatures made on the message are considered valid.
|
|
|
|
* Signatures made later than this date are considered invalid.
|
|
|
|
*
|
|
|
|
* @return Latest possible creation date or null.
|
|
|
|
*/
|
2021-06-15 17:56:36 +02:00
|
|
|
public Date getVerifyNotAfter() {
|
|
|
|
return verifyNotAfter;
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
/**
|
|
|
|
* Add a certificate (public key ring) for signature verification.
|
|
|
|
*
|
|
|
|
* @param verificationCert certificate for signature verification
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) {
|
|
|
|
this.certificates.add(verificationCert);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a set of certificates (public key rings) for signature verification.
|
|
|
|
*
|
|
|
|
* @param verificationCerts certificates for signature verification
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addVerificationCerts(PGPPublicKeyRingCollection verificationCerts) {
|
|
|
|
for (PGPPublicKeyRing certificate : verificationCerts) {
|
|
|
|
addVerificationCert(certificate);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-16 15:38:02 +02:00
|
|
|
public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException {
|
2021-06-16 15:49:43 +02:00
|
|
|
List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream);
|
2021-06-16 15:38:02 +02:00
|
|
|
return addVerificationOfDetachedSignatures(signatures);
|
|
|
|
}
|
|
|
|
|
|
|
|
public ConsumerOptions addVerificationOfDetachedSignatures(List<PGPSignature> detachedSignatures) {
|
|
|
|
for (PGPSignature signature : detachedSignatures) {
|
|
|
|
addVerificationOfDetachedSignature(signature);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
/**
|
|
|
|
* Add a detached signature for the signature verification process.
|
|
|
|
*
|
|
|
|
* @param detachedSignature detached signature
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addVerificationOfDetachedSignature(PGPSignature detachedSignature) {
|
|
|
|
detachedSignatures.add(detachedSignature);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a callback that's used when a certificate (public key) is missing for signature verification.
|
|
|
|
*
|
|
|
|
* @param callback callback
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions setMissingCertificateCallback(MissingPublicKeyCallback callback) {
|
|
|
|
this.missingCertificateCallback = callback;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attempt decryption using a session key.
|
|
|
|
*
|
|
|
|
* Note: PGPainless does not yet support decryption with session keys.
|
|
|
|
*
|
|
|
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a>
|
|
|
|
*
|
|
|
|
* @param sessionKey session key
|
|
|
|
* @return options
|
|
|
|
*/
|
2021-10-15 14:58:17 +02:00
|
|
|
public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) {
|
2021-06-15 17:08:40 +02:00
|
|
|
this.sessionKey = sessionKey;
|
2021-10-15 14:58:17 +02:00
|
|
|
return this;
|
2021-06-15 17:08:40 +02:00
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
/**
|
|
|
|
* Return the session key.
|
|
|
|
*
|
|
|
|
* @return session key or null
|
|
|
|
*/
|
2021-10-15 14:58:17 +02:00
|
|
|
public @Nullable SessionKey getSessionKey() {
|
|
|
|
return sessionKey;
|
2021-06-15 17:56:36 +02:00
|
|
|
}
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
/**
|
|
|
|
* Add a key for message decryption.
|
|
|
|
* The key is expected to be unencrypted.
|
|
|
|
*
|
|
|
|
* @param key unencrypted key
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key) {
|
|
|
|
return addDecryptionKey(key, SecretKeyRingProtector.unprotectedKeys());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a key for message decryption. If the key is encrypted, the {@link SecretKeyRingProtector} is used to decrypt it
|
|
|
|
* when needed.
|
|
|
|
*
|
|
|
|
* @param key key
|
|
|
|
* @param keyRingProtector protector for the secret key
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addDecryptionKey(@Nonnull PGPSecretKeyRing key, @Nonnull SecretKeyRingProtector keyRingProtector) {
|
|
|
|
decryptionKeys.put(key, keyRingProtector);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-16 15:38:02 +02:00
|
|
|
/**
|
|
|
|
* Add the keys in the provided key collection for message decryption.
|
|
|
|
*
|
|
|
|
* @param keys key collection
|
|
|
|
* @param keyRingProtector protector for encrypted secret keys
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addDecryptionKeys(@Nonnull PGPSecretKeyRingCollection keys, @Nonnull SecretKeyRingProtector keyRingProtector) {
|
|
|
|
for (PGPSecretKeyRing key : keys) {
|
|
|
|
addDecryptionKey(key, keyRingProtector);
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:08:40 +02:00
|
|
|
/**
|
|
|
|
* Add a passphrase for message decryption.
|
2021-10-18 16:19:12 +02:00
|
|
|
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase.
|
|
|
|
*
|
|
|
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.7">Symmetrically Encrypted Data Packet</a>
|
2021-06-15 17:08:40 +02:00
|
|
|
*
|
|
|
|
* @param passphrase passphrase
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) {
|
|
|
|
decryptionPassphrases.add(passphrase);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
|
2021-06-15 17:08:40 +02:00
|
|
|
return Collections.unmodifiableSet(decryptionKeys.keySet());
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
public @Nonnull Set<Passphrase> getDecryptionPassphrases() {
|
2021-06-15 17:08:40 +02:00
|
|
|
return Collections.unmodifiableSet(decryptionPassphrases);
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
public @Nonnull Set<PGPPublicKeyRing> getCertificates() {
|
2021-06-15 17:08:40 +02:00
|
|
|
return Collections.unmodifiableSet(certificates);
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() {
|
2021-06-15 17:08:40 +02:00
|
|
|
return missingCertificateCallback;
|
|
|
|
}
|
|
|
|
|
2021-09-15 16:33:03 +02:00
|
|
|
public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
|
2021-06-15 17:08:40 +02:00
|
|
|
return decryptionKeys.get(decryptionKeyRing);
|
|
|
|
}
|
|
|
|
|
2021-06-15 17:56:36 +02:00
|
|
|
public @Nonnull Set<PGPSignature> getDetachedSignatures() {
|
2021-06-15 17:08:40 +02:00
|
|
|
return Collections.unmodifiableSet(detachedSignatures);
|
|
|
|
}
|
2021-10-01 13:54:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* By default, PGPainless will require encrypted messages to make use of SEIP data packets.
|
|
|
|
* Those are Symmetrically Encrypted Integrity Protected Data packets.
|
|
|
|
* Symmetrically Encrypted Data Packets without integrity protection are rejected by default.
|
|
|
|
* Furthermore, PGPainless will throw an exception if verification of the MDC error detection code of the SEIP packet
|
|
|
|
* fails.
|
|
|
|
*
|
|
|
|
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an attack or data corruption.
|
|
|
|
*
|
|
|
|
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data without integrity protection.
|
|
|
|
* If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will
|
|
|
|
* <ul>
|
|
|
|
* <li>not throw exceptions for SEIP packets with tampered ciphertext</li>
|
|
|
|
* <li>not throw exceptions for SEIP packets with tampered MDC</li>
|
|
|
|
* <li>not throw exceptions for MDCs with bad CTB</li>
|
|
|
|
* <li>not throw exceptions for MDCs with bad length</li>
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
|
|
|
|
*
|
|
|
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.13">Sym. Encrypted Integrity Protected Data Packet</a>
|
|
|
|
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
@Deprecated
|
|
|
|
public ConsumerOptions setIgnoreMDCErrors(boolean ignoreMDCErrors) {
|
|
|
|
this.ignoreMDCErrors = ignoreMDCErrors;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:19:12 +02:00
|
|
|
/**
|
|
|
|
* Return true, if PGPainless is ignoring MDC errors.
|
|
|
|
*
|
|
|
|
* @return ignore mdc errors
|
|
|
|
*/
|
2021-10-01 13:54:51 +02:00
|
|
|
boolean isIgnoreMDCErrors() {
|
|
|
|
return ignoreMDCErrors;
|
|
|
|
}
|
2021-10-18 16:01:19 +02:00
|
|
|
|
2022-04-22 21:33:13 +02:00
|
|
|
/**
|
|
|
|
* Force PGPainless to handle the data provided by the {@link InputStream} as non-OpenPGP data.
|
|
|
|
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data.
|
|
|
|
*
|
|
|
|
* @return options
|
|
|
|
*/
|
|
|
|
public ConsumerOptions forceNonOpenPgpData() {
|
|
|
|
this.forceNonOpenPgpData = true;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean isForceNonOpenPgpData() {
|
|
|
|
return forceNonOpenPgpData;
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:19:12 +02:00
|
|
|
/**
|
|
|
|
* Specify the {@link MissingKeyPassphraseStrategy}.
|
|
|
|
* This strategy defines, how missing passphrases for unlocking secret keys are handled.
|
|
|
|
* In interactive mode ({@link MissingKeyPassphraseStrategy#INTERACTIVE}) PGPainless will try to obtain missing
|
|
|
|
* passphrases for secret keys via the {@link SecretKeyRingProtector SecretKeyRingProtectors}
|
|
|
|
* {@link org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider} callback.
|
|
|
|
*
|
|
|
|
* In non-interactice mode ({@link MissingKeyPassphraseStrategy#THROW_EXCEPTION}, PGPainless will instead
|
|
|
|
* throw a {@link org.pgpainless.exception.MissingPassphraseException} containing the ids of all keys for which
|
|
|
|
* there are missing passphrases.
|
|
|
|
*
|
|
|
|
* @param strategy strategy
|
|
|
|
* @return options
|
|
|
|
*/
|
2021-10-18 16:01:19 +02:00
|
|
|
public ConsumerOptions setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy strategy) {
|
|
|
|
this.missingKeyPassphraseStrategy = strategy;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-10-18 16:19:12 +02:00
|
|
|
/**
|
|
|
|
* Return the currently configured {@link MissingKeyPassphraseStrategy}.
|
|
|
|
*
|
|
|
|
* @return missing key passphrase strategy
|
|
|
|
*/
|
2021-10-18 16:01:19 +02:00
|
|
|
MissingKeyPassphraseStrategy getMissingKeyPassphraseStrategy() {
|
|
|
|
return missingKeyPassphraseStrategy;
|
|
|
|
}
|
2021-11-02 12:12:29 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a custom multi-pass strategy for processing cleartext-signed messages.
|
|
|
|
* Uses {@link InMemoryMultiPassStrategy} by default.
|
|
|
|
*
|
|
|
|
* @param multiPassStrategy multi-pass caching strategy
|
|
|
|
* @return builder
|
|
|
|
*/
|
|
|
|
public ConsumerOptions setMultiPassStrategy(@Nonnull MultiPassStrategy multiPassStrategy) {
|
|
|
|
this.multiPassStrategy = multiPassStrategy;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the currently configured {@link MultiPassStrategy}.
|
|
|
|
* Defaults to {@link InMemoryMultiPassStrategy}.
|
|
|
|
*
|
|
|
|
* @return multi-pass strategy
|
|
|
|
*/
|
|
|
|
public MultiPassStrategy getMultiPassStrategy() {
|
|
|
|
return multiPassStrategy;
|
|
|
|
}
|
2021-06-15 17:08:40 +02:00
|
|
|
}
|