mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-19 10:52:05 +01:00
WIP: Kotlin conversion: ConsumerOptions
This commit is contained in:
parent
cc63095ab0
commit
4a19e6ca20
5 changed files with 417 additions and 553 deletions
|
@ -1,508 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
|
||||||
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
|
||||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
|
||||||
import org.pgpainless.util.Passphrase;
|
|
||||||
import org.pgpainless.util.SessionKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for decryption and signature verification.
|
|
||||||
*/
|
|
||||||
public class ConsumerOptions {
|
|
||||||
|
|
||||||
private boolean ignoreMDCErrors = false;
|
|
||||||
private boolean forceNonOpenPgpData = false;
|
|
||||||
|
|
||||||
private Date verifyNotBefore = null;
|
|
||||||
private Date verifyNotAfter = new Date();
|
|
||||||
|
|
||||||
private final CertificateSource certificates = new CertificateSource();
|
|
||||||
private final Set<PGPSignature> detachedSignatures = new HashSet<>();
|
|
||||||
private MissingPublicKeyCallback missingCertificateCallback = null;
|
|
||||||
|
|
||||||
// Session key for decryption without passphrase/key
|
|
||||||
private SessionKey sessionKey = null;
|
|
||||||
private final Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> customPublicKeyDataDecryptorFactories =
|
|
||||||
new HashMap<>();
|
|
||||||
|
|
||||||
private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
|
|
||||||
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
|
|
||||||
private MissingKeyPassphraseStrategy missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE;
|
|
||||||
|
|
||||||
private MultiPassStrategy multiPassStrategy = new InMemoryMultiPassStrategy();
|
|
||||||
|
|
||||||
public static ConsumerOptions get() {
|
|
||||||
return new ConsumerOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consider signatures on the message made before the given timestamp invalid.
|
|
||||||
* Null means no limitation.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions verifyNotBefore(Date timestamp) {
|
|
||||||
this.verifyNotBefore = timestamp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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() {
|
|
||||||
return verifyNotBefore;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consider signatures on the message made after the given timestamp invalid.
|
|
||||||
* Null means no limitation.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions verifyNotAfter(Date timestamp) {
|
|
||||||
this.verifyNotAfter = timestamp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public Date getVerifyNotAfter() {
|
|
||||||
return verifyNotAfter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a certificate (public key ring) for signature verification.
|
|
||||||
*
|
|
||||||
* @param verificationCert certificate for signature verification
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) {
|
|
||||||
this.certificates.addCertificate(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add some detached signatures from the given {@link InputStream} for verification.
|
|
||||||
*
|
|
||||||
* @param signatureInputStream input stream of detached signatures
|
|
||||||
* @return options
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws PGPException in case of an OpenPGP error
|
|
||||||
*/
|
|
||||||
public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream)
|
|
||||||
throws IOException, PGPException {
|
|
||||||
List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream);
|
|
||||||
return addVerificationOfDetachedSignatures(signatures);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add some detached signatures for verification.
|
|
||||||
*
|
|
||||||
* @param detachedSignatures detached signatures
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions addVerificationOfDetachedSignatures(List<PGPSignature> detachedSignatures) {
|
|
||||||
for (PGPSignature signature : detachedSignatures) {
|
|
||||||
addVerificationOfDetachedSignature(signature);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) {
|
|
||||||
this.sessionKey = sessionKey;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the session key.
|
|
||||||
*
|
|
||||||
* @return session key or null
|
|
||||||
*/
|
|
||||||
public @Nullable SessionKey getSessionKey() {
|
|
||||||
return sessionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a passphrase for message decryption.
|
|
||||||
* 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>
|
|
||||||
*
|
|
||||||
* @param passphrase passphrase
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) {
|
|
||||||
decryptionPassphrases.add(passphrase);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a custom {@link PublicKeyDataDecryptorFactory} which enable decryption of messages, e.g. using
|
|
||||||
* hardware-backed secret keys.
|
|
||||||
* (See e.g. {@link org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory}).
|
|
||||||
*
|
|
||||||
* @param factory decryptor factory
|
|
||||||
* @return options
|
|
||||||
*/
|
|
||||||
public ConsumerOptions addCustomDecryptorFactory(@Nonnull CustomPublicKeyDataDecryptorFactory factory) {
|
|
||||||
this.customPublicKeyDataDecryptorFactories.put(factory.getSubkeyIdentifier(), factory);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the custom {@link PublicKeyDataDecryptorFactory PublicKeyDataDecryptorFactories} that were
|
|
||||||
* set by the user.
|
|
||||||
* These factories can be used to decrypt session keys using a custom logic.
|
|
||||||
*
|
|
||||||
* @return custom decryptor factories
|
|
||||||
*/
|
|
||||||
Map<SubkeyIdentifier, PublicKeyDataDecryptorFactory> getCustomDecryptorFactories() {
|
|
||||||
return new HashMap<>(customPublicKeyDataDecryptorFactories);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the set of available decryption keys.
|
|
||||||
*
|
|
||||||
* @return decryption keys
|
|
||||||
*/
|
|
||||||
public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
|
|
||||||
return Collections.unmodifiableSet(decryptionKeys.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the set of available message decryption passphrases.
|
|
||||||
*
|
|
||||||
* @return decryption passphrases
|
|
||||||
*/
|
|
||||||
public @Nonnull Set<Passphrase> getDecryptionPassphrases() {
|
|
||||||
return Collections.unmodifiableSet(decryptionPassphrases);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the explicitly set verification certificates.
|
|
||||||
*
|
|
||||||
* @deprecated use {@link #getCertificateSource()} instead.
|
|
||||||
* @return verification certs
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public @Nonnull Set<PGPPublicKeyRing> getCertificates() {
|
|
||||||
return certificates.getExplicitCertificates();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object holding available certificates for signature verification.
|
|
||||||
*
|
|
||||||
* @return certificate source
|
|
||||||
*/
|
|
||||||
public @Nonnull CertificateSource getCertificateSource() {
|
|
||||||
return certificates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the callback that gets called when a certificate for signature verification is missing.
|
|
||||||
* This method might return <pre>null</pre> if the users hasn't set a callback.
|
|
||||||
*
|
|
||||||
* @return missing public key callback
|
|
||||||
*/
|
|
||||||
public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() {
|
|
||||||
return missingCertificateCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the {@link SecretKeyRingProtector} for the given {@link PGPSecretKeyRing}.
|
|
||||||
*
|
|
||||||
* @param decryptionKeyRing secret key
|
|
||||||
* @return protector for that particular secret key
|
|
||||||
*/
|
|
||||||
public @Nonnull SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
|
|
||||||
return decryptionKeys.get(decryptionKeyRing);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the set of detached signatures the user provided.
|
|
||||||
*
|
|
||||||
* @return detached signatures
|
|
||||||
*/
|
|
||||||
public @Nonnull Set<PGPSignature> getDetachedSignatures() {
|
|
||||||
return Collections.unmodifiableSet(detachedSignatures);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if PGPainless is ignoring MDC errors.
|
|
||||||
*
|
|
||||||
* @return ignore mdc errors
|
|
||||||
*/
|
|
||||||
boolean isIgnoreMDCErrors() {
|
|
||||||
return ignoreMDCErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
|
|
||||||
*
|
|
||||||
* @return true if non-OpenPGP data is forced
|
|
||||||
*/
|
|
||||||
boolean isForceNonOpenPgpData() {
|
|
||||||
return forceNonOpenPgpData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public ConsumerOptions setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy strategy) {
|
|
||||||
this.missingKeyPassphraseStrategy = strategy;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the currently configured {@link MissingKeyPassphraseStrategy}.
|
|
||||||
*
|
|
||||||
* @return missing key passphrase strategy
|
|
||||||
*/
|
|
||||||
MissingKeyPassphraseStrategy getMissingKeyPassphraseStrategy() {
|
|
||||||
return missingKeyPassphraseStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source for OpenPGP certificates.
|
|
||||||
* When verifying signatures on a message, this object holds available signer certificates.
|
|
||||||
*/
|
|
||||||
public static class CertificateSource {
|
|
||||||
|
|
||||||
private Set<PGPPublicKeyRing> explicitCertificates = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a certificate as verification cert explicitly.
|
|
||||||
*
|
|
||||||
* @param certificate certificate
|
|
||||||
*/
|
|
||||||
public void addCertificate(PGPPublicKeyRing certificate) {
|
|
||||||
this.explicitCertificates.add(certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the set of explicitly set verification certificates.
|
|
||||||
* @return explicitly set verification certs
|
|
||||||
*/
|
|
||||||
public Set<PGPPublicKeyRing> getExplicitCertificates() {
|
|
||||||
return Collections.unmodifiableSet(explicitCertificates);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a certificate which contains a subkey with the given keyId.
|
|
||||||
* This method first checks all explicitly set verification certs and if no cert is found it consults
|
|
||||||
* the certificate stores.
|
|
||||||
*
|
|
||||||
* @param keyId key id
|
|
||||||
* @return certificate
|
|
||||||
*/
|
|
||||||
public PGPPublicKeyRing getCertificate(long keyId) {
|
|
||||||
|
|
||||||
for (PGPPublicKeyRing cert : explicitCertificates) {
|
|
||||||
if (cert.getPublicKey(keyId) != null) {
|
|
||||||
return cert;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,394 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.*
|
||||||
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||||
|
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy
|
||||||
|
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
|
import org.pgpainless.signature.SignatureUtils
|
||||||
|
import org.pgpainless.util.Passphrase
|
||||||
|
import org.pgpainless.util.SessionKey
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for decryption and signature verification.
|
||||||
|
*/
|
||||||
|
class ConsumerOptions {
|
||||||
|
|
||||||
|
private var ignoreMDCErrors = false
|
||||||
|
private var forceNonOpenPgpData = false
|
||||||
|
private var verifyNotBefore: Date? = null
|
||||||
|
private var verifyNotAfter: Date? = Date()
|
||||||
|
|
||||||
|
private val certificates = CertificateSource()
|
||||||
|
private val detachedSignatures = mutableSetOf<PGPSignature>()
|
||||||
|
private var missingCertificateCallback: MissingPublicKeyCallback? = null
|
||||||
|
|
||||||
|
private var sessionKey: SessionKey? = null
|
||||||
|
private val customDecryptorFactories = mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
|
||||||
|
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
|
||||||
|
private val decryptionPassphrases = mutableSetOf<Passphrase>()
|
||||||
|
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
|
||||||
|
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consider signatures on the message made before the given timestamp invalid.
|
||||||
|
* Null means no limitation.
|
||||||
|
*
|
||||||
|
* @param timestamp timestamp
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun verifyNotBefore(timestamp: Date?): ConsumerOptions = apply {
|
||||||
|
this.verifyNotBefore = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVerifyNotBefore() = verifyNotBefore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consider signatures on the message made after the given timestamp invalid.
|
||||||
|
* Null means no limitation.
|
||||||
|
*
|
||||||
|
* @param timestamp timestamp
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun verifyNotAfter(timestamp: Date?): ConsumerOptions = apply {
|
||||||
|
this.verifyNotAfter = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVerifyNotAfter() = verifyNotAfter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a certificate (public key ring) for signature verification.
|
||||||
|
*
|
||||||
|
* @param verificationCert certificate for signature verification
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addVerificationCert(verificationCert: PGPPublicKeyRing): ConsumerOptions = apply {
|
||||||
|
this.certificates.addCertificate(verificationCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a set of certificates (public key rings) for signature verification.
|
||||||
|
*
|
||||||
|
* @param verificationCerts certificates for signature verification
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply {
|
||||||
|
for (cert in verificationCerts) {
|
||||||
|
addVerificationCert(cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add some detached signatures from the given [InputStream] for verification.
|
||||||
|
*
|
||||||
|
* @param signatureInputStream input stream of detached signatures
|
||||||
|
* @return options
|
||||||
|
*
|
||||||
|
* @throws IOException in case of an IO error
|
||||||
|
* @throws PGPException in case of an OpenPGP error
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class, PGPException::class)
|
||||||
|
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply {
|
||||||
|
val signatures = SignatureUtils.readSignatures(signatureInputStream)
|
||||||
|
addVerificationOfDetachedSignatures(signatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add some detached signatures for verification.
|
||||||
|
*
|
||||||
|
* @param detachedSignatures detached signatures
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addVerificationOfDetachedSignatures(detachedSignatures: List<PGPSignature>): ConsumerOptions = apply {
|
||||||
|
for (signature in detachedSignatures) {
|
||||||
|
addVerificationOfDetachedSignature(signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a detached signature for the signature verification process.
|
||||||
|
*
|
||||||
|
* @param detachedSignature detached signature
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply {
|
||||||
|
detachedSignatures.add(detachedSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDetachedSignatures() = detachedSignatures.toList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a callback that's used when a certificate (public key) is missing for signature verification.
|
||||||
|
*
|
||||||
|
* @param callback callback
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun setMissingCertificateCallback(callback: MissingPublicKeyCallback): ConsumerOptions = apply {
|
||||||
|
this.missingCertificateCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt decryption using a session key.
|
||||||
|
*
|
||||||
|
* Note: PGPainless does not yet support decryption with session keys.
|
||||||
|
*
|
||||||
|
* See [RFC4880 on Session Keys](https://datatracker.ietf.org/doc/html/rfc4880#section-2.1)
|
||||||
|
*
|
||||||
|
* @param sessionKey session key
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun setSessionKey(sessionKey: SessionKey) = apply { this.sessionKey = sessionKey }
|
||||||
|
|
||||||
|
fun getSessionKey() = sessionKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector]
|
||||||
|
* is used to decrypt it when needed.
|
||||||
|
*
|
||||||
|
* @param key key
|
||||||
|
* @param keyRingProtector protector for the secret key
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun addDecryptionKey(key: PGPSecretKeyRing,
|
||||||
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
||||||
|
decryptionKeys[key] = protector
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the keys in the provided key collection for message decryption.
|
||||||
|
*
|
||||||
|
* @param keys key collection
|
||||||
|
* @param keyRingProtector protector for encrypted secret keys
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun addDecryptionKeys(keys: PGPSecretKeyRingCollection,
|
||||||
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
||||||
|
for (key in keys) {
|
||||||
|
addDecryptionKey(key, protector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a passphrase for message decryption.
|
||||||
|
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase.
|
||||||
|
*
|
||||||
|
* See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
||||||
|
*
|
||||||
|
* @param passphrase passphrase
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addDecryptionPassphrase(passphrase: Passphrase) = apply {
|
||||||
|
decryptionPassphrases.add(passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
|
||||||
|
* hardware-backed secret keys.
|
||||||
|
* (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
|
||||||
|
*
|
||||||
|
* @param factory decryptor factory
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun addCustomDecryptorFactory(factory: CustomPublicKeyDataDecryptorFactory) = apply {
|
||||||
|
customDecryptorFactories[factory.subkeyIdentifier] = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the custom [PublicKeyDataDecryptorFactory] that were
|
||||||
|
* set by the user.
|
||||||
|
* These factories can be used to decrypt session keys using a custom logic.
|
||||||
|
*
|
||||||
|
* @return custom decryptor factories
|
||||||
|
*/
|
||||||
|
fun getCustomDecryptorFactories() = customDecryptorFactories.toMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the set of available decryption keys.
|
||||||
|
*
|
||||||
|
* @return decryption keys
|
||||||
|
*/
|
||||||
|
fun getDecryptionKeys() = decryptionKeys.keys.toSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the set of available message decryption passphrases.
|
||||||
|
*
|
||||||
|
* @return decryption passphrases
|
||||||
|
*/
|
||||||
|
fun getDecryptionPassphrases() = decryptionPassphrases.toSet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object holding available certificates for signature verification.
|
||||||
|
*
|
||||||
|
* @return certificate source
|
||||||
|
*/
|
||||||
|
fun getCertificateSource() = certificates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the callback that gets called when a certificate for signature verification is missing.
|
||||||
|
* This method might return `null` if the users hasn't set a callback.
|
||||||
|
*
|
||||||
|
* @return missing public key callback
|
||||||
|
*/
|
||||||
|
fun getMissingCertificateCallback() = missingCertificateCallback
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the [SecretKeyRingProtector] for the given [PGPSecretKeyRing].
|
||||||
|
*
|
||||||
|
* @param decryptionKeyRing secret key
|
||||||
|
* @return protector for that particular secret key
|
||||||
|
*/
|
||||||
|
fun getSecretKeyProtector(decryptionKeyRing: PGPSecretKeyRing): SecretKeyRingProtector? {
|
||||||
|
return decryptionKeys[decryptionKeyRing]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* * not throw exceptions for SEIP packets with tampered ciphertext
|
||||||
|
* * not throw exceptions for SEIP packets with tampered MDC
|
||||||
|
* * not throw exceptions for MDCs with bad CTB
|
||||||
|
* * not throw exceptions for MDCs with bad length
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
|
||||||
|
*
|
||||||
|
* See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
|
||||||
|
*
|
||||||
|
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
@Deprecated("Ignoring non-integrity-protected packets is discouraged.")
|
||||||
|
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors }
|
||||||
|
|
||||||
|
fun isIgnoreMDCErrors() = ignoreMDCErrors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data.
|
||||||
|
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data.
|
||||||
|
*
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun forceNonOpenPgpData(): ConsumerOptions = apply {
|
||||||
|
this.forceNonOpenPgpData = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
|
||||||
|
*
|
||||||
|
* @return true if non-OpenPGP data is forced
|
||||||
|
*/
|
||||||
|
fun isForceNonOpenPgpData() = forceNonOpenPgpData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the [MissingKeyPassphraseStrategy].
|
||||||
|
* This strategy defines, how missing passphrases for unlocking secret keys are handled.
|
||||||
|
* In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
|
||||||
|
* passphrases for secret keys via the [SecretKeyRingProtector]
|
||||||
|
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback.
|
||||||
|
*
|
||||||
|
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead
|
||||||
|
* throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which
|
||||||
|
* there are missing passphrases.
|
||||||
|
*
|
||||||
|
* @param strategy strategy
|
||||||
|
* @return options
|
||||||
|
*/
|
||||||
|
fun setMissingKeyPassphraseStrategy(strategy: MissingKeyPassphraseStrategy): ConsumerOptions {
|
||||||
|
this.missingKeyPassphraseStrategy = strategy
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the currently configured [MissingKeyPassphraseStrategy].
|
||||||
|
*
|
||||||
|
* @return missing key passphrase strategy
|
||||||
|
*/
|
||||||
|
fun getMissingKeyPassphraseStrategy(): MissingKeyPassphraseStrategy {
|
||||||
|
return missingKeyPassphraseStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom multi-pass strategy for processing cleartext-signed messages.
|
||||||
|
* Uses [InMemoryMultiPassStrategy] by default.
|
||||||
|
*
|
||||||
|
* @param multiPassStrategy multi-pass caching strategy
|
||||||
|
* @return builder
|
||||||
|
*/
|
||||||
|
fun setMultiPassStrategy(multiPassStrategy: MultiPassStrategy): ConsumerOptions {
|
||||||
|
this.multiPassStrategy = multiPassStrategy
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the currently configured [MultiPassStrategy].
|
||||||
|
* Defaults to [InMemoryMultiPassStrategy].
|
||||||
|
*
|
||||||
|
* @return multi-pass strategy
|
||||||
|
*/
|
||||||
|
fun getMultiPassStrategy(): MultiPassStrategy {
|
||||||
|
return multiPassStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source for OpenPGP certificates.
|
||||||
|
* When verifying signatures on a message, this object holds available signer certificates.
|
||||||
|
*/
|
||||||
|
class CertificateSource {
|
||||||
|
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a certificate as verification cert explicitly.
|
||||||
|
*
|
||||||
|
* @param certificate certificate
|
||||||
|
*/
|
||||||
|
fun addCertificate(certificate: PGPPublicKeyRing) {
|
||||||
|
explicitCertificates.add(certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the set of explicitly set verification certificates.
|
||||||
|
* @return explicitly set verification certs
|
||||||
|
*/
|
||||||
|
fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
|
||||||
|
return explicitCertificates.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a certificate which contains a subkey with the given keyId.
|
||||||
|
* This method first checks all explicitly set verification certs and if no cert is found it consults
|
||||||
|
* the certificate stores.
|
||||||
|
*
|
||||||
|
* @param keyId key id
|
||||||
|
* @return certificate
|
||||||
|
*/
|
||||||
|
fun getCertificate(keyId: Long): PGPPublicKeyRing? {
|
||||||
|
return explicitCertificates.firstOrNull { it.getPublicKey(keyId) != null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun get() = ConsumerOptions()
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ class OpenPgpMessageInputStream(
|
||||||
|
|
||||||
// Add detached signatures only on the outermost OpenPgpMessageInputStream
|
// Add detached signatures only on the outermost OpenPgpMessageInputStream
|
||||||
if (metadata is Message) {
|
if (metadata is Message) {
|
||||||
signatures.addDetachedSignatures(options.detachedSignatures)
|
signatures.addDetachedSignatures(options.getDetachedSignatures())
|
||||||
}
|
}
|
||||||
|
|
||||||
when(type) {
|
when(type) {
|
||||||
|
@ -67,7 +67,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.cleartext_signed -> {
|
Type.cleartext_signed -> {
|
||||||
val multiPassStrategy = options.multiPassStrategy
|
val multiPassStrategy = options.getMultiPassStrategy()
|
||||||
val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
||||||
inputStream, multiPassStrategy.messageOutputStream)
|
inputStream, multiPassStrategy.messageOutputStream)
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class OpenPgpMessageInputStream(
|
||||||
signatures.addDetachedSignature(signature)
|
signatures.addDetachedSignature(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
options.forceNonOpenPgpData()
|
options.isForceNonOpenPgpData()
|
||||||
nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ class OpenPgpMessageInputStream(
|
||||||
val encDataList = packetInputStream!!.readEncryptedDataList()
|
val encDataList = packetInputStream!!.readEncryptedDataList()
|
||||||
if (!encDataList.isIntegrityProtected) {
|
if (!encDataList.isIntegrityProtected) {
|
||||||
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
|
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
|
||||||
if (!options.isIgnoreMDCErrors) {
|
if (!options.isIgnoreMDCErrors()) {
|
||||||
throw MessageNotIntegrityProtectedException()
|
throw MessageNotIntegrityProtectedException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ class OpenPgpMessageInputStream(
|
||||||
" have an anonymous recipient.")
|
" have an anonymous recipient.")
|
||||||
|
|
||||||
// try custom decryptor factories
|
// try custom decryptor factories
|
||||||
for ((key, decryptorFactory) in options.customDecryptorFactories) {
|
for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) {
|
||||||
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
|
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
|
||||||
esks.pkesks.filter {
|
esks.pkesks.filter {
|
||||||
// find matching PKESK
|
// find matching PKESK
|
||||||
|
@ -237,8 +237,8 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
// try provided session key
|
// try provided session key
|
||||||
if (options.sessionKey != null) {
|
if (options.getSessionKey() != null) {
|
||||||
val sk = options.sessionKey!!
|
val sk = options.getSessionKey()!!
|
||||||
LOGGER.debug("Attempt decryption with provided session key.")
|
LOGGER.debug("Attempt decryption with provided session key.")
|
||||||
throwIfUnacceptable(sk.algorithm)
|
throwIfUnacceptable(sk.algorithm)
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
// try passwords
|
// try passwords
|
||||||
for (passphrase in options.decryptionPassphrases) {
|
for (passphrase in options.getDecryptionPassphrases()) {
|
||||||
for (skesk in esks.skesks) {
|
for (skesk in esks.skesks) {
|
||||||
LOGGER.debug("Attempt decryption with provided passphrase")
|
LOGGER.debug("Attempt decryption with provided passphrase")
|
||||||
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
|
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
|
||||||
|
@ -295,7 +295,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
|
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
|
||||||
val protector = options.getSecretKeyProtector(decryptionKeys)
|
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||||
if (!protector.hasPassphraseFor(keyId)) {
|
if (!protector.hasPassphraseFor(keyId)) {
|
||||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||||
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
||||||
|
@ -317,7 +317,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.")
|
LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.")
|
||||||
val protector = options.getSecretKeyProtector(decryptionKeys)
|
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||||
|
|
||||||
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
||||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||||
|
@ -332,7 +332,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||||
// Non-interactive mode: Throw an exception with all locked decryption keys
|
// Non-interactive mode: Throw an exception with all locked decryption keys
|
||||||
postponedDueToMissingPassphrase.map {
|
postponedDueToMissingPassphrase.map {
|
||||||
SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID)
|
SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID)
|
||||||
|
@ -340,7 +340,7 @@ class OpenPgpMessageInputStream(
|
||||||
if (it.isNotEmpty())
|
if (it.isNotEmpty())
|
||||||
throw MissingPassphraseException(it.toSet())
|
throw MissingPassphraseException(it.toSet())
|
||||||
}
|
}
|
||||||
} else if (options.missingKeyPassphraseStrategy == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
} else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||||
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
|
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
|
||||||
val keyId = secretKey.keyID
|
val keyId = secretKey.keyID
|
||||||
val decryptionKeys = getDecryptionKey(keyId)!!
|
val decryptionKeys = getDecryptionKey(keyId)!!
|
||||||
|
@ -532,7 +532,7 @@ class OpenPgpMessageInputStream(
|
||||||
return MessageMetadata((metadata as Message))
|
return MessageMetadata((metadata as Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.decryptionKeys.firstOrNull {
|
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull {
|
||||||
it.any {
|
it.any {
|
||||||
k -> k.keyID == keyId
|
k -> k.keyID == keyId
|
||||||
}.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any {
|
}.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any {
|
||||||
|
@ -543,7 +543,7 @@ class OpenPgpMessageInputStream(
|
||||||
private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
|
private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
|
||||||
val algorithm = pkesk.algorithm
|
val algorithm = pkesk.algorithm
|
||||||
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
||||||
options.decryptionKeys.forEach {
|
options.getDecryptionKeys().forEach {
|
||||||
val info = PGPainless.inspectKeyRing(it)
|
val info = PGPainless.inspectKeyRing(it)
|
||||||
for (key in info.decryptionSubkeys) {
|
for (key in info.decryptionSubkeys) {
|
||||||
if (key.algorithm == algorithm && info.isSecretKeyAvailable(key.keyID)) {
|
if (key.algorithm == algorithm && info.isSecretKeyAvailable(key.keyID)) {
|
||||||
|
@ -679,7 +679,7 @@ class OpenPgpMessageInputStream(
|
||||||
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
|
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter)
|
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
.verify(signature)
|
.verify(signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
|
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
|
||||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||||
|
@ -713,13 +713,13 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findCertificate(keyId: Long): PGPPublicKeyRing? {
|
fun findCertificate(keyId: Long): PGPPublicKeyRing? {
|
||||||
val cert = options.certificateSource.getCertificate(keyId)
|
val cert = options.getCertificateSource().getCertificate(keyId)
|
||||||
if (cert != null) {
|
if (cert != null) {
|
||||||
return cert
|
return cert
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.missingCertificateCallback != null) {
|
if (options.getMissingCertificateCallback() != null) {
|
||||||
return options.missingCertificateCallback!!.onMissingPublicKeyEncountered(keyId)
|
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(keyId)
|
||||||
}
|
}
|
||||||
return null // TODO: Missing cert for sig
|
return null // TODO: Missing cert for sig
|
||||||
}
|
}
|
||||||
|
@ -772,7 +772,7 @@ class OpenPgpMessageInputStream(
|
||||||
for (detached in detachedSignatures) {
|
for (detached in detachedSignatures) {
|
||||||
val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter)
|
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
.verify(detached.signature)
|
.verify(detached.signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||||
detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy)
|
detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy)
|
||||||
|
@ -787,7 +787,7 @@ class OpenPgpMessageInputStream(
|
||||||
for (prepended in prependedSignatures) {
|
for (prepended in prependedSignatures) {
|
||||||
val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.verifyNotBefore, options.verifyNotAfter)
|
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
.verify(prepended.signature)
|
.verify(prepended.signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||||
prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy)
|
prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy)
|
||||||
|
@ -877,7 +877,7 @@ class OpenPgpMessageInputStream(
|
||||||
val openPgpIn = OpenPgpInputStream(inputStream)
|
val openPgpIn = OpenPgpInputStream(inputStream)
|
||||||
openPgpIn.reset()
|
openPgpIn.reset()
|
||||||
|
|
||||||
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData) {
|
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
|
||||||
return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy)
|
return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.key.util.RevocationAttributes.Reason
|
import org.pgpainless.key.util.RevocationAttributes.Reason
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||||
import org.pgpainless.util.ArmorUtils
|
import org.pgpainless.util.ArmorUtils
|
||||||
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -127,6 +128,7 @@ class SignatureUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@Throws(IOException::class, PGPException::class)
|
||||||
fun readSignatures(inputStream: InputStream): List<PGPSignature> {
|
fun readSignatures(inputStream: InputStream): List<PGPSignature> {
|
||||||
return readSignatures(inputStream, MAX_ITERATIONS)
|
return readSignatures(inputStream, MAX_ITERATIONS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.pgpainless.decryption_verification;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
@ -59,29 +58,6 @@ public class MissingPassphraseForDecryptionTest {
|
||||||
message = out.toByteArray();
|
message = out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void invalidPostponedKeysStrategyTest() {
|
|
||||||
SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() {
|
|
||||||
@Override
|
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
|
||||||
fail("MUST NOT get called in if postponed key strategy is invalid.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPassphrase(Long keyId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ConsumerOptions options = new ConsumerOptions()
|
|
||||||
.setMissingKeyPassphraseStrategy(null) // illegal
|
|
||||||
.addDecryptionKey(secretKeys, SecretKeyRingProtector.defaultSecretKeyRingProtector(callback));
|
|
||||||
|
|
||||||
assertThrows(IllegalStateException.class, () -> PGPainless.decryptAndOrVerify()
|
|
||||||
.onInputStream(new ByteArrayInputStream(message))
|
|
||||||
.withOptions(options));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void interactiveStrategy() throws PGPException, IOException {
|
public void interactiveStrategy() throws PGPException, IOException {
|
||||||
// interactive callback
|
// interactive callback
|
||||||
|
|
Loading…
Reference in a new issue