From 70c4dcd1d2d46d4022c6c2cf63895894e55f9ec8 Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Tue, 15 Jun 2021 17:08:40 +0200
Subject: [PATCH 1/8] Begin introducing new Decryption API

---
 .../ConsumerOptions.java                      | 188 ++++++++++++++++++
 .../DecryptionBuilder.java                    |  32 ++-
 .../DecryptionBuilderInterface.java           |   2 +
 .../DecryptionStreamFactory.java              | 132 ++++++------
 .../SymmetricEncryptionTest.java              |   2 +-
 5 files changed, 293 insertions(+), 63 deletions(-)
 create mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
new file mode 100644
index 00000000..e9f6c916
--- /dev/null
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
@@ -0,0 +1,188 @@
+package org.pgpainless.decryption_verification;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+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.pgpainless.key.protection.SecretKeyRingProtector;
+import org.pgpainless.util.Passphrase;
+
+/**
+ * Options for decryption and signature verification.
+ */
+public class ConsumerOptions {
+
+    private Date verifyNotBefore;
+    private Date verifyNotAfter;
+
+    // Set of verification keys
+    private Set<PGPPublicKeyRing> certificates = new HashSet<>();
+    private Set<PGPSignature> detachedSignatures = new HashSet<>();
+    private MissingPublicKeyCallback missingCertificateCallback = null;
+
+    // Session key for decryption without passphrase/key
+    private byte[] sessionKey = null;
+
+    private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
+    private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
+
+
+    /**
+     * Consider signatures made before the given timestamp invalid.
+     *
+     * @param timestamp timestamp
+     * @return options
+     */
+    public ConsumerOptions verifyNotBefore(Date timestamp) {
+        this.verifyNotBefore = timestamp;
+        return this;
+    }
+
+    /**
+     * Consider signatures made after the given timestamp invalid.
+     *
+     * @param timestamp timestamp
+     * @return options
+     */
+    public ConsumerOptions verifyNotAfter(Date timestamp) {
+        this.verifyNotAfter = timestamp;
+        return this;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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.
+     * TODO: Implement
+     *
+     * @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 byte[] sessionKey) {
+        this.sessionKey = sessionKey;
+        return this;
+    }
+
+    /**
+     * 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 a passphrase for message decryption.
+     *
+     * @param passphrase passphrase
+     * @return options
+     */
+    public ConsumerOptions addDecryptionPassphrase(@Nonnull Passphrase passphrase) {
+        decryptionPassphrases.add(passphrase);
+        return this;
+    }
+
+    public Set<PGPSecretKeyRing> getDecryptionKeys() {
+        return Collections.unmodifiableSet(decryptionKeys.keySet());
+    }
+
+    public Set<Passphrase> getDecryptionPassphrases() {
+        return Collections.unmodifiableSet(decryptionPassphrases);
+    }
+
+    public Set<PGPPublicKeyRing> getCertificates() {
+        return Collections.unmodifiableSet(certificates);
+    }
+
+    public MissingPublicKeyCallback getMissingCertificateCallback() {
+        return missingCertificateCallback;
+    }
+
+    public SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
+        return decryptionKeys.get(decryptionKeyRing);
+    }
+
+    public Set<PGPSignature> getDetachedSignatures() {
+        return Collections.unmodifiableSet(detachedSignatures);
+    }
+}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java
index 8bafe6ff..25e76057 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilder.java
@@ -63,6 +63,15 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
 
     class DecryptWithImpl implements DecryptWith {
 
+        @Override
+        public DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException {
+            if (consumerOptions == null) {
+                throw new IllegalArgumentException("Consumer options cannot be null.");
+            }
+
+            return DecryptionStreamFactory.create(inputStream, consumerOptions);
+        }
+
         @Override
         public Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings) {
             DecryptionBuilder.this.decryptionKeys = secretKeyRings;
@@ -219,8 +228,27 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
 
         @Override
         public DecryptionStream build() throws IOException, PGPException {
-            return DecryptionStreamFactory.create(inputStream, decryptionKeys, decryptionKeyDecryptor,
-                    decryptionPassphrase, detachedSignatures, verificationKeys, missingPublicKeyCallback);
+            ConsumerOptions options = new ConsumerOptions();
+
+            for (PGPSecretKeyRing decryptionKey : (decryptionKeys != null ? decryptionKeys : Collections.<PGPSecretKeyRing>emptyList())) {
+                options.addDecryptionKey(decryptionKey, decryptionKeyDecryptor);
+            }
+
+            for (PGPPublicKeyRing certificate : (verificationKeys != null ? verificationKeys : Collections.<PGPPublicKeyRing>emptyList())) {
+                options.addVerificationCert(certificate);
+            }
+
+            for (PGPSignature detachedSignature : (detachedSignatures != null ? detachedSignatures : Collections.<PGPSignature>emptyList())) {
+                options.addVerificationOfDetachedSignature(detachedSignature);
+            }
+
+            options.setMissingCertificateCallback(missingPublicKeyCallback);
+
+            if (decryptionPassphrase != null) {
+                options.addDecryptionPassphrase(decryptionPassphrase);
+            }
+
+            return DecryptionStreamFactory.create(inputStream, options);
         }
     }
 }
diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
index c890ff0f..45717753 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
@@ -46,6 +46,8 @@ public interface DecryptionBuilderInterface {
 
     interface DecryptWith {
 
+        DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException;
+
         /**
          * Decrypt the encrypted data using the secret keys found in the provided {@link PGPSecretKeyRingCollection}.
          * Here it is assumed that the secret keys are not password protected.
diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
index c9c42034..686876a9 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
@@ -54,6 +54,7 @@ import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
 import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
 import org.pgpainless.PGPainless;
 import org.pgpainless.algorithm.CompressionAlgorithm;
+import org.pgpainless.algorithm.EncryptionPurpose;
 import org.pgpainless.algorithm.StreamEncoding;
 import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
 import org.pgpainless.exception.MessageNotIntegrityProtectedException;
@@ -62,6 +63,7 @@ import org.pgpainless.exception.UnacceptableAlgorithmException;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
 import org.pgpainless.key.SubkeyIdentifier;
+import org.pgpainless.key.info.KeyRingInfo;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.protection.UnlockSecretKey;
 import org.pgpainless.signature.DetachedSignature;
@@ -75,11 +77,7 @@ public final class DecryptionStreamFactory {
     private static final Level LEVEL = Level.FINE;
     private static final int MAX_RECURSION_DEPTH = 16;
 
-    private final PGPSecretKeyRingCollection decryptionKeys;
-    private final SecretKeyRingProtector decryptionKeyDecryptor;
-    private final Passphrase decryptionPassphrase;
-    private final Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
-    private final MissingPublicKeyCallback missingPublicKeyCallback;
+    private final ConsumerOptions options;
 
     private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
     private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
@@ -87,47 +85,30 @@ public final class DecryptionStreamFactory {
     private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
     private final List<IntegrityProtectedInputStream> integrityProtectedStreams = new ArrayList<>();
 
-    private DecryptionStreamFactory(@Nullable PGPSecretKeyRingCollection decryptionKeys,
-                                    @Nullable SecretKeyRingProtector decryptor,
-                                    @Nullable Passphrase decryptionPassphrase,
-                                    @Nullable Set<PGPPublicKeyRing> verificationKeys,
-                                    @Nullable MissingPublicKeyCallback missingPublicKeyCallback) {
-        this.decryptionKeys = decryptionKeys;
-        this.decryptionKeyDecryptor = decryptor;
-        this.decryptionPassphrase = decryptionPassphrase;
-        this.verificationKeys.addAll(verificationKeys != null ? verificationKeys : Collections.emptyList());
-        this.missingPublicKeyCallback = missingPublicKeyCallback;
+    public DecryptionStreamFactory(ConsumerOptions options) {
+        this.options = options;
     }
 
     public static DecryptionStream create(@Nonnull InputStream inputStream,
-                                          @Nullable PGPSecretKeyRingCollection decryptionKeys,
-                                          @Nullable SecretKeyRingProtector decryptor,
-                                          @Nullable Passphrase decryptionPassphrase,
-                                          @Nullable List<PGPSignature> detachedSignatures,
-                                          @Nullable Set<PGPPublicKeyRing> verificationKeys,
-                                          @Nullable MissingPublicKeyCallback missingPublicKeyCallback)
-            throws IOException, PGPException {
-        InputStream pgpInputStream;
-        DecryptionStreamFactory factory = new DecryptionStreamFactory(decryptionKeys, decryptor,
-                decryptionPassphrase, verificationKeys, missingPublicKeyCallback);
+                                          @Nonnull ConsumerOptions options) throws PGPException, IOException {
+        InputStream pgpInputStream = inputStream;
+        DecryptionStreamFactory factory = new DecryptionStreamFactory(options);
 
-        if (detachedSignatures != null) {
-            pgpInputStream = inputStream;
-            for (PGPSignature signature : detachedSignatures) {
-                PGPPublicKeyRing signingKeyRing = factory.findSignatureVerificationKeyRing(signature.getKeyID());
-                if (signingKeyRing == null) {
-                    continue;
-                }
-                PGPPublicKey signingKey = signingKeyRing.getPublicKey(signature.getKeyID());
-                signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
-                factory.resultBuilder.addDetachedSignature(
-                        new DetachedSignature(signature, signingKeyRing, new SubkeyIdentifier(signingKeyRing, signature.getKeyID())));
+        for (PGPSignature signature : options.getDetachedSignatures()) {
+            PGPPublicKeyRing signingKeyRing = factory.findSignatureVerificationKeyRing(signature.getKeyID());
+            if (signingKeyRing == null) {
+                continue;
             }
-        } else {
-            PGPObjectFactory objectFactory = new PGPObjectFactory(
-                    PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator);
-            pgpInputStream = factory.processPGPPackets(objectFactory, 1);
+            PGPPublicKey signingKey = signingKeyRing.getPublicKey(signature.getKeyID());
+            signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
+            factory.resultBuilder.addDetachedSignature(
+                    new DetachedSignature(signature, signingKeyRing, new SubkeyIdentifier(signingKeyRing, signature.getKeyID())));
         }
+
+        PGPObjectFactory objectFactory = new PGPObjectFactory(
+                PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator);
+        pgpInputStream = factory.processPGPPackets(objectFactory, 1);
+
         return new DecryptionStream(pgpInputStream, factory.resultBuilder, factory.integrityProtectedStreams);
     }
 
@@ -210,50 +191,68 @@ public final class DecryptionStreamFactory {
         while (encryptedDataIterator.hasNext()) {
             PGPEncryptedData encryptedData = encryptedDataIterator.next();
 
+            // TODO: Can we just skip non-integrity-protected packages?
             if (!encryptedData.isIntegrityProtected()) {
                 throw new MessageNotIntegrityProtectedException();
             }
+
             // Data is passphrase encrypted
             if (encryptedData instanceof PGPPBEEncryptedData) {
                 PGPPBEEncryptedData pbeEncryptedData = (PGPPBEEncryptedData) encryptedData;
-                if (decryptionPassphrase != null) {
+                for (Passphrase passphrase : options.getDecryptionPassphrases()) {
                     PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance()
-                            .getPBEDataDecryptorFactory(decryptionPassphrase);
-                    SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId(
-                            pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor));
-                    throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
-                    resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
-
+                            .getPBEDataDecryptorFactory(passphrase);
                     try {
-                        return pbeEncryptedData.getDataStream(passphraseDecryptor);
+                        InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
+
+                        SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId(
+                                pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor));
+                        throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
+                        resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
+
+                        return decryptedDataStream;
                     } catch (PGPException e) {
                         LOGGER.log(LEVEL, "Probable passphrase mismatch, skip PBE encrypted data block", e);
                     }
                 }
             }
+
             // data is public key encrypted
             else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
+                if (options.getDecryptionKeys().isEmpty()) {
+
+                }
                 PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
                 long keyId = publicKeyEncryptedData.getKeyID();
-                if (decryptionKeys != null) {
+                if (!options.getDecryptionKeys().isEmpty()) {
                     // Known key id
                     if (keyId != 0) {
                         LOGGER.log(LEVEL, "PGPEncryptedData is encrypted for key " + Long.toHexString(keyId));
                         resultBuilder.addRecipientKeyId(keyId);
-                        PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId);
-                        if (secretKey != null) {
+                        PGPSecretKeyRing decryptionKeyRing = findDecryptionKeyRing(keyId);
+                        if (decryptionKeyRing != null) {
+                            PGPSecretKey secretKey = decryptionKeyRing.getSecretKey(keyId);
                             LOGGER.log(LEVEL, "Found respective secret key " + Long.toHexString(keyId));
                             // Watch out! This assignment is possibly done multiple times.
                             encryptedSessionKey = publicKeyEncryptedData;
-                            decryptionKey = UnlockSecretKey.unlockSecretKey(secretKey, decryptionKeyDecryptor);
+                            decryptionKey = UnlockSecretKey.unlockSecretKey(secretKey, options.getSecretKeyProtector(decryptionKeyRing));
                             resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(secretKey));
                         }
-                    } else {
-                        // Hidden recipient
+                    }
+
+                    // Hidden recipient
+                    else {
                         LOGGER.log(LEVEL, "Hidden recipient detected. Try to decrypt with all available secret keys.");
-                        outerloop: for (PGPSecretKeyRing ring : decryptionKeys) {
-                            for (PGPSecretKey key : ring) {
-                                PGPPrivateKey privateKey = key.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(key.getKeyID()));
+                        outerloop: for (PGPSecretKeyRing ring : options.getDecryptionKeys()) {
+                            KeyRingInfo info = new KeyRingInfo(ring);
+                            List<PGPPublicKey> encryptionSubkeys = info.getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS);
+                            for (PGPPublicKey pubkey : encryptionSubkeys) {
+                                PGPSecretKey key = ring.getSecretKey(pubkey.getKeyID());
+                                if (key == null) {
+                                    continue;
+                                }
+
+                                PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(key, options.getSecretKeyProtector(ring).getDecryptor(key.getKeyID()));
                                 PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey);
                                 try {
                                     publicKeyEncryptedData.getSymmetricAlgorithm(decryptorFactory); // will only succeed if we have the right secret key
@@ -271,7 +270,11 @@ public final class DecryptionStreamFactory {
                 }
             }
         }
+        return decryptWith(encryptedSessionKey, decryptionKey);
+    }
 
+    private InputStream decryptWith(PGPPublicKeyEncryptedData encryptedSessionKey, PGPPrivateKey decryptionKey)
+            throws PGPException {
         if (decryptionKey == null) {
             throw new MissingDecryptionMethodException("Decryption failed - No suitable decryption key or passphrase found");
         }
@@ -339,9 +342,18 @@ public final class DecryptionStreamFactory {
         verifiableOnePassSignatures.put(fingerprint, onePassSignature);
     }
 
+    private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
+        for (PGPSecretKeyRing key : options.getDecryptionKeys()) {
+            if (key.getSecretKey(keyId) != null) {
+                return key;
+            }
+        }
+        return null;
+    }
+
     private PGPPublicKeyRing findSignatureVerificationKeyRing(long keyId) {
         PGPPublicKeyRing verificationKeyRing = null;
-        for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
+        for (PGPPublicKeyRing publicKeyRing : options.getCertificates()) {
             PGPPublicKey verificationKey = publicKeyRing.getPublicKey(keyId);
             if (verificationKey != null) {
                 LOGGER.log(LEVEL, "Found public key " + Long.toHexString(keyId) + " for signature verification");
@@ -350,8 +362,8 @@ public final class DecryptionStreamFactory {
             }
         }
 
-        if (verificationKeyRing == null && missingPublicKeyCallback != null) {
-            verificationKeyRing = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId);
+        if (verificationKeyRing == null && options.getMissingCertificateCallback() != null) {
+            verificationKeyRing = options.getMissingCertificateCallback().onMissingPublicKeyEncountered(keyId);
         }
 
         return verificationKeyRing;
diff --git a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
index 1dc95319..9c7e6f8b 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
@@ -109,7 +109,7 @@ public class SymmetricEncryptionTest {
 
     @ParameterizedTest
     @MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
-    public void testMissmatchPassphraseFails(ImplementationFactory implementationFactory) throws IOException, PGPException {
+    public void testMismatchPassphraseFails(ImplementationFactory implementationFactory) throws IOException, PGPException {
         ImplementationFactory.setFactoryImplementation(implementationFactory);
 
         byte[] bytes = new byte[5000];

From 8f425cd31d582dfb0317a1805ff59acd30c1ed17 Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Tue, 15 Jun 2021 17:35:58 +0200
Subject: [PATCH 2/8] Fix parsing of non-OpenPGP messages when handling
 detached signatures

---
 .../DecryptionStreamFactory.java              | 22 +++++++++++----
 .../MissingLiteralDataException.java          | 28 +++++++++++++++++++
 2 files changed, 45 insertions(+), 5 deletions(-)
 create mode 100644 pgpainless-core/src/main/java/org/pgpainless/exception/MissingLiteralDataException.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
index 686876a9..685417fa 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
@@ -15,6 +15,7 @@
  */
 package org.pgpainless.decryption_verification;
 
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -59,6 +60,7 @@ import org.pgpainless.algorithm.StreamEncoding;
 import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
 import org.pgpainless.exception.MessageNotIntegrityProtectedException;
 import org.pgpainless.exception.MissingDecryptionMethodException;
+import org.pgpainless.exception.MissingLiteralDataException;
 import org.pgpainless.exception.UnacceptableAlgorithmException;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
@@ -91,7 +93,8 @@ public final class DecryptionStreamFactory {
 
     public static DecryptionStream create(@Nonnull InputStream inputStream,
                                           @Nonnull ConsumerOptions options) throws PGPException, IOException {
-        InputStream pgpInputStream = inputStream;
+        BufferedInputStream bufferedIn = new BufferedInputStream(inputStream);
+        bufferedIn.mark(200);
         DecryptionStreamFactory factory = new DecryptionStreamFactory(options);
 
         for (PGPSignature signature : options.getDetachedSignatures()) {
@@ -106,10 +109,19 @@ public final class DecryptionStreamFactory {
         }
 
         PGPObjectFactory objectFactory = new PGPObjectFactory(
-                PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator);
-        pgpInputStream = factory.processPGPPackets(objectFactory, 1);
+                PGPUtil.getDecoderStream(bufferedIn), keyFingerprintCalculator);
 
-        return new DecryptionStream(pgpInputStream, factory.resultBuilder, factory.integrityProtectedStreams);
+        try {
+            // Parse OpenPGP message
+            inputStream = factory.processPGPPackets(objectFactory, 1);
+        } catch (MissingLiteralDataException e) {
+            // Not an OpenPGP message. Reset the buffered stream to parse the message as arbitrary binary data
+            //  to allow for detached signature verification.
+            bufferedIn.reset();
+            inputStream = bufferedIn;
+        }
+
+        return new DecryptionStream(inputStream, factory.resultBuilder, factory.integrityProtectedStreams);
     }
 
     private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth) throws IOException, PGPException {
@@ -132,7 +144,7 @@ public final class DecryptionStreamFactory {
             }
         }
 
-        throw new PGPException("No Literal Data Packet found");
+        throw new MissingLiteralDataException("No Literal Data Packet found");
     }
 
     private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/MissingLiteralDataException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/MissingLiteralDataException.java
new file mode 100644
index 00000000..aca46a41
--- /dev/null
+++ b/pgpainless-core/src/main/java/org/pgpainless/exception/MissingLiteralDataException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Paul Schaub.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.pgpainless.exception;
+
+import org.bouncycastle.openpgp.PGPException;
+
+/**
+ * Exception that gets thrown if a {@link org.bouncycastle.bcpg.LiteralDataPacket} is expected, but not found.
+ */
+public class MissingLiteralDataException extends PGPException {
+
+    public MissingLiteralDataException(String message) {
+        super(message);
+    }
+}

From 715d055b412969f0a6e648ff39d30b99c0cbd4c3 Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Tue, 15 Jun 2021 17:56:36 +0200
Subject: [PATCH 3/8] Add documentation and deprecate old methods

---
 .../ConsumerOptions.java                      | 60 ++++++++++---
 .../DecryptionBuilderInterface.java           | 88 ++++++++++++++++++-
 .../DecryptionStreamFactory.java              |  9 +-
 3 files changed, 135 insertions(+), 22 deletions(-)

diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
index e9f6c916..f0ae17f1 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
@@ -1,23 +1,32 @@
+/*
+ * Copyright 2021 Paul Schaub.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.pgpainless.decryption_verification;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
 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.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.util.Passphrase;
@@ -31,8 +40,8 @@ public class ConsumerOptions {
     private Date verifyNotAfter;
 
     // Set of verification keys
-    private Set<PGPPublicKeyRing> certificates = new HashSet<>();
-    private Set<PGPSignature> detachedSignatures = new HashSet<>();
+    private final Set<PGPPublicKeyRing> certificates = new HashSet<>();
+    private final Set<PGPSignature> detachedSignatures = new HashSet<>();
     private MissingPublicKeyCallback missingCertificateCallback = null;
 
     // Session key for decryption without passphrase/key
@@ -53,6 +62,10 @@ public class ConsumerOptions {
         return this;
     }
 
+    public Date getVerifyNotBefore() {
+        return verifyNotBefore;
+    }
+
     /**
      * Consider signatures made after the given timestamp invalid.
      *
@@ -64,6 +77,10 @@ public class ConsumerOptions {
         return this;
     }
 
+    public Date getVerifyNotAfter() {
+        return verifyNotAfter;
+    }
+
     /**
      * Add a certificate (public key ring) for signature verification.
      *
@@ -127,6 +144,21 @@ public class ConsumerOptions {
         return this;
     }
 
+    /**
+     * Return the session key.
+     *
+     * @return session key or null
+     */
+    public @Nullable byte[] getSessionKey() {
+        if (sessionKey == null) {
+            return null;
+        }
+
+        byte[] sk = new byte[sessionKey.length];
+        System.arraycopy(sessionKey, 0, sk, 0, sessionKey.length);
+        return sk;
+    }
+
     /**
      * Add a key for message decryption.
      * The key is expected to be unencrypted.
@@ -162,27 +194,27 @@ public class ConsumerOptions {
         return this;
     }
 
-    public Set<PGPSecretKeyRing> getDecryptionKeys() {
+    public @Nonnull Set<PGPSecretKeyRing> getDecryptionKeys() {
         return Collections.unmodifiableSet(decryptionKeys.keySet());
     }
 
-    public Set<Passphrase> getDecryptionPassphrases() {
+    public @Nonnull Set<Passphrase> getDecryptionPassphrases() {
         return Collections.unmodifiableSet(decryptionPassphrases);
     }
 
-    public Set<PGPPublicKeyRing> getCertificates() {
+    public @Nonnull Set<PGPPublicKeyRing> getCertificates() {
         return Collections.unmodifiableSet(certificates);
     }
 
-    public MissingPublicKeyCallback getMissingCertificateCallback() {
+    public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() {
         return missingCertificateCallback;
     }
 
-    public SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
+    public @Nullable SecretKeyRingProtector getSecretKeyProtector(PGPSecretKeyRing decryptionKeyRing) {
         return decryptionKeys.get(decryptionKeyRing);
     }
 
-    public Set<PGPSignature> getDetachedSignatures() {
+    public @Nonnull Set<PGPSignature> getDetachedSignatures() {
         return Collections.unmodifiableSet(detachedSignatures);
     }
 }
diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
index 45717753..0b9a89e5 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionBuilderInterface.java
@@ -46,6 +46,14 @@ public interface DecryptionBuilderInterface {
 
     interface DecryptWith {
 
+        /**
+         * Add options for decryption / signature verification, such as keys, passphrases etc.
+         *
+         * @param consumerOptions consumer options
+         * @return decryption stream
+         * @throws PGPException in case of an OpenPGP related error
+         * @throws IOException in case of an IO error
+         */
         DecryptionStream withOptions(ConsumerOptions consumerOptions) throws PGPException, IOException;
 
         /**
@@ -56,7 +64,11 @@ public interface DecryptionBuilderInterface {
          *
          * @param secretKeyRings secret keys
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addDecryptionKey(PGPSecretKeyRing, SecretKeyRingProtector)}
+         * ({@link #withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         default Verify decryptWith(@Nonnull PGPSecretKeyRingCollection secretKeyRings) {
             return decryptWith(new UnprotectedKeysProtector(), secretKeyRings);
         }
@@ -68,7 +80,11 @@ public interface DecryptionBuilderInterface {
          * @param decryptor for unlocking locked secret keys
          * @param secretKeyRings secret keys
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addDecryptionKey(PGPSecretKeyRing, SecretKeyRingProtector)}
+         * ({@link #withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings);
 
         /**
@@ -78,8 +94,13 @@ public interface DecryptionBuilderInterface {
          * @param decryptor for unlocking locked secret key
          * @param secretKeyRing secret key
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addDecryptionKey(PGPSecretKeyRing, SecretKeyRingProtector)}
+         * ({@link #withOptions(ConsumerOptions)}) instead.
          */
-        Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing) throws PGPException, IOException;
+        @Deprecated
+        Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRing secretKeyRing)
+                throws PGPException, IOException;
 
         /**
          * Decrypt the encrypted data using a passphrase.
@@ -87,7 +108,11 @@ public interface DecryptionBuilderInterface {
          *
          * @param passphrase passphrase
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addDecryptionPassphrase(Passphrase)}
+         * ({@link #withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         Verify decryptWith(@Nonnull Passphrase passphrase);
 
         /**
@@ -95,32 +120,41 @@ public interface DecryptionBuilderInterface {
          * Useful for signature verification of signed-only data.
          *
          * @return api handle
+         *
+         * @deprecated use {@link #withOptions(ConsumerOptions)} instead and set no decryption keys.
          */
+        @Deprecated
         Verify doNotDecrypt();
 
     }
 
+    @Deprecated
     interface Verify extends VerifyWith {
 
         @Override
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull PGPPublicKeyRingCollection publicKeyRings);
 
         @Override
+        @Deprecated
         default HandleMissingPublicKeys verifyWith(@Nonnull OpenPgpV4Fingerprint trustedFingerprint,
                                                    @Nonnull PGPPublicKeyRingCollection publicKeyRings) {
             return verifyWith(Collections.singleton(trustedFingerprint), publicKeyRings);
         }
 
         @Override
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull Set<OpenPgpV4Fingerprint> trustedFingerprints,
                                            @Nonnull PGPPublicKeyRingCollection publicKeyRings);
 
         @Override
+        @Deprecated
         default HandleMissingPublicKeys verifyWith(@Nonnull PGPPublicKeyRing publicKeyRing) {
             return verifyWith(Collections.singleton(publicKeyRing));
         }
 
         @Override
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull Set<PGPPublicKeyRing> publicKeyRings);
 
         /**
@@ -130,7 +164,11 @@ public interface DecryptionBuilderInterface {
          * @return api handle
          * @throws IOException if some IO error occurs
          * @throws PGPException if the detached signatures are malformed
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationOfDetachedSignature(PGPSignature)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         default VerifyWith verifyDetachedSignature(@Nonnull byte[] bytes) throws IOException, PGPException {
             return verifyDetachedSignature(new ByteArrayInputStream(bytes));
         }
@@ -142,7 +180,11 @@ public interface DecryptionBuilderInterface {
          * @return api handle
          * @throws IOException in case something is wrong with the input stream
          * @throws PGPException if the detached signatures are malformed
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationOfDetachedSignature(PGPSignature)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         VerifyWith verifyDetachedSignature(@Nonnull InputStream inputStream) throws IOException, PGPException;
 
         /**
@@ -150,7 +192,11 @@ public interface DecryptionBuilderInterface {
          *
          * @param signature detached signature
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationOfDetachedSignature(PGPSignature)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         default VerifyWith verifyDetachedSignature(@Nonnull PGPSignature signature) {
             return verifyDetachedSignatures(Collections.singletonList(signature));
         }
@@ -160,17 +206,25 @@ public interface DecryptionBuilderInterface {
          *
          * @param signatures detached signatures
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationOfDetachedSignature(PGPSignature)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         VerifyWith verifyDetachedSignatures(@Nonnull List<PGPSignature> signatures);
 
         /**
          * Instruct the {@link DecryptionStream} to not verify any signatures.
          *
          * @return api handle
+         *
+         * @deprecated use {@link DecryptWith#withOptions(ConsumerOptions)} instead and don't set verification keys.
          */
+        @Deprecated
         Build doNotVerify();
     }
 
+    @Deprecated
     interface VerifyWith {
 
         /**
@@ -178,7 +232,11 @@ public interface DecryptionBuilderInterface {
          *
          * @param publicKeyRings public keys
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationCerts(PGPPublicKeyRingCollection)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull PGPPublicKeyRingCollection publicKeyRings);
 
         /**
@@ -188,7 +246,10 @@ public interface DecryptionBuilderInterface {
          * @param trustedFingerprint {@link OpenPgpV4Fingerprint} of the public key that shall be used to verify the signatures.
          * @param publicKeyRings public keys
          * @return api handle
+         * @deprecated use {@link ConsumerOptions#addVerificationCert(PGPPublicKeyRing)}
+          * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         default HandleMissingPublicKeys verifyWith(@Nonnull OpenPgpV4Fingerprint trustedFingerprint,
                                                    @Nonnull PGPPublicKeyRingCollection publicKeyRings) {
             return verifyWith(Collections.singleton(trustedFingerprint), publicKeyRings);
@@ -201,7 +262,11 @@ public interface DecryptionBuilderInterface {
          * @param trustedFingerprints set of trusted {@link OpenPgpV4Fingerprint OpenPgpV4Fingerprints}.
          * @param publicKeyRings public keys
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationCert(PGPPublicKeyRing)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull Set<OpenPgpV4Fingerprint> trustedFingerprints,
                                            @Nonnull PGPPublicKeyRingCollection publicKeyRings);
 
@@ -210,7 +275,11 @@ public interface DecryptionBuilderInterface {
          *
          * @param publicKeyRing public key
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationCert(PGPPublicKeyRing)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         default HandleMissingPublicKeys verifyWith(@Nonnull PGPPublicKeyRing publicKeyRing) {
             return verifyWith(Collections.singleton(publicKeyRing));
         }
@@ -220,11 +289,16 @@ public interface DecryptionBuilderInterface {
          *
          * @param publicKeyRings public keys
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#addVerificationCert(PGPPublicKeyRing)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         HandleMissingPublicKeys verifyWith(@Nonnull Set<PGPPublicKeyRing> publicKeyRings);
 
     }
 
+    @Deprecated
     interface HandleMissingPublicKeys {
 
         /**
@@ -232,17 +306,26 @@ public interface DecryptionBuilderInterface {
          *
          * @param callback callback
          * @return api handle
+         *
+         * @deprecated use {@link ConsumerOptions#setMissingCertificateCallback(MissingPublicKeyCallback)}
+         * ({@link DecryptWith#withOptions(ConsumerOptions)}) instead.
          */
+        @Deprecated
         Build handleMissingPublicKeysWith(@Nonnull MissingPublicKeyCallback callback);
 
         /**
          * Instruct the {@link DecryptionStream} to ignore any missing public keys.
          *
          * @return api handle
+         *
+         * @deprecated simply do not set a {@link MissingPublicKeyCallback} and use
+         * {@link DecryptWith#withOptions(ConsumerOptions)} instead.
          */
+        @Deprecated
         Build ignoreMissingPublicKeys();
     }
 
+    @Deprecated
     interface Build {
 
         /**
@@ -252,7 +335,10 @@ public interface DecryptionBuilderInterface {
          * @throws IOException in case of an I/O error
          * @throws PGPException if something is malformed
          * @throws org.pgpainless.exception.UnacceptableAlgorithmException if the message uses weak/unacceptable algorithms
+         *
+         * @deprecated use {@link DecryptWith#withOptions(ConsumerOptions)} instead.
          */
+        @Deprecated
         DecryptionStream build() throws IOException, PGPException;
 
     }
diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
index 685417fa..f863ccf9 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java
@@ -19,17 +19,13 @@ import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
 
 import org.bouncycastle.openpgp.PGPCompressedData;
 import org.bouncycastle.openpgp.PGPEncryptedData;
@@ -46,7 +42,6 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPUtil;
 import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
@@ -66,7 +61,6 @@ import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
 import org.pgpainless.key.SubkeyIdentifier;
 import org.pgpainless.key.info.KeyRingInfo;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.protection.UnlockSecretKey;
 import org.pgpainless.signature.DetachedSignature;
 import org.pgpainless.signature.OnePassSignature;
@@ -92,7 +86,8 @@ public final class DecryptionStreamFactory {
     }
 
     public static DecryptionStream create(@Nonnull InputStream inputStream,
-                                          @Nonnull ConsumerOptions options) throws PGPException, IOException {
+                                          @Nonnull ConsumerOptions options)
+            throws PGPException, IOException {
         BufferedInputStream bufferedIn = new BufferedInputStream(inputStream);
         bufferedIn.mark(200);
         DecryptionStreamFactory factory = new DecryptionStreamFactory(options);

From 88891e1337104796a5288de576cd4f124f04707f Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Wed, 16 Jun 2021 15:38:02 +0200
Subject: [PATCH 4/8] Remove usage of deprecated decryption/verification API in
 tests

---
 .../ConsumerOptions.java                      |  68 ++++++++++
 .../pgpainless/signature/SignatureUtils.java  |  52 +++++--
 .../DecryptAndVerifyMessageTest.java          |  14 +-
 .../DecryptHiddenRecipientMessage.java        |  12 +-
 .../ModificationDetectionTests.java           |  24 ++--
 .../RecursionDepthTest.java                   |   8 +-
 ...eakSymmetricAlgorithmDuringDecryption.java |  38 +++---
 .../VerifyWithMissingPublicKeyCallback.java   |  11 +-
 .../EncryptDecryptTest.java                   |  53 ++++----
 .../encryption_signing/FileInfoTest.java      |   9 +-
 .../encryption_signing/LengthTest.java        | 127 ------------------
 ...ymmetricAlgorithmDuringEncryptionTest.java |   3 +-
 .../encryption_signing/SigningTest.java       |  36 +++--
 .../BindingSignatureSubpacketsTest.java       |  67 ++++-----
 .../signature/IgnoreMarkerPackets.java        |  28 ++--
 .../signature/KeyRevocationTest.java          |   5 +-
 .../SignatureChainValidatorTest.java          |  15 ++-
 .../signature/SignatureStructureTest.java     |   3 +-
 ...ultiPassphraseSymmetricEncryptionTest.java |  22 +--
 .../SymmetricEncryptionTest.java              |  32 ++---
 ...ncryptCommsStorageFlagsDifferentiated.java |  13 +-
 .../weird_keys/TestTwoSubkeysEncryption.java  |   2 +-
 .../org/pgpainless/sop/commands/Decrypt.java  |  38 +++---
 .../org/pgpainless/sop/commands/Verify.java   |  52 +++----
 24 files changed, 342 insertions(+), 390 deletions(-)
 delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/encryption_signing/LengthTest.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
index f0ae17f1..86bc8176 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
@@ -15,19 +15,31 @@
  */
 package org.pgpainless.decryption_verification;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
 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.bcpg.MarkerPacket;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPObjectFactory;
 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.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.util.Passphrase;
 
@@ -105,6 +117,48 @@ public class ConsumerOptions {
         return this;
     }
 
+    public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException {
+        List<PGPSignature> signatures = new ArrayList<>();
+        InputStream pgpIn = PGPUtil.getDecoderStream(signatureInputStream);
+        PGPObjectFactory objectFactory = new PGPObjectFactory(
+                pgpIn, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
+
+        Object nextObject = objectFactory.nextObject();
+        while (nextObject != null) {
+            if (nextObject instanceof MarkerPacket) {
+                nextObject = objectFactory.nextObject();
+                continue;
+            }
+            if (nextObject instanceof PGPCompressedData) {
+                PGPCompressedData compressedData = (PGPCompressedData) nextObject;
+                objectFactory = new PGPObjectFactory(compressedData.getDataStream(),
+                        ImplementationFactory.getInstance().getKeyFingerprintCalculator());
+                nextObject = objectFactory.nextObject();
+                continue;
+            }
+            if (nextObject instanceof PGPSignatureList) {
+                PGPSignatureList signatureList = (PGPSignatureList) nextObject;
+                for (PGPSignature s : signatureList) {
+                    signatures.add(s);
+                }
+            }
+            if (nextObject instanceof PGPSignature) {
+                signatures.add((PGPSignature) nextObject);
+            }
+            nextObject = objectFactory.nextObject();
+        }
+        pgpIn.close();
+
+        return addVerificationOfDetachedSignatures(signatures);
+    }
+
+    public ConsumerOptions addVerificationOfDetachedSignatures(List<PGPSignature> detachedSignatures) {
+        for (PGPSignature signature : detachedSignatures) {
+            addVerificationOfDetachedSignature(signature);
+        }
+        return this;
+    }
+
     /**
      * Add a detached signature for the signature verification process.
      *
@@ -183,6 +237,20 @@ public class ConsumerOptions {
         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.
      *
diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java
index b8117fad..c1d418e1 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java
@@ -15,22 +15,27 @@
  */
 package org.pgpainless.signature;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
+import org.bouncycastle.bcpg.MarkerPacket;
 import org.bouncycastle.bcpg.sig.KeyExpirationTime;
 import org.bouncycastle.bcpg.sig.RevocationReason;
 import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
-import org.bouncycastle.openpgp.PGPMarker;
+import org.bouncycastle.openpgp.PGPCompressedData;
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPObjectFactory;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.openpgp.PGPSignatureGenerator;
 import org.bouncycastle.openpgp.PGPSignatureList;
+import org.bouncycastle.openpgp.PGPUtil;
 import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
 import org.bouncycastle.util.encoders.Hex;
 import org.pgpainless.PGPainless;
@@ -41,7 +46,6 @@ import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
 import org.pgpainless.key.util.RevocationAttributes;
 import org.pgpainless.policy.Policy;
 import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
-import org.pgpainless.util.BCUtil;
 
 /**
  * Utility methods related to signatures.
@@ -183,18 +187,44 @@ public class SignatureUtils {
      * @return signature list
      * @throws IOException if the signatures cannot be read
      */
-    public static PGPSignatureList readSignatures(String encodedSignatures) throws IOException {
-        InputStream inputStream = BCUtil.getPgpDecoderInputStream(encodedSignatures.getBytes(Charset.forName("UTF8")));
-        PGPObjectFactory objectFactory = new PGPObjectFactory(inputStream, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
-        Object next = objectFactory.nextObject();
-        while (next != null) {
-            if (next instanceof PGPMarker) {
-                next = objectFactory.nextObject();
+    public static List<PGPSignature> readSignatures(String encodedSignatures) throws IOException, PGPException {
+        InputStream inputStream = new ByteArrayInputStream(encodedSignatures.getBytes(Charset.forName("UTF8")));
+        return readSignatures(inputStream);
+    }
+
+    public static List<PGPSignature> readSignatures(InputStream inputStream) throws IOException, PGPException {
+        List<PGPSignature> signatures = new ArrayList<>();
+        InputStream pgpIn = PGPUtil.getDecoderStream(inputStream);
+        PGPObjectFactory objectFactory = new PGPObjectFactory(
+                pgpIn, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
+
+        Object nextObject = objectFactory.nextObject();
+        while (nextObject != null) {
+            if (nextObject instanceof MarkerPacket) {
+                nextObject = objectFactory.nextObject();
                 continue;
             }
-            return (PGPSignatureList) next;
+            if (nextObject instanceof PGPCompressedData) {
+                PGPCompressedData compressedData = (PGPCompressedData) nextObject;
+                objectFactory = new PGPObjectFactory(compressedData.getDataStream(),
+                        ImplementationFactory.getInstance().getKeyFingerprintCalculator());
+                nextObject = objectFactory.nextObject();
+                continue;
+            }
+            if (nextObject instanceof PGPSignatureList) {
+                PGPSignatureList signatureList = (PGPSignatureList) nextObject;
+                for (PGPSignature s : signatureList) {
+                    signatures.add(s);
+                }
+            }
+            if (nextObject instanceof PGPSignature) {
+                signatures.add((PGPSignature) nextObject);
+            }
+            nextObject = objectFactory.nextObject();
         }
-        return null;
+        pgpIn.close();
+
+        return signatures;
     }
 
     public static String getSignatureDigestPrefix(PGPSignature signature) {
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java
index aa33d45e..c47f095f 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptAndVerifyMessageTest.java
@@ -23,12 +23,9 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.Charset;
-import java.util.Collections;
 
 import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -38,7 +35,7 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
 import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.TestKeys;
-import org.pgpainless.key.protection.UnprotectedKeysProtector;
+import org.pgpainless.key.util.KeyRingUtils;
 
 public class DecryptAndVerifyMessageTest {
 
@@ -60,12 +57,13 @@ public class DecryptAndVerifyMessageTest {
         ImplementationFactory.setFactoryImplementation(implementationFactory);
         String encryptedMessage = TestKeys.MSG_SIGN_CRYPT_JULIET_JULIET;
 
+        ConsumerOptions options = new ConsumerOptions()
+                .addDecryptionKey(juliet)
+                .addVerificationCert(KeyRingUtils.publicKeyRingFrom(juliet));
+
         DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes()))
-                .decryptWith(new UnprotectedKeysProtector(), new PGPSecretKeyRingCollection(Collections.singleton(juliet)))
-                .verifyWith(Collections.singleton(new PGPPublicKeyRing(Collections.singletonList(juliet.getPublicKey()))))
-                .ignoreMissingPublicKeys()
-                .build();
+                .withOptions(options);
 
         ByteArrayOutputStream toPlain = new ByteArrayOutputStream();
         Streams.pipeAll(decryptor, toPlain);
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
index 4b388cba..7cc76f33 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
@@ -21,13 +21,11 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
 import java.util.Set;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
@@ -35,7 +33,6 @@ import org.pgpainless.PGPainless;
 import org.pgpainless.algorithm.KeyFlag;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.util.KeyRingUtils;
 import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
 
@@ -144,11 +141,12 @@ public class DecryptHiddenRecipientMessage {
                 "=1knQ\n" +
                 "-----END PGP MESSAGE-----\n";
         ByteArrayInputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
+        ConsumerOptions options = new ConsumerOptions()
+                .addDecryptionKey(secretKeys);
 
-        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(messageIn)
-                .decryptWith(SecretKeyRingProtector.unprotectedKeys(), new PGPSecretKeyRingCollection(Collections.singletonList(secretKeys)))
-                .doNotVerify()
-                .build();
+        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
+                .onInputStream(messageIn)
+                .withOptions(options);
 
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         Streams.pipeAll(decryptionStream, out);
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java
index a9b92750..0504aa23 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/ModificationDetectionTests.java
@@ -154,9 +154,9 @@ public class ModificationDetectionTests {
         InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
         DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
                 .onInputStream(in)
-                .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeyRings)
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKeys(secretKeyRings, SecretKeyRingProtector.unprotectedKeys())
+                );
 
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         assertThrows(EOFException.class, () -> {
@@ -187,9 +187,9 @@ public class ModificationDetectionTests {
         ByteArrayInputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
         DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
                 .onInputStream(in)
-                .decryptWith(getDecryptionKey())
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys())
+                );
 
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         Streams.pipeAll(decryptionStream, out);
@@ -218,9 +218,9 @@ public class ModificationDetectionTests {
         ByteArrayInputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
         DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
                 .onInputStream(in)
-                .decryptWith(getDecryptionKey())
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKeys(getDecryptionKey(), SecretKeyRingProtector.unprotectedKeys())
+                );
 
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         Streams.pipeAll(decryptionStream, out);
@@ -387,9 +387,9 @@ public class ModificationDetectionTests {
 
         assertThrows(MessageNotIntegrityProtectedException.class, () -> PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(ciphertext.getBytes(StandardCharsets.UTF_8)))
-                .decryptWith(SecretKeyRingProtector.unlockAllKeysWith(passphrase, secretKeyRing), new PGPSecretKeyRingCollection(Collections.singleton(secretKeyRing)))
-                .doNotVerify()
-                .build());
+                .withOptions(new ConsumerOptions().addDecryptionKey(secretKeyRing,
+                        SecretKeyRingProtector.unlockAllKeysWith(passphrase, secretKeyRing)))
+        );
     }
 
     private PGPSecretKeyRingCollection getDecryptionKey() throws IOException, PGPException {
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RecursionDepthTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RecursionDepthTest.java
index 74dfa22f..3ec55359 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RecursionDepthTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RecursionDepthTest.java
@@ -21,17 +21,14 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.pgpainless.PGPainless;
 import org.pgpainless.implementation.ImplementationFactory;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 
 public class RecursionDepthTest {
 
@@ -127,7 +124,6 @@ public class RecursionDepthTest {
                 "=miES\n" +
                 "-----END PGP PRIVATE KEY BLOCK-----\n";
         PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
-        PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(Collections.singletonList(secretKey));
 
         // message contains compressed data that contains compressed data that contains... 64 times.
         String msg = "-----BEGIN PGP ARMORED FILE-----\n" +
@@ -161,9 +157,7 @@ public class RecursionDepthTest {
         assertThrows(PGPException.class, () -> {
             DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
                     .onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
-                    .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                    .doNotVerify()
-                    .build();
+                    .withOptions(new ConsumerOptions().addDecryptionKey(secretKey));
 
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
             Streams.pipeAll(decryptionStream, outputStream);
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RejectWeakSymmetricAlgorithmDuringDecryption.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RejectWeakSymmetricAlgorithmDuringDecryption.java
index 6db5eda0..46c43143 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RejectWeakSymmetricAlgorithmDuringDecryption.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/RejectWeakSymmetricAlgorithmDuringDecryption.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 import org.bouncycastle.openpgp.PGPException;
@@ -27,7 +28,6 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.junit.jupiter.api.Test;
 import org.pgpainless.PGPainless;
 import org.pgpainless.exception.UnacceptableAlgorithmException;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 
 /**
  * Test PGPainless' default symmetric key algorithm policy for decryption of messages.
@@ -146,11 +146,13 @@ public class RejectWeakSymmetricAlgorithmDuringDecryption {
                 "=w0KS\n" +
                 "-----END PGP MESSAGE-----\n";
 
+        InputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
+
         assertThrows(UnacceptableAlgorithmException.class, () ->
-                PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
-                        .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                        .doNotVerify()
-                        .build());
+                PGPainless.decryptAndOrVerify()
+                        .onInputStream(messageIn)
+                        .withOptions(new ConsumerOptions().addDecryptionKey(secretKeys))
+        );
     }
 
     @Test
@@ -171,11 +173,14 @@ public class RejectWeakSymmetricAlgorithmDuringDecryption {
                 "WLlG7ee7fRqQPTSP+OLh4Cm8zDIaCNowj0Ua4KwcWZDYERzg\n" +
                 "=j71X\n" +
                 "-----END PGP ARMORED FILE-----\n";
+
+        InputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
+
         assertThrows(UnacceptableAlgorithmException.class, () ->
-                PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
-                        .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                        .doNotVerify()
-                        .build());
+                PGPainless.decryptAndOrVerify()
+                        .onInputStream(messageIn)
+                        .withOptions(new ConsumerOptions().addDecryptionKey(secretKeys))
+        );
     }
 
     @Test
@@ -197,11 +202,11 @@ public class RejectWeakSymmetricAlgorithmDuringDecryption {
                 "=qNxx\n" +
                 "-----END PGP ARMORED FILE-----\n";
 
+        InputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
         assertThrows(UnacceptableAlgorithmException.class, () ->
-                PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
-                        .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                        .doNotVerify()
-                        .build());
+                PGPainless.decryptAndOrVerify().onInputStream(messageIn)
+                        .withOptions(new ConsumerOptions().addDecryptionKey(secretKeys))
+        );
     }
 
     // Control: In contrast, AES256 is acceptable
@@ -222,11 +227,10 @@ public class RejectWeakSymmetricAlgorithmDuringDecryption {
                 "4yjtOxfmmp9Fac50SS5i9dzBdnVNllLs+ADQt+LksJnzTW1IINGnIw8=\n" +
                 "=kLfl\n" +
                 "-----END PGP ARMORED FILE-----\n";
+        InputStream messageIn = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
 
-        PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)))
-                .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                .doNotVerify()
-                .build();
+        PGPainless.decryptAndOrVerify().onInputStream(messageIn)
+                .withOptions(new ConsumerOptions().addDecryptionKey(secretKeys));
     }
 
 }
diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallback.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallback.java
index 514ced4d..5efb51dc 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallback.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/VerifyWithMissingPublicKeyCallback.java
@@ -72,18 +72,17 @@ public class VerifyWithMissingPublicKeyCallback {
 
         DecryptionStream verificationStream = PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(signOut.toByteArray()))
-                .doNotDecrypt()
-                .verifyWith(unrelatedKeys)
-                .handleMissingPublicKeysWith(
-                        new MissingPublicKeyCallback() {
+                .withOptions(new ConsumerOptions()
+                        .addVerificationCert(unrelatedKeys)
+                        .setMissingCertificateCallback(new MissingPublicKeyCallback() {
                             @Nullable
                             @Override
                             public PGPPublicKeyRing onMissingPublicKeyEncountered(@Nonnull Long keyId) {
                                 assertEquals(signingKey.getKeyID(), keyId, "Signing key-ID mismatch.");
                                 return signingPubKeys;
                             }
-                        }
-                ).build();
+                        }));
+
         ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
         Streams.pipeAll(verificationStream, plainOut);
         verificationStream.close();
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java
index a660778d..24151e57 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java
@@ -27,9 +27,7 @@ import java.io.IOException;
 import java.nio.charset.Charset;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
 import java.util.Set;
-import java.util.logging.Logger;
 
 import org.bouncycastle.bcpg.ArmoredOutputStream;
 import org.bouncycastle.openpgp.PGPException;
@@ -44,6 +42,7 @@ import org.pgpainless.PGPainless;
 import org.pgpainless.algorithm.DocumentSignatureType;
 import org.pgpainless.algorithm.KeyFlag;
 import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
 import org.pgpainless.decryption_verification.OpenPgpMetadata;
 import org.pgpainless.implementation.ImplementationFactory;
@@ -61,17 +60,16 @@ import org.pgpainless.util.ArmoredOutputStreamFactory;
 
 public class EncryptDecryptTest {
 
-    private static final Logger LOGGER = Logger.getLogger(EncryptDecryptTest.class.getName());
     // Don't use StandardCharsets.UTF_8 because of Android API level.
     private static final Charset UTF8 = Charset.forName("UTF-8");
 
     private static final String testMessage =
             "Ah, Juliet, if the measure of thy joy\n" +
-            "Be heaped like mine, and that thy skill be more\n" +
-            "To blazon it, then sweeten with thy breath\n" +
-            "This neighbor air, and let rich music’s tongue\n" +
-            "Unfold the imagined happiness that both\n" +
-            "Receive in either by this dear encounter.";
+                    "Be heaped like mine, and that thy skill be more\n" +
+                    "To blazon it, then sweeten with thy breath\n" +
+                    "This neighbor air, and let rich music’s tongue\n" +
+                    "Unfold the imagined happiness that both\n" +
+                    "Receive in either by this dear encounter.";
 
     @ParameterizedTest
     @MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
@@ -178,10 +176,10 @@ public class EncryptDecryptTest {
         ByteArrayInputStream envelopeIn = new ByteArrayInputStream(encryptedSecretMessage);
         DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
                 .onInputStream(envelopeIn)
-                .decryptWith(keyDecryptor, KeyRingUtils.keyRingsToKeyRingCollection(recipientSec))
-                .verifyWith(KeyRingUtils.keyRingsToKeyRingCollection(senderPub))
-                .ignoreMissingPublicKeys()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKey(recipientSec, keyDecryptor)
+                        .addVerificationCert(senderPub)
+                );
 
         ByteArrayOutputStream decryptedSecretMessage = new ByteArrayOutputStream();
 
@@ -227,12 +225,13 @@ public class EncryptDecryptTest {
         // CHECKSTYLE:ON
 
         inputStream = new ByteArrayInputStream(testMessage.getBytes());
-        DecryptionStream verifier = PGPainless.decryptAndOrVerify().onInputStream(inputStream)
-                .doNotDecrypt()
-                .verifyDetachedSignature(new ByteArrayInputStream(armorSig.getBytes()))
-                .verifyWith(Collections.singleton(KeyRingUtils.publicKeyRingFrom(signingKeys)))
-                .ignoreMissingPublicKeys()
-                .build();
+        DecryptionStream verifier = PGPainless.decryptAndOrVerify()
+                .onInputStream(inputStream)
+                .withOptions(new ConsumerOptions()
+                        .addVerificationOfDetachedSignatures(new ByteArrayInputStream(armorSig.getBytes()))
+                        .addVerificationCert(KeyRingUtils.publicKeyRingFrom(signingKeys))
+                );
+
         dummyOut = new ByteArrayOutputStream();
         Streams.pipeAll(verifier, dummyOut);
         verifier.close();
@@ -257,16 +256,12 @@ public class EncryptDecryptTest {
         Streams.pipeAll(inputStream, signer);
         signer.close();
 
-        // CHECKSTYLE:OFF
-        System.out.println(signOut.toString());
-        // CHECKSTYLE:ON
-
         inputStream = new ByteArrayInputStream(signOut.toByteArray());
-        DecryptionStream verifier = PGPainless.decryptAndOrVerify().onInputStream(inputStream)
-                .doNotDecrypt()
-                .verifyWith(Collections.singleton(KeyRingUtils.publicKeyRingFrom(signingKeys)))
-                .ignoreMissingPublicKeys()
-                .build();
+        DecryptionStream verifier = PGPainless.decryptAndOrVerify()
+                .onInputStream(inputStream)
+                .withOptions(new ConsumerOptions()
+                        .addVerificationCert(KeyRingUtils.publicKeyRingFrom(signingKeys))
+                );
         signOut = new ByteArrayOutputStream();
         Streams.pipeAll(verifier, signOut);
         verifier.close();
@@ -335,7 +330,7 @@ public class EncryptDecryptTest {
         PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         assertThrows(IllegalArgumentException.class, () ->
-        PGPainless.encryptAndOrSign().onOutputStream(outputStream)
-                .toRecipient(publicKeys));
+                PGPainless.encryptAndOrSign().onOutputStream(outputStream)
+                        .toRecipient(publicKeys));
     }
 }
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/FileInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/FileInfoTest.java
index 97fdac81..fbf2dc62 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/FileInfoTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/FileInfoTest.java
@@ -23,20 +23,18 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
-import java.util.Collections;
 import java.util.Date;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.api.Test;
 import org.pgpainless.PGPainless;
 import org.pgpainless.algorithm.StreamEncoding;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
 import org.pgpainless.decryption_verification.OpenPgpMetadata;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.util.KeyRingUtils;
 
 public class FileInfoTest {
@@ -85,9 +83,8 @@ public class FileInfoTest {
 
         DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
                 .onInputStream(cryptIn)
-                .decryptWith(SecretKeyRingProtector.unprotectedKeys(), new PGPSecretKeyRingCollection(Collections.singleton(secretKeys)))
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKey(secretKeys));
         Streams.pipeAll(decryptionStream, plainOut);
 
         decryptionStream.close();
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/LengthTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/LengthTest.java
deleted file mode 100644
index 63862e61..00000000
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/LengthTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.encryption_signing;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.util.Random;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.util.io.Streams;
-import org.pgpainless.PGPainless;
-import org.pgpainless.algorithm.DocumentSignatureType;
-import org.pgpainless.key.TestKeys;
-import org.pgpainless.key.generation.type.rsa.RsaLength;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
-import org.pgpainless.key.protection.UnprotectedKeysProtector;
-import org.pgpainless.key.util.KeyRingUtils;
-
-/**
- * Class used to determine the length of cipher-text depending on used algorithms.
- */
-public class LengthTest {
-
-    private static final Logger LOGGER = Logger.getLogger(LengthTest.class.getName());
-
-    // @Test
-    public void ecEc()
-            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
-            IOException {
-        LOGGER.log(Level.FINER, "\nEC -> EC");
-        PGPSecretKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("simplejid@server.tld");
-        PGPSecretKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("otherjid@other.srv");
-        encryptDecryptForSecretKeyRings(sender, recipient);
-    }
-
-
-    // @Test
-    public void RsaRsa()
-            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
-            IOException {
-        LOGGER.log(Level.FINER, "\nRSA-2048 -> RSA-2048");
-        PGPSecretKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("simplejid@server.tld", RsaLength._2048);
-        PGPSecretKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("otherjid@other.srv", RsaLength._2048);
-        encryptDecryptForSecretKeyRings(sender, recipient);
-    }
-
-    // @Test
-    public void RsaRsa4096()
-            throws PGPException,
-            IOException {
-        LOGGER.log(Level.FINER, "\nRSA-4096 -> RSA-4096");
-        PGPSecretKeyRing sender = PGPainless.readKeyRing().secretKeyRing(TestKeys.JULIET_SEC);
-        PGPSecretKeyRing recipient = PGPainless.readKeyRing().secretKeyRing(TestKeys.ROMEO_SEC);
-        encryptDecryptForSecretKeyRings(sender, recipient);
-    }
-
-    // @Test
-    public void rsaEc() throws PGPException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
-            NoSuchProviderException {
-        LOGGER.log(Level.FINER, "\nRSA-2048 -> EC");
-        PGPSecretKeyRing sender = PGPainless.generateKeyRing().simpleRsaKeyRing("simplejid@server.tld", RsaLength._2048);
-        PGPSecretKeyRing recipient = PGPainless.generateKeyRing().simpleEcKeyRing("otherjid@other.srv");
-        encryptDecryptForSecretKeyRings(sender, recipient);
-    }
-
-    // @Test
-    public void ecRsa()
-            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException,
-            IOException {
-        LOGGER.log(Level.FINER, "\nEC -> RSA-2048");
-        PGPSecretKeyRing sender = PGPainless.generateKeyRing().simpleEcKeyRing("simplejid@server.tld");
-        @SuppressWarnings("deprecation")
-        PGPSecretKeyRing recipient = PGPainless.generateKeyRing().simpleRsaKeyRing("otherjid@other.srv", RsaLength._2048);
-        encryptDecryptForSecretKeyRings(sender, recipient);
-    }
-
-    private void encryptDecryptForSecretKeyRings(PGPSecretKeyRing senderSec, PGPSecretKeyRing recipientSec)
-            throws PGPException,
-            IOException {
-        PGPPublicKeyRing recipientPub = KeyRingUtils.publicKeyRingFrom(recipientSec);
-        PGPPublicKeyRing senderPub = KeyRingUtils.publicKeyRingFrom(senderSec);
-
-        SecretKeyRingProtector keyDecryptor = new UnprotectedKeysProtector();
-
-        for (int i = 1; i <= 100; i++) {
-            byte[] secretMessage = new byte[i * 20];
-            new Random().nextBytes(secretMessage);
-
-            ByteArrayOutputStream envelope = new ByteArrayOutputStream();
-
-            OutputStream encryptor = PGPainless.encryptAndOrSign()
-                    .onOutputStream(envelope)
-                    .toRecipient(recipientPub)
-                    .and()
-                    .signInlineWith(keyDecryptor, senderSec, "simplejid@server.tld", DocumentSignatureType.BINARY_DOCUMENT)
-                    .noArmor();
-
-            Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor);
-            encryptor.close();
-            byte[] encryptedSecretMessage = envelope.toByteArray();
-
-            LOGGER.log(Level.FINER,"\n" + encryptedSecretMessage.length);
-        }
-    }
-}
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/RespectPreferredSymmetricAlgorithmDuringEncryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/RespectPreferredSymmetricAlgorithmDuringEncryptionTest.java
index b63e8332..7b18b2be 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/RespectPreferredSymmetricAlgorithmDuringEncryptionTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/RespectPreferredSymmetricAlgorithmDuringEncryptionTest.java
@@ -89,8 +89,7 @@ public class RespectPreferredSymmetricAlgorithmDuringEncryptionTest {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         EncryptionStream encryptionStream = PGPainless.encryptAndOrSign().onOutputStream(out)
                 .withOptions(
-                        ProducerOptions.encrypt(
-                                new EncryptionOptions()
+                        ProducerOptions.encrypt(new EncryptionOptions()
                                 .addRecipient(publicKeys)
                         ));
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
index 49223c71..f04b2c11 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
@@ -26,8 +26,6 @@ import java.nio.charset.StandardCharsets;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
@@ -41,12 +39,11 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.pgpainless.PGPainless;
 import org.pgpainless.algorithm.DocumentSignatureType;
-import org.pgpainless.algorithm.EncryptionPurpose;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
 import org.pgpainless.decryption_verification.OpenPgpMetadata;
 import org.pgpainless.exception.KeyValidationException;
 import org.pgpainless.implementation.ImplementationFactory;
-import org.pgpainless.key.OpenPgpV4Fingerprint;
 import org.pgpainless.key.TestKeys;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.util.KeyRingUtils;
@@ -71,15 +68,16 @@ public class SigningTest {
         PGPPublicKeyRingCollection keys = new PGPPublicKeyRingCollection(Arrays.asList(julietKeys, romeoKeys));
 
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionPurpose.STORAGE)
+        EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
                 .onOutputStream(out)
-                .toRecipients(keys)
-                .and()
-                .toRecipient(KeyRingUtils.publicKeyRingFrom(cryptieKeys))
-                .and()
-                .signInlineWith(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey),
-                        cryptieKeys, TestKeys.CRYPTIE_UID, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
-                .asciiArmor();
+                .withOptions(ProducerOptions.signAndEncrypt(
+                        EncryptionOptions.encryptDataAtRest()
+                                .addRecipients(keys)
+                                .addRecipient(KeyRingUtils.publicKeyRingFrom(cryptieKeys)),
+                        new SigningOptions()
+                                .addInlineSignature(SecretKeyRingProtector.unlockSingleKeyWith(TestKeys.CRYPTIE_PASSPHRASE, cryptieSigningKey),
+                                        cryptieKeys, TestKeys.CRYPTIE_UID, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
+                ).setAsciiArmor(true));
 
         byte[] messageBytes = "This message is signed and encrypted to Romeo and Juliet.".getBytes(StandardCharsets.UTF_8);
         ByteArrayInputStream message = new ByteArrayInputStream(messageBytes);
@@ -94,16 +92,14 @@ public class SigningTest {
         PGPSecretKeyRing julietSecret = TestKeys.getJulietSecretKeyRing();
 
         PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(Arrays.asList(romeoSecret, julietSecret));
-        Set<OpenPgpV4Fingerprint> trustedFingerprints = new HashSet<>();
-        trustedFingerprints.add(new OpenPgpV4Fingerprint(cryptieKeys));
-        trustedFingerprints.add(new OpenPgpV4Fingerprint(julietKeys));
         PGPPublicKeyRingCollection verificationKeys = new PGPPublicKeyRingCollection(Arrays.asList(KeyRingUtils.publicKeyRingFrom(cryptieKeys), romeoKeys));
 
-        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(cryptIn)
-                .decryptWith(secretKeys)
-                .verifyWith(trustedFingerprints, verificationKeys)
-                .ignoreMissingPublicKeys()
-                .build();
+        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
+                .onInputStream(cryptIn)
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKeys(secretKeys, SecretKeyRingProtector.unprotectedKeys())
+                        .addVerificationCerts(verificationKeys)
+                );
 
         ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java
index 924b2c53..bb3db213 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/signature/BindingSignatureSubpacketsTest.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.junit.jupiter.api.Test;
@@ -59,7 +60,7 @@ public class BindingSignatureSubpacketsTest {
     private Policy policy = PGPainless.getPolicy();
 
     @Test
-    public void baseCase() throws IOException {
+    public void baseCase() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -118,7 +119,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingIssuerFpOnly() throws IOException {
+    public void subkeyBindingIssuerFpOnly() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -177,7 +178,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingIssuerV6IssuerFp() throws IOException {
+    public void subkeyBindingIssuerV6IssuerFp() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -236,7 +237,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingIssuerFakeIssuer() throws IOException {
+    public void subkeyBindingIssuerFakeIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -295,7 +296,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingFakeIssuerIssuer() throws IOException {
+    public void subkeyBindingFakeIssuerIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -354,7 +355,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingFakeIssuer() throws IOException {
+    public void subkeyBindingFakeIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -413,7 +414,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingNoIssuer() throws IOException {
+    public void subkeyBindingNoIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -471,7 +472,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void unknownSubpacketHashed() throws IOException {
+    public void unknownSubpacketHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -530,7 +531,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingUnknownCriticalSubpacket() throws IOException {
+    public void subkeyBindingUnknownCriticalSubpacket() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -589,7 +590,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingUnknownSubpacketUnhashed() throws IOException {
+    public void subkeyBindingUnknownSubpacketUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -648,7 +649,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingUnknownCriticalSubpacketUnhashed() throws IOException {
+    public void subkeyBindingUnknownCriticalSubpacketUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -707,7 +708,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingUnknownNotationHashed() throws IOException {
+    public void subkeyBindingUnknownNotationHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -767,7 +768,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingCriticalUnknownNotationHashed() throws IOException {
+    public void subkeyBindingCriticalUnknownNotationHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -827,7 +828,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingUnknownNotationUnhashed() throws IOException {
+    public void subkeyBindingUnknownNotationUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -887,7 +888,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingCriticalUnknownNotationUnhashed() throws IOException {
+    public void subkeyBindingCriticalUnknownNotationUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -947,7 +948,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingBackSigFakeBackSig() throws IOException {
+    public void subkeyBindingBackSigFakeBackSig() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1017,7 +1018,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void subkeyBindingFakeBackSigBackSig() throws IOException {
+    public void subkeyBindingFakeBackSigBackSig() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1087,7 +1088,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingIssuerFpOnly() throws IOException {
+    public void primaryBindingIssuerFpOnly() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1146,7 +1147,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingIssuerV6IssuerFp() throws IOException {
+    public void primaryBindingIssuerV6IssuerFp() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1205,7 +1206,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingIssuerFakeIssuer() throws IOException {
+    public void primaryBindingIssuerFakeIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1264,7 +1265,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingFakeIssuerIssuer() throws IOException {
+    public void primaryBindingFakeIssuerIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1323,7 +1324,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingFakeIssuer() throws IOException {
+    public void primaryBindingFakeIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1382,7 +1383,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingNoIssuer() throws IOException {
+    public void primaryBindingNoIssuer() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1440,7 +1441,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingUnknownSubpacketHashed() throws IOException {
+    public void primaryBindingUnknownSubpacketHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1499,7 +1500,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingCriticalUnknownSubpacketHashed() throws IOException {
+    public void primaryBindingCriticalUnknownSubpacketHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1558,7 +1559,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingUnknownSubpacketUnhashed() throws IOException {
+    public void primaryBindingUnknownSubpacketUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1617,7 +1618,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingCriticalUnknownSubpacketUnhashed() throws IOException {
+    public void primaryBindingCriticalUnknownSubpacketUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1676,7 +1677,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingUnknownNotationHashed() throws IOException {
+    public void primaryBindingUnknownNotationHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1736,7 +1737,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingCriticalUnknownNotationHashed() throws IOException {
+    public void primaryBindingCriticalUnknownNotationHashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1796,7 +1797,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingUnknownNotationUnhashed() throws IOException {
+    public void primaryBindingUnknownNotationUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1856,7 +1857,7 @@ public class BindingSignatureSubpacketsTest {
     }
 
     @Test
-    public void primaryBindingCriticalUnknownNotationUnhashed() throws IOException {
+    public void primaryBindingCriticalUnknownNotationUnhashed() throws IOException, PGPException {
         String key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "\n" +
                 "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
@@ -1915,7 +1916,7 @@ public class BindingSignatureSubpacketsTest {
         expectSignatureValidationSucceeds(key, "Critical unknown notation is acceptable in unhashed area of primary key binding sig.");
     }
 
-    private void expectSignatureValidationSucceeds(String key, String message) throws IOException {
+    private void expectSignatureValidationSucceeds(String key, String message) throws IOException, PGPException {
         PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
         PGPSignature signature = SignatureUtils.readSignatures(sig).get(0);
 
@@ -1929,7 +1930,7 @@ public class BindingSignatureSubpacketsTest {
         }
     }
 
-    private void expectSignatureValidationFails(String key, String message) throws IOException {
+    private void expectSignatureValidationFails(String key, String message) throws IOException, PGPException {
         PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(key);
         PGPSignature signature = SignatureUtils.readSignatures(sig).get(0);
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java
index 4806f1fe..5c6846e2 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/signature/IgnoreMarkerPackets.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 import org.bouncycastle.openpgp.PGPException;
@@ -32,10 +33,10 @@ import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.api.Test;
 import org.pgpainless.PGPainless;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
 import org.pgpainless.decryption_verification.OpenPgpMetadata;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
-import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.util.KeyRingUtils;
 
 /**
@@ -151,12 +152,13 @@ public class IgnoreMarkerPackets {
         String data = "Marker + Detached signature";
         PGPSignature signature = SignatureUtils.readSignatures(sig).get(0);
 
-        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)))
-                .doNotDecrypt()
-                .verifyDetachedSignature(signature)
-                .verifyWith(publicKeys)
-                .ignoreMissingPublicKeys()
-                .build();
+        InputStream messageIn = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
+                .onInputStream(messageIn)
+                .withOptions(new ConsumerOptions()
+                        .addVerificationCert(publicKeys)
+                        .addVerificationOfDetachedSignature(signature)
+                );
 
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 
@@ -199,11 +201,13 @@ public class IgnoreMarkerPackets {
         PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
         String data = "Marker + Encrypted Message";
 
-        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)))
-                .decryptWith(SecretKeyRingProtector.unprotectedKeys(), secretKeys)
-                .verifyWith(publicKeys)
-                .ignoreMissingPublicKeys()
-                .build();
+        InputStream messageIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
+        DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
+                .onInputStream(messageIn)
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKey(secretKeys)
+                        .addVerificationCert(publicKeys)
+                );
 
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java
index a3bc1f1f..a70c5921 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/signature/KeyRevocationTest.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.junit.jupiter.api.Test;
@@ -33,7 +34,7 @@ public class KeyRevocationTest {
     private static final String data = "Hello, World";
 
     @Test
-    public void subkeySignsPrimaryKeyRevokedNoReason() throws IOException, SignatureValidationException {
+    public void subkeySignsPrimaryKeyRevokedNoReason() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -178,7 +179,7 @@ public class KeyRevocationTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__primary_key_is_not_revoked__base_case_">Sequoia Test-Suite</a>
      */
     @Test
-    public void subkeySignsPrimaryKeyNotRevoked() throws IOException, SignatureValidationException {
+    public void subkeySignsPrimaryKeyNotRevoked() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java
index a527e154..849f202a 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureChainValidatorTest.java
@@ -24,6 +24,7 @@ import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.junit.jupiter.api.Test;
@@ -39,7 +40,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__primary_key_signs_and_is_revoked__revoked__unknown">Sequoia Test Suite</a>
      */
     @Test
-    public void testPrimaryKeySignsAndIsHardRevokedUnknown() throws IOException {
+    public void testPrimaryKeySignsAndIsHardRevokedUnknown() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -188,7 +189,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__primary_key_is_revoked__revoked__unknown">Sequoia Test Suite</a>
      */
     @Test
-    public void testSubkeySignsPrimaryKeyIsHardRevokedUnknown() throws IOException {
+    public void testSubkeySignsPrimaryKeyIsHardRevokedUnknown() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -338,7 +339,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__subkey_is_revoked__revoked__unknown">Sequoia Test Suite</a>
      */
     @Test
-    public void testSubkeySignsAndIsHardRevokedUnknown() throws IOException {
+    public void testSubkeySignsAndIsHardRevokedUnknown() throws IOException, PGPException {
         String keyWithHardRev = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -488,7 +489,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__primary_key_signs_and_is_revoked__revoked__superseded">Sequoia Test Suite</a>
      */
     @Test
-    public void testPrimaryKeySignsAndIsSoftRevokedSuperseded() throws IOException {
+    public void testPrimaryKeySignsAndIsSoftRevokedSuperseded() throws IOException, PGPException {
         String keyWithSoftRev = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -643,7 +644,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__subkey_signs__primary_key_is_revoked__revoked__superseded">Sequoia Test Suite</a>
      */
     @Test
-    public void testSubkeySignsPrimaryKeyIsSoftRevokedSuperseded() throws IOException {
+    public void testSubkeySignsPrimaryKeyIsSoftRevokedSuperseded() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -794,7 +795,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Key_revocation_test__primary_key_signs_and_is_revoked__revoked__key_retired">Sequoia Test Suite</a>
      */
     @Test
-    public void testPrimaryKeySignsAndIsSoftRevokedRetired() throws IOException {
+    public void testPrimaryKeySignsAndIsSoftRevokedRetired() throws IOException, PGPException {
         String key = "-----BEGIN PGP ARMORED FILE-----\n" +
                 "Comment: ASCII Armor added by openpgp-interoperability-test-suite\n" +
                 "\n" +
@@ -945,7 +946,7 @@ public class SignatureChainValidatorTest {
      * @see <a href="https://tests.sequoia-pgp.org/#Temporary_validity">Sequoia Test Suite</a>
      */
     @Test
-    public void testTemporaryValidity() throws IOException {
+    public void testTemporaryValidity() throws IOException, PGPException {
         String keyA = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
                 "Comment: D1A6 6E1A 23B1 82C9 980F  788C FBFC C82A 015E 7330\n" +
                 "Comment: Bob Babbage <bob@openpgp.example>\n" +
diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureStructureTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureStructureTest.java
index 610a3e72..e25cd682 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureStructureTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureStructureTest.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.util.List;
 
 import org.bouncycastle.bcpg.sig.NotationData;
+import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.util.encoders.Hex;
 import org.junit.jupiter.api.BeforeAll;
@@ -38,7 +39,7 @@ public class SignatureStructureTest {
     private static PGPSignature signature;
 
     @BeforeAll
-    public static void parseSignature() throws IOException {
+    public static void parseSignature() throws IOException, PGPException {
         // see https://tests.sequoia-pgp.org/#Detached_signature_with_Subpackets (base case)
         signature = SignatureUtils.readSignatures("-----BEGIN PGP SIGNATURE-----\n" +
                 "\n" +
diff --git a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/MultiPassphraseSymmetricEncryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/MultiPassphraseSymmetricEncryptionTest.java
index 50c3662a..8eb19130 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/MultiPassphraseSymmetricEncryptionTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/MultiPassphraseSymmetricEncryptionTest.java
@@ -26,8 +26,11 @@ import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.pgpainless.PGPainless;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
+import org.pgpainless.encryption_signing.EncryptionOptions;
 import org.pgpainless.encryption_signing.EncryptionStream;
+import org.pgpainless.encryption_signing.ProducerOptions;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.util.Passphrase;
 
@@ -44,12 +47,11 @@ public class MultiPassphraseSymmetricEncryptionTest {
         ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
         EncryptionStream encryptor = PGPainless.encryptAndOrSign()
                 .onOutputStream(ciphertextOut)
-                .forPassphrase(Passphrase.fromPassword("p1"))
-                .and()
-                .forPassphrase(Passphrase.fromPassword("p2"))
-                .and()
-                .doNotSign()
-                .noArmor();
+                .withOptions(ProducerOptions.encrypt(
+                        EncryptionOptions.encryptCommunications()
+                        .addPassphrase(Passphrase.fromPassword("p1"))
+                        .addPassphrase(Passphrase.fromPassword("p2"))
+                ).setAsciiArmor(false));
 
         Streams.pipeAll(plaintextIn, encryptor);
         encryptor.close();
@@ -58,10 +60,10 @@ public class MultiPassphraseSymmetricEncryptionTest {
 
         // decrypting the p1 package with p2 first will not work. Test if it is handled correctly.
         for (Passphrase passphrase : new Passphrase[] {Passphrase.fromPassword("p2"), Passphrase.fromPassword("p1")}) {
-            DecryptionStream decryptor = PGPainless.decryptAndOrVerify().onInputStream(new ByteArrayInputStream(ciphertext))
-                    .decryptWith(passphrase)
-                    .doNotVerify()
-                    .build();
+            DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
+                    .onInputStream(new ByteArrayInputStream(ciphertext))
+                    .withOptions(new ConsumerOptions()
+                    .addDecryptionPassphrase(passphrase));
 
             ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
index 9c7e6f8b..b4bc4e72 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/symmetric_encryption/SymmetricEncryptionTest.java
@@ -31,8 +31,8 @@ import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.pgpainless.PGPainless;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
-import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
 import org.pgpainless.encryption_signing.EncryptionOptions;
 import org.pgpainless.encryption_signing.EncryptionStream;
 import org.pgpainless.encryption_signing.ProducerOptions;
@@ -60,14 +60,13 @@ public class SymmetricEncryptionTest {
         Passphrase encryptionPassphrase = Passphrase.fromPassword("greenBeans");
 
         ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
-        EncryptionBuilderInterface.Armor armor = PGPainless.encryptAndOrSign().onOutputStream(ciphertextOut)
-                .forPassphrase(encryptionPassphrase)
-                .and()
-                .toRecipient(encryptionKey)
-                .and()
-                .doNotSign();
-        EncryptionStream encryptor = armor
-                .noArmor();
+        EncryptionStream encryptor = PGPainless.encryptAndOrSign()
+                .onOutputStream(ciphertextOut)
+                .withOptions(ProducerOptions.encrypt(
+                        EncryptionOptions.encryptCommunications()
+                                .addPassphrase(encryptionPassphrase)
+                                .addRecipient(encryptionKey)
+                ));
 
         Streams.pipeAll(plaintextIn, encryptor);
         encryptor.close();
@@ -77,9 +76,8 @@ public class SymmetricEncryptionTest {
         // Test symmetric decryption
         DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(ciphertext))
-                .decryptWith(encryptionPassphrase)
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionPassphrase(encryptionPassphrase));
 
         ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
 
@@ -95,9 +93,8 @@ public class SymmetricEncryptionTest {
                 new SolitaryPassphraseProvider(Passphrase.fromPassword(TestKeys.CRYPTIE_PASSWORD)));
         decryptor = PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(ciphertext))
-                .decryptWith(protector, decryptionKeys)
-                .doNotVerify()
-                .build();
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionKeys(decryptionKeys, protector));
 
         decrypted = new ByteArrayOutputStream();
 
@@ -126,8 +123,7 @@ public class SymmetricEncryptionTest {
 
         assertThrows(MissingDecryptionMethodException.class, () -> PGPainless.decryptAndOrVerify()
                 .onInputStream(new ByteArrayInputStream(ciphertextOut.toByteArray()))
-                .decryptWith(Passphrase.fromPassword("meldir"))
-                .doNotVerify()
-                .build());
+                .withOptions(new ConsumerOptions()
+                        .addDecryptionPassphrase(Passphrase.fromPassword("meldir"))));
     }
 }
diff --git a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestEncryptCommsStorageFlagsDifferentiated.java b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestEncryptCommsStorageFlagsDifferentiated.java
index 2728933e..772995cb 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestEncryptCommsStorageFlagsDifferentiated.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestEncryptCommsStorageFlagsDifferentiated.java
@@ -17,7 +17,6 @@ package org.pgpainless.weird_keys;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.NoSuchAlgorithmException;
@@ -27,9 +26,8 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.junit.jupiter.api.Test;
 import org.pgpainless.PGPainless;
-import org.pgpainless.algorithm.EncryptionPurpose;
 import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
+import org.pgpainless.encryption_signing.EncryptionOptions;
 import org.pgpainless.key.generation.KeySpec;
 import org.pgpainless.key.generation.type.KeyType;
 import org.pgpainless.key.generation.type.rsa.RsaLength;
@@ -49,13 +47,10 @@ public class TestEncryptCommsStorageFlagsDifferentiated {
                 .withPrimaryUserId("cannot@encrypt.comms")
                 .withoutPassphrase()
                 .build();
+
         PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
 
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        EncryptionBuilderInterface.ToRecipients builder = PGPainless.encryptAndOrSign(EncryptionPurpose.COMMUNICATIONS)
-                .onOutputStream(out);
-
-        // since the key does not carry the flag ENCRYPT_COMMS, it cannot be used by the stream.
-        assertThrows(IllegalArgumentException.class, () -> builder.toRecipient(publicKeys));
+        assertThrows(IllegalArgumentException.class, () -> EncryptionOptions.encryptCommunications()
+                .addRecipient(publicKeys));
     }
 }
diff --git a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java
index 766afdee..d1f8f181 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/weird_keys/TestTwoSubkeysEncryption.java
@@ -59,7 +59,7 @@ public class TestTwoSubkeysEncryption {
         PGPSecretKeyRing twoSuitableSubkeysKeyRing = WeirdKeys.getTwoCryptSubkeysKey();
         PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(twoSuitableSubkeysKeyRing);
         ByteArrayOutputStream out = new ByteArrayOutputStream();
-        EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionPurpose.STORAGE)
+        EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
                 .onOutputStream(out)
                 .withOptions(
                         ProducerOptions.encrypt(new EncryptionOptions(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS)
diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java
index 4d14e671..de3f57e9 100644
--- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java
+++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java
@@ -24,17 +24,15 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.HashSet;
 import java.util.List;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.bouncycastle.openpgp.PGPSignature;
 import org.bouncycastle.util.io.Streams;
 import org.pgpainless.PGPainless;
-import org.pgpainless.decryption_verification.DecryptionBuilderInterface;
+import org.pgpainless.decryption_verification.ConsumerOptions;
 import org.pgpainless.decryption_verification.DecryptionStream;
 import org.pgpainless.decryption_verification.OpenPgpMetadata;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
@@ -108,34 +106,34 @@ public class Decrypt implements Runnable {
             System.exit(1);
         }
 
-        PGPSecretKeyRingCollection secretKeys;
-        List<PGPPublicKeyRing> verifyWith = null;
+        ConsumerOptions options = new ConsumerOptions();
 
+        List<PGPPublicKeyRing> verifyWith = null;
         try {
+
             List<PGPSecretKeyRing> secretKeyRings = loadKeysFromFiles(keys);
-            secretKeys = new PGPSecretKeyRingCollection(secretKeyRings);
+            for (PGPSecretKeyRing secretKey : secretKeyRings) {
+                options.addDecryptionKey(secretKey);
+            }
+
             if (certs != null) {
                 verifyWith = SopKeyUtil.loadCertificatesFromFile(certs);
+                for (PGPPublicKeyRing cert : verifyWith) {
+                    options.addVerificationCert(cert);
+                }
             }
+
         } catch (IOException | PGPException e) {
             err_ln(e.getMessage());
             System.exit(1);
             return;
         }
 
-
-        DecryptionBuilderInterface.Verify builder = PGPainless.decryptAndOrVerify()
-                .onInputStream(System.in)
-                .decryptWith(secretKeys);
-        DecryptionStream decryptionStream = null;
+        DecryptionStream decryptionStream;
         try {
-            if (verifyWith != null) {
-                decryptionStream = builder.verifyWith(new HashSet<>(verifyWith))
-                        .ignoreMissingPublicKeys().build();
-            } else {
-                decryptionStream = builder.doNotVerify()
-                        .build();
-            }
+            decryptionStream = PGPainless.decryptAndOrVerify()
+                    .onInputStream(System.in)
+                    .withOptions(options);
         } catch (IOException | PGPException e) {
             err_ln("Error constructing decryption stream: " + e.getMessage());
             System.exit(1);
@@ -169,14 +167,14 @@ public class Decrypt implements Runnable {
                 PGPSignature signature = metadata.getVerifiedSignatures().get(fingerprint);
                 sb.append(df.format(signature.getCreationTime())).append(' ')
                         .append(fingerprint).append(' ')
-                        .append(new OpenPgpV4Fingerprint(verifier)).append('\n');
+                        .append(verifier != null ? new OpenPgpV4Fingerprint(verifier) : "null").append('\n');
             }
 
             try {
                 verifyOut.createNewFile();
                 PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut));
                 // CHECKSTYLE:OFF
-                verifyPrinter.println(sb.toString());
+                verifyPrinter.println(sb);
                 // CHECKSTYLE:ON
                 verifyPrinter.close();
             } catch (IOException e) {
diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java
index 0456fb47..52581709 100644
--- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java
+++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Verify.java
@@ -15,20 +15,11 @@
  */
 package org.pgpainless.sop.commands;
 
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.bouncycastle.util.io.Streams;
-import org.pgpainless.PGPainless;
-import org.pgpainless.decryption_verification.DecryptionStream;
-import org.pgpainless.decryption_verification.OpenPgpMetadata;
-import org.pgpainless.key.OpenPgpV4Fingerprint;
-import picocli.CommandLine;
+import static org.pgpainless.sop.Print.err_ln;
+import static org.pgpainless.sop.Print.print_ln;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.text.DateFormat;
@@ -36,12 +27,20 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.TimeZone;
 
-import static org.pgpainless.sop.Print.err_ln;
-import static org.pgpainless.sop.Print.print_ln;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.util.io.Streams;
+import org.pgpainless.PGPainless;
+import org.pgpainless.decryption_verification.ConsumerOptions;
+import org.pgpainless.decryption_verification.DecryptionStream;
+import org.pgpainless.decryption_verification.OpenPgpMetadata;
+import org.pgpainless.key.OpenPgpV4Fingerprint;
+import picocli.CommandLine;
 
 @CommandLine.Command(name = "verify",
         description = "Verify a detached signature over the data from standard input",
@@ -89,32 +88,35 @@ public class Verify implements Runnable {
         Date notBeforeDate = parseNotBefore();
         Date notAfterDate = parseNotAfter();
 
+        ConsumerOptions options = new ConsumerOptions();
+        try (FileInputStream sigIn = new FileInputStream(signature)) {
+            options.addVerificationOfDetachedSignatures(sigIn);
+        } catch (IOException | PGPException e) {
+            err_ln("Cannot read detached signature: " + e.getMessage());
+            System.exit(1);
+        }
+
         Map<PGPPublicKeyRing, File> publicKeys = readCertificatesFromFiles();
         if (publicKeys.isEmpty()) {
             err_ln("No certificates supplied.");
             System.exit(19);
         }
 
+        for (PGPPublicKeyRing cert : publicKeys.keySet()) {
+            options.addVerificationCert(cert);
+        }
+
         OpenPgpMetadata metadata;
-        try (FileInputStream sigIn = new FileInputStream(signature)) {
+        try {
             DecryptionStream verifier = PGPainless.decryptAndOrVerify()
                     .onInputStream(System.in)
-                    .doNotDecrypt()
-                    .verifyDetachedSignature(sigIn)
-                    .verifyWith(new HashSet<>(publicKeys.keySet()))
-                    .ignoreMissingPublicKeys()
-                    .build();
+                    .withOptions(options);
 
             OutputStream out = new NullOutputStream();
             Streams.pipeAll(verifier, out);
             verifier.close();
 
             metadata = verifier.getResult();
-        } catch (FileNotFoundException e) {
-            err_ln("Signature file not found:");
-            err_ln(e.getMessage());
-            System.exit(1);
-            return;
         } catch (IOException | PGPException e) {
             err_ln("Signature validation failed.");
             err_ln(e.getMessage());

From df22c2a102b4b7c234fa339b015ce3c007288d0c Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Wed, 16 Jun 2021 15:49:43 +0200
Subject: [PATCH 5/8] Remove duplicate code and throw
 NotYetImplementedExceptions where sensible

---
 .../ConsumerOptions.java                      | 58 +++++--------------
 .../exception/NotYetImplementedException.java | 23 ++++++++
 2 files changed, 39 insertions(+), 42 deletions(-)
 create mode 100644 pgpainless-core/src/main/java/org/pgpainless/exception/NotYetImplementedException.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
index 86bc8176..5cbf2ce7 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java
@@ -17,7 +17,6 @@ package org.pgpainless.decryption_verification;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -28,19 +27,15 @@ import java.util.Set;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.bouncycastle.bcpg.MarkerPacket;
-import org.bouncycastle.openpgp.PGPCompressedData;
 import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPObjectFactory;
 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.PGPSignatureList;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.pgpainless.implementation.ImplementationFactory;
+import org.pgpainless.exception.NotYetImplementedException;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
+import org.pgpainless.signature.SignatureUtils;
 import org.pgpainless.util.Passphrase;
 
 /**
@@ -66,12 +61,16 @@ public class ConsumerOptions {
     /**
      * Consider signatures made before the given timestamp invalid.
      *
+     * Note: This method does not have any effect yet.
+     * TODO: Add support for custom signature validity date ranges
+     *
      * @param timestamp timestamp
      * @return options
      */
     public ConsumerOptions verifyNotBefore(Date timestamp) {
         this.verifyNotBefore = timestamp;
-        return this;
+        throw new NotYetImplementedException();
+        // return this;
     }
 
     public Date getVerifyNotBefore() {
@@ -81,12 +80,16 @@ public class ConsumerOptions {
     /**
      * Consider signatures made after the given timestamp invalid.
      *
+     * Note: This method does not have any effect yet.
+     * TODO: Add support for custom signature validity date ranges
+     *
      * @param timestamp timestamp
      * @return options
      */
     public ConsumerOptions verifyNotAfter(Date timestamp) {
         this.verifyNotAfter = timestamp;
-        return this;
+        throw new NotYetImplementedException();
+        // return this;
     }
 
     public Date getVerifyNotAfter() {
@@ -118,37 +121,7 @@ public class ConsumerOptions {
     }
 
     public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException {
-        List<PGPSignature> signatures = new ArrayList<>();
-        InputStream pgpIn = PGPUtil.getDecoderStream(signatureInputStream);
-        PGPObjectFactory objectFactory = new PGPObjectFactory(
-                pgpIn, ImplementationFactory.getInstance().getKeyFingerprintCalculator());
-
-        Object nextObject = objectFactory.nextObject();
-        while (nextObject != null) {
-            if (nextObject instanceof MarkerPacket) {
-                nextObject = objectFactory.nextObject();
-                continue;
-            }
-            if (nextObject instanceof PGPCompressedData) {
-                PGPCompressedData compressedData = (PGPCompressedData) nextObject;
-                objectFactory = new PGPObjectFactory(compressedData.getDataStream(),
-                        ImplementationFactory.getInstance().getKeyFingerprintCalculator());
-                nextObject = objectFactory.nextObject();
-                continue;
-            }
-            if (nextObject instanceof PGPSignatureList) {
-                PGPSignatureList signatureList = (PGPSignatureList) nextObject;
-                for (PGPSignature s : signatureList) {
-                    signatures.add(s);
-                }
-            }
-            if (nextObject instanceof PGPSignature) {
-                signatures.add((PGPSignature) nextObject);
-            }
-            nextObject = objectFactory.nextObject();
-        }
-        pgpIn.close();
-
+        List<PGPSignature> signatures = SignatureUtils.readSignatures(signatureInputStream);
         return addVerificationOfDetachedSignatures(signatures);
     }
 
@@ -186,7 +159,7 @@ public class ConsumerOptions {
      * Attempt decryption using a session key.
      *
      * Note: PGPainless does not yet support decryption with session keys.
-     * TODO: Implement
+     * TODO: Add support for decryption using session key.
      *
      * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a>
      *
@@ -195,7 +168,8 @@ public class ConsumerOptions {
      */
     public ConsumerOptions setSessionKey(@Nonnull byte[] sessionKey) {
         this.sessionKey = sessionKey;
-        return this;
+        throw new NotYetImplementedException();
+        // return this;
     }
 
     /**
diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/NotYetImplementedException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/NotYetImplementedException.java
new file mode 100644
index 00000000..12478f05
--- /dev/null
+++ b/pgpainless-core/src/main/java/org/pgpainless/exception/NotYetImplementedException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 Paul Schaub.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.pgpainless.exception;
+
+/**
+ * Method that gets thrown if the user requests some functionality which is not yet implemented.
+ */
+public class NotYetImplementedException extends RuntimeException {
+
+}

From e7f685e63b566988f0ca309c4d40bea96108014a Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Wed, 23 Jun 2021 19:20:47 +0200
Subject: [PATCH 6/8] Tests: Make use of KeyRingInfo to determine encryption
 subkeys

---
 .../DecryptHiddenRecipientMessage.java              | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
index 7cc76f33..e86659c7 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/DecryptHiddenRecipientMessage.java
@@ -21,7 +21,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Set;
+import java.util.List;
 
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
@@ -30,11 +30,10 @@ import org.bouncycastle.util.io.Streams;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.pgpainless.PGPainless;
-import org.pgpainless.algorithm.KeyFlag;
+import org.pgpainless.algorithm.EncryptionPurpose;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.OpenPgpV4Fingerprint;
-import org.pgpainless.key.util.KeyRingUtils;
-import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
+import org.pgpainless.key.info.KeyRingInfo;
 
 public class DecryptHiddenRecipientMessage {
 
@@ -155,10 +154,8 @@ public class DecryptHiddenRecipientMessage {
         OpenPgpMetadata metadata = decryptionStream.getResult();
         assertEquals(0, metadata.getRecipientKeyIds().size());
 
-        // Hacky way of getting the encryption subkey of the key ring
-        //  TODO: Create convenient method for this
-        Set<PGPPublicKey> encryptionKeys = new EncryptionKeySelectionStrategy(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
-                .selectKeysFromKeyRing(KeyRingUtils.publicKeyRingFrom(secretKeys));
+        KeyRingInfo info = new KeyRingInfo(secretKeys);
+        List<PGPPublicKey> encryptionKeys = info.getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS);
         assertEquals(1, encryptionKeys.size());
 
         assertEquals(new OpenPgpV4Fingerprint(encryptionKeys.iterator().next()), metadata.getDecryptionFingerprint());

From 3c37072774a30fb51103c9288ecd452f10815a78 Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Wed, 23 Jun 2021 19:33:13 +0200
Subject: [PATCH 7/8] Remove unused Selection classes

---
 .../org/pgpainless/key/util/KeyRingUtils.java |  36 ----
 .../util/selection/key/impl/And.java          |  66 -------
 .../impl/EncryptionKeySelectionStrategy.java  |  54 ------
 .../impl/HasAllKeyFlagSelectionStrategy.java  |  71 --------
 .../impl/HasAnyKeyFlagSelectionStrategy.java  |  74 --------
 .../key/impl/KeyBelongsToKeyRing.java         |  67 -------
 .../util/selection/key/impl/NoRevocation.java |  51 ------
 .../util/selection/key/impl/Or.java           |  66 -------
 .../impl/SignatureKeySelectionStrategy.java   |  55 ------
 .../util/selection/key/impl/package-info.java |  19 --
 .../encryption_signing/SigningTest.java       |   7 +-
 .../java/org/pgpainless/util/BCUtilTest.java  |  31 ----
 .../key/AndOrSelectionStrategyTest.java       |  62 -------
 .../key/KeyBelongsToKeyRingTest.java          |  54 ------
 .../KeyFlagBasedSelectionStrategyTest.java    | 169 ------------------
 15 files changed, 3 insertions(+), 879 deletions(-)
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/And.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/EncryptionKeySelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAllKeyFlagSelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/KeyBelongsToKeyRing.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/NoRevocation.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/Or.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/SignatureKeySelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/package-info.java
 delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/selection/key/AndOrSelectionStrategyTest.java
 delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyBelongsToKeyRingTest.java
 delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyFlagBasedSelectionStrategyTest.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java
index 7b75e084..c7872589 100644
--- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java
+++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java
@@ -21,7 +21,6 @@ import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
-
 import javax.annotation.Nonnull;
 
 import org.bouncycastle.openpgp.PGPException;
@@ -35,10 +34,6 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.protection.UnlockSecretKey;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.impl.And;
-import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing;
-import org.pgpainless.util.selection.key.impl.NoRevocation;
 
 public class KeyRingUtils {
 
@@ -162,37 +157,6 @@ public class KeyRingUtils {
         return new PGPSecretKeyRingCollection(Arrays.asList(rings));
     }
 
-    /**
-     * Remove all keys from the key ring, are either not having a subkey signature from the master key
-     * (identified by {@code masterKeyId}), or are revoked ("normal" key revocation, as well as subkey revocation).
-     *
-     * @param ring key ring
-     * @param masterKey master key
-     * @return "cleaned" key ring
-     */
-    public static PGPSecretKeyRing removeUnassociatedKeysFromKeyRing(@Nonnull PGPSecretKeyRing ring,
-                                                                     @Nonnull PGPPublicKey masterKey) {
-        if (!masterKey.isMasterKey()) {
-            throw new IllegalArgumentException("Given key is not a master key.");
-        }
-        // Only select keys which are signed by the master key and not revoked.
-        PublicKeySelectionStrategy selector = new And.PubKeySelectionStrategy(
-                new KeyBelongsToKeyRing.PubkeySelectionStrategy(masterKey),
-                new NoRevocation.PubKeySelectionStrategy());
-
-        PGPSecretKeyRing cleaned = ring;
-
-        Iterator<PGPSecretKey> secretKeys = ring.getSecretKeys();
-        while (secretKeys.hasNext()) {
-            PGPSecretKey secretKey = secretKeys.next();
-            if (!selector.accept(secretKey.getPublicKey())) {
-                cleaned = PGPSecretKeyRing.removeSecretKey(cleaned, secretKey);
-            }
-        }
-
-        return cleaned;
-    }
-
     public static boolean keyRingContainsKeyWithId(@Nonnull PGPPublicKeyRing ring,
                                                    long keyId) {
         return ring.getPublicKey(keyId) != null;
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/And.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/And.java
deleted file mode 100644
index f2f7194e..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/And.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-public class And {
-
-    public static class PubKeySelectionStrategy extends PublicKeySelectionStrategy {
-
-        private final Set<PublicKeySelectionStrategy> strategies = new HashSet<>();
-
-        public PubKeySelectionStrategy(@Nonnull PublicKeySelectionStrategy... strategies) {
-            this.strategies.addAll(Arrays.asList(strategies));
-        }
-
-        @Override
-        public boolean accept(PGPPublicKey key) {
-            boolean accept = true;
-            for (PublicKeySelectionStrategy strategy : strategies) {
-                accept &= strategy.accept(key);
-            }
-            return accept;
-        }
-    }
-
-    public static class SecKeySelectionStrategy extends SecretKeySelectionStrategy {
-
-        private final Set<SecretKeySelectionStrategy> strategies = new HashSet<>();
-
-        public SecKeySelectionStrategy(@Nonnull SecretKeySelectionStrategy... strategies) {
-            this.strategies.addAll(Arrays.asList(strategies));
-        }
-
-        @Override
-        public boolean accept(PGPSecretKey key) {
-            boolean accept = true;
-            for (SecretKeySelectionStrategy strategy : strategies) {
-                accept &= strategy.accept(key);
-            }
-            return accept;
-        }
-    }
-
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/EncryptionKeySelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/EncryptionKeySelectionStrategy.java
deleted file mode 100644
index 71c5e26e..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/EncryptionKeySelectionStrategy.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.algorithm.PublicKeyAlgorithm;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-
-/**
- * Key Selection Strategy that only accepts {@link PGPPublicKey}s which are capable of encryption.
- */
-public class EncryptionKeySelectionStrategy extends PublicKeySelectionStrategy {
-
-    public static final Logger LOGGER = Logger.getLogger(EncryptionKeySelectionStrategy.class.getName());
-
-    private final HasAnyKeyFlagSelectionStrategy.PublicKey keyFlagSelector;
-
-    public EncryptionKeySelectionStrategy(KeyFlag... flags) {
-        this.keyFlagSelector = new HasAnyKeyFlagSelectionStrategy.PublicKey(flags);
-    }
-
-    @Override
-    public boolean accept(@Nonnull PGPPublicKey key) {
-        if (!key.isEncryptionKey()) {
-            LOGGER.log(Level.FINE, "Rejecting key " + Long.toHexString(key.getKeyID()) + " as its algorithm (" +
-                    PublicKeyAlgorithm.fromId(key.getAlgorithm()) + ") is not suitable of encryption.");
-            return false;
-        }
-        if (!keyFlagSelector.accept(key)) {
-            LOGGER.log(Level.FINE, "Rejecting key " + Long.toHexString(key.getKeyID()) + " as it does not the appropriate encryption key flags.");
-            return false;
-        }
-
-        return true;
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAllKeyFlagSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAllKeyFlagSelectionStrategy.java
deleted file mode 100644
index 791687ec..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAllKeyFlagSelectionStrategy.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2021 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.Iterator;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-/**
- * Selection Strategy that accepts a key if it carries all of the specified key flags.
- */
-public class HasAllKeyFlagSelectionStrategy {
-
-    public static class PublicKey extends PublicKeySelectionStrategy {
-
-        private final int keyFlagMask;
-
-        public PublicKey(KeyFlag... flags) {
-            this(KeyFlag.toBitmask(flags));
-        }
-
-        public PublicKey(int mask) {
-            this.keyFlagMask = mask;
-        }
-
-        @Override
-        public boolean accept(PGPPublicKey key) {
-            Iterator<PGPSignature> signatures = key.getSignatures();
-            int flags = signatures.next().getHashedSubPackets().getKeyFlags();
-            return (keyFlagMask & flags) == keyFlagMask;
-        }
-    }
-
-    public static class SecretKey extends SecretKeySelectionStrategy {
-
-        private final int keyFlagMask;
-
-        public SecretKey(KeyFlag... flags) {
-            this(KeyFlag.toBitmask(flags));
-        }
-
-        public SecretKey(int mask) {
-            this.keyFlagMask = mask;
-        }
-
-        @Override
-        public boolean accept(PGPSecretKey key) {
-            Iterator<PGPSignature> signatures = key.getPublicKey().getSignatures();
-            int flags = signatures.next().getHashedSubPackets().getKeyFlags();
-            return (keyFlagMask & flags) == keyFlagMask;
-        }
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java
deleted file mode 100644
index 70b7267c..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/HasAnyKeyFlagSelectionStrategy.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2021 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.Iterator;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-/**
- * Selection Strategies that accept a key if it carries at least one of the given key flags.
- */
-public class HasAnyKeyFlagSelectionStrategy {
-
-    public static class PublicKey extends PublicKeySelectionStrategy {
-
-        private final int keyFlagMask;
-
-        public PublicKey(KeyFlag... flags) {
-            this(KeyFlag.toBitmask(flags));
-        }
-
-        public PublicKey(int mask) {
-            this.keyFlagMask = mask;
-        }
-
-        @Override
-        public boolean accept(PGPPublicKey key) {
-            Iterator<PGPSignature> signatures = key.getSignatures();
-            int flags = 0;
-            while (signatures.hasNext()) {
-                flags = signatures.next().getHashedSubPackets().getKeyFlags();
-            }
-            return (keyFlagMask & flags) != 0;
-        }
-    }
-
-    public static class SecretKey extends SecretKeySelectionStrategy {
-
-        private final int keyFlagMask;
-
-        public SecretKey(KeyFlag... flags) {
-            this(KeyFlag.toBitmask(flags));
-        }
-
-        public SecretKey(int mask) {
-            this.keyFlagMask = mask;
-        }
-
-        @Override
-        public boolean accept(PGPSecretKey key) {
-            Iterator<PGPSignature> signatures = key.getPublicKey().getSignatures();
-            int flags = signatures.next().getHashedSubPackets().getKeyFlags();
-            return (keyFlagMask & flags) != 0;
-        }
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/KeyBelongsToKeyRing.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/KeyBelongsToKeyRing.java
deleted file mode 100644
index 5e2b4af2..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/KeyBelongsToKeyRing.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import javax.annotation.Nonnull;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSignature;
-import org.pgpainless.implementation.ImplementationFactory;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-
-public class KeyBelongsToKeyRing {
-
-    private static final Logger LOGGER = Logger.getLogger(KeyBelongsToKeyRing.class.getName());
-
-    public static class PubkeySelectionStrategy extends PublicKeySelectionStrategy {
-
-        private final PGPPublicKey masterKey;
-
-        public PubkeySelectionStrategy(PGPPublicKey masterKey) {
-            this.masterKey = masterKey;
-        }
-
-        @Override
-        public boolean accept(@Nonnull PGPPublicKey key) {
-            // Same key -> accept
-            if (Arrays.equals(masterKey.getFingerprint(), key.getFingerprint())) {
-                return true;
-            }
-
-            Iterator<PGPSignature> signatures = key.getSignaturesForKeyID(masterKey.getKeyID());
-            while (signatures.hasNext()) {
-                PGPSignature signature = signatures.next();
-                if (signature.getSignatureType() == PGPSignature.SUBKEY_BINDING) {
-                    try {
-                        signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), masterKey);
-                        return signature.verifyCertification(masterKey, key);
-                    } catch (PGPException e) {
-                        LOGGER.log(Level.WARNING, "Could not verify subkey signature of key " +
-                                Long.toHexString(masterKey.getKeyID()) + " on key " + Long.toHexString(key.getKeyID()));
-
-                        return false;
-                    }
-                }
-            }
-            return false;
-        }
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/NoRevocation.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/NoRevocation.java
deleted file mode 100644
index c5c7899f..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/NoRevocation.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-/**
- * Key Selection Strategies that do accept only keys, which have no revocation.
- */
-public class NoRevocation {
-
-    /**
-     * Key Selection Strategy which only accepts {@link PGPPublicKey}s which have no revocation.
-     */
-    public static class PubKeySelectionStrategy extends PublicKeySelectionStrategy {
-
-        @Override
-        public boolean accept(@Nonnull PGPPublicKey key) {
-            return !key.hasRevocation();
-        }
-    }
-
-    /**
-     * Key Selection Strategy which only accepts {@link PGPSecretKey}s which have no revocation.
-     */
-    public static class SecKeySelectionStrategy extends SecretKeySelectionStrategy {
-
-        @Override
-        public boolean accept(@Nonnull PGPSecretKey key) {
-            return !key.getPublicKey().hasRevocation();
-        }
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/Or.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/Or.java
deleted file mode 100644
index 880df4f1..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/Or.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-public class Or {
-
-    public static class PubKeySelectionStrategy extends PublicKeySelectionStrategy {
-
-        private final Set<PublicKeySelectionStrategy> strategies = new HashSet<>();
-
-        public PubKeySelectionStrategy(@Nonnull PublicKeySelectionStrategy... strategies) {
-            this.strategies.addAll(Arrays.asList(strategies));
-        }
-
-        @Override
-        public boolean accept(PGPPublicKey key) {
-            boolean accept = false;
-            for (PublicKeySelectionStrategy strategy : strategies) {
-                accept |= strategy.accept(key);
-            }
-            return accept;
-        }
-    }
-
-    public static class SecKeySelectionStrategy extends SecretKeySelectionStrategy {
-
-        private final Set<SecretKeySelectionStrategy> strategies = new HashSet<>();
-
-        public SecKeySelectionStrategy(@Nonnull SecretKeySelectionStrategy... strategies) {
-            this.strategies.addAll(Arrays.asList(strategies));
-        }
-
-        @Override
-        public boolean accept(PGPSecretKey key) {
-            boolean accept = false;
-            for (SecretKeySelectionStrategy strategy : strategies) {
-                accept |= strategy.accept(key);
-            }
-            return accept;
-        }
-    }
-
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/SignatureKeySelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/SignatureKeySelectionStrategy.java
deleted file mode 100644
index 9b5e310b..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/SignatureKeySelectionStrategy.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key.impl;
-
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.algorithm.PublicKeyAlgorithm;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-/**
- * Key Selection Strategy that only accepts {@link PGPSecretKey}s which are capable of signing.
- */
-public class SignatureKeySelectionStrategy extends SecretKeySelectionStrategy {
-
-    private static final Logger LOGGER = Logger.getLogger(SignatureKeySelectionStrategy.class.getName());
-
-    HasAnyKeyFlagSelectionStrategy.SecretKey flagSelector =
-            new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.SIGN_DATA);
-
-    @Override
-    public boolean accept(@Nonnull PGPSecretKey key) {
-        boolean hasSignDataKeyFlag = flagSelector.accept(key);
-
-        if (!key.isSigningKey()) {
-            LOGGER.log(Level.FINE, "Rejecting key " + Long.toHexString(key.getKeyID()) + " as its algorithm (" +
-                    PublicKeyAlgorithm.fromId(key.getPublicKey().getAlgorithm()) + ") is not capable of signing.");
-            return false;
-        }
-
-        if (!hasSignDataKeyFlag) {
-            LOGGER.log(Level.FINE, "Rejecting key " + Long.toHexString(key.getKeyID()) +
-                    " as it does not carry the key flag SIGN_DATA.");
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/package-info.java
deleted file mode 100644
index 6729a311..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/impl/package-info.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
- * Implementations of Key Selection Strategies.
- */
-package org.pgpainless.util.selection.key.impl;
diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
index f04b2c11..ab03f45a 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java
@@ -45,10 +45,10 @@ import org.pgpainless.decryption_verification.OpenPgpMetadata;
 import org.pgpainless.exception.KeyValidationException;
 import org.pgpainless.implementation.ImplementationFactory;
 import org.pgpainless.key.TestKeys;
+import org.pgpainless.key.info.KeyRingInfo;
 import org.pgpainless.key.protection.SecretKeyRingProtector;
 import org.pgpainless.key.util.KeyRingUtils;
 import org.pgpainless.util.Passphrase;
-import org.pgpainless.util.selection.key.impl.SignatureKeySelectionStrategy;
 
 public class SigningTest {
 
@@ -61,9 +61,8 @@ public class SigningTest {
         PGPPublicKeyRing romeoKeys = TestKeys.getRomeoPublicKeyRing();
 
         PGPSecretKeyRing cryptieKeys = TestKeys.getCryptieSecretKeyRing();
-        PGPSecretKey cryptieSigningKey = new SignatureKeySelectionStrategy()
-                .selectKeysFromKeyRing(cryptieKeys)
-                .iterator().next();
+        KeyRingInfo cryptieInfo = new KeyRingInfo(cryptieKeys);
+        PGPSecretKey cryptieSigningKey = cryptieKeys.getSecretKey(cryptieInfo.getSigningSubkeys().get(0).getKeyID());
 
         PGPPublicKeyRingCollection keys = new PGPPublicKeyRingCollection(Arrays.asList(julietKeys, romeoKeys));
 
diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/BCUtilTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/BCUtilTest.java
index 69887b8d..857e6574 100644
--- a/pgpainless-core/src/test/java/org/pgpainless/util/BCUtilTest.java
+++ b/pgpainless-core/src/test/java/org/pgpainless/util/BCUtilTest.java
@@ -16,8 +16,6 @@
 package org.pgpainless.util;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
@@ -30,7 +28,6 @@ import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPPublicKeyRing;
 import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
-import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.PGPSecretKeyRing;
 import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
 import org.junit.jupiter.api.Test;
@@ -105,32 +102,4 @@ public class BCUtilTest {
 
         LOGGER.log(Level.FINER, "PubCol: " + pubColSize);
     }
-
-    @Test
-    public void removeUnsignedKeysTest()
-            throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
-        @SuppressWarnings("deprecation")
-        PGPSecretKeyRing alice = PGPainless.generateKeyRing().simpleRsaKeyRing("alice@wonderland.lit", RsaLength._1024);
-        PGPSecretKeyRing mallory = PGPainless.generateKeyRing().simpleEcKeyRing("mallory@mall.ory");
-
-        PGPSecretKey subKey = null;
-        Iterator<PGPSecretKey> sit = mallory.getSecretKeys();
-        while (sit.hasNext()) {
-            PGPSecretKey s = sit.next();
-            if (!s.isMasterKey()) {
-                subKey = s;
-                break;
-            }
-        }
-
-        assertNotNull(subKey);
-
-        PGPSecretKeyRing alice_mallory = PGPSecretKeyRing.insertSecretKey(alice, subKey);
-
-        // Check, if alice_mallory contains mallory's key
-        assertNotNull(alice_mallory.getSecretKey(subKey.getKeyID()));
-
-        PGPSecretKeyRing cleaned = KeyRingUtils.removeUnassociatedKeysFromKeyRing(alice_mallory, alice.getPublicKey());
-        assertNull(cleaned.getSecretKey(subKey.getKeyID()));
-    }
 }
diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/AndOrSelectionStrategyTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/AndOrSelectionStrategyTest.java
deleted file mode 100644
index 32e85006..00000000
--- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/AndOrSelectionStrategyTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2021 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.IOException;
-import java.util.Iterator;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.junit.jupiter.api.Test;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.key.TestKeys;
-import org.pgpainless.util.selection.key.impl.EncryptionKeySelectionStrategy;
-import org.pgpainless.util.selection.key.impl.HasAnyKeyFlagSelectionStrategy;
-import org.pgpainless.util.selection.key.impl.Or;
-
-public class AndOrSelectionStrategyTest {
-
-    @Test
-    public void testOr() throws IOException, PGPException {
-        PGPSecretKeyRing ring = TestKeys.getEmilSecretKeyRing();
-        Iterator<PGPSecretKey> secretKeys = ring.getSecretKeys();
-        Or.SecKeySelectionStrategy secStrategy = new Or.SecKeySelectionStrategy(
-                new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_COMMS),
-                new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_STORAGE)
-        );
-        PGPSecretKey certSecKey = secretKeys.next();
-        PGPSecretKey cryptSecKey = secretKeys.next();
-
-        assertFalse(secStrategy.accept(certSecKey));
-        assertTrue(secStrategy.accept(cryptSecKey));
-
-        Iterator<PGPPublicKey> publicKeys = ring.getPublicKeys();
-        Or.PubKeySelectionStrategy pubStrategy = new Or.PubKeySelectionStrategy(
-                new EncryptionKeySelectionStrategy(KeyFlag.ENCRYPT_COMMS),
-                new EncryptionKeySelectionStrategy(KeyFlag.ENCRYPT_STORAGE)
-        );
-        PGPPublicKey certPubKey = publicKeys.next();
-        PGPPublicKey cryptPubKey = publicKeys.next();
-
-        assertFalse(pubStrategy.accept(certPubKey));
-        assertTrue(pubStrategy.accept(cryptPubKey));
-    }
-}
diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyBelongsToKeyRingTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyBelongsToKeyRingTest.java
deleted file mode 100644
index 1fd17535..00000000
--- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyBelongsToKeyRingTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2021 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.IOException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
-import java.util.Iterator;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.junit.jupiter.api.Test;
-import org.pgpainless.PGPainless;
-import org.pgpainless.key.TestKeys;
-import org.pgpainless.util.selection.key.impl.KeyBelongsToKeyRing;
-
-public class KeyBelongsToKeyRingTest {
-
-    @Test
-    public void testStrategyOnlyAcceptsKeysThatBelongToKeyRing() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
-        PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing().simpleEcKeyRing("test@test.test");
-        Iterator<PGPPublicKey> iterator = secretKeys.getPublicKeys();
-        PGPPublicKey primaryKey = iterator.next();
-        PGPPublicKey subKey = iterator.next();
-
-        KeyBelongsToKeyRing.PubkeySelectionStrategy strategy = new KeyBelongsToKeyRing.PubkeySelectionStrategy(primaryKey);
-        assertTrue(strategy.accept(primaryKey));
-        assertTrue(strategy.accept(subKey));
-
-        PGPSecretKeyRing unrelatedKeys = TestKeys.getEmilSecretKeyRing();
-        Iterator<PGPPublicKey> unrelated = unrelatedKeys.getPublicKeys();
-        while (unrelated.hasNext()) {
-            PGPPublicKey unrelatedKey = unrelated.next();
-            assertFalse(strategy.accept(unrelatedKey));
-        }
-    }
-}
diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyFlagBasedSelectionStrategyTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyFlagBasedSelectionStrategyTest.java
deleted file mode 100644
index 27422354..00000000
--- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/key/KeyFlagBasedSelectionStrategyTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2021 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.junit.jupiter.api.Test;
-import org.pgpainless.PGPainless;
-import org.pgpainless.algorithm.KeyFlag;
-import org.pgpainless.key.generation.KeySpec;
-import org.pgpainless.key.generation.type.KeyType;
-import org.pgpainless.key.generation.type.ecc.EllipticCurve;
-import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
-import org.pgpainless.key.generation.type.xdh.XDHSpec;
-import org.pgpainless.util.selection.key.impl.HasAllKeyFlagSelectionStrategy;
-import org.pgpainless.util.selection.key.impl.HasAnyKeyFlagSelectionStrategy;
-import org.pgpainless.key.util.KeyRingUtils;
-
-public class KeyFlagBasedSelectionStrategyTest {
-
-    @Test
-    public void testKeyFlagSelectors() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
-        PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
-                .withSubKey(KeySpec.getBuilder(KeyType.ECDSA(EllipticCurve._P256))
-                        .withKeyFlags(KeyFlag.SIGN_DATA)
-                        .withDefaultAlgorithms())
-                .withSubKey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
-                        .withKeyFlags(KeyFlag.ENCRYPT_COMMS)
-                        .withDefaultAlgorithms())
-                .withPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
-                        .withKeyFlags(KeyFlag.CERTIFY_OTHER, KeyFlag.AUTHENTICATION)
-                        .withDefaultAlgorithms())
-                .withPrimaryUserId("test@test.test")
-                .withoutPassphrase().build();
-
-        Iterator<PGPSecretKey> iterator = secretKeys.iterator();
-        // CERTIFY_OTHER and AUTHENTICATION
-        PGPSecretKey s_primaryKey = iterator.next();
-        // SIGN_DATA
-        PGPSecretKey s_signingKey = iterator.next();
-        // ENCRYPT_COMMS
-        PGPSecretKey s_encryptionKey = iterator.next();
-
-        HasAllKeyFlagSelectionStrategy.SecretKey s_certifyOther =
-                new HasAllKeyFlagSelectionStrategy.SecretKey(KeyFlag.CERTIFY_OTHER);
-        HasAllKeyFlagSelectionStrategy.SecretKey s_encryptComms =
-                new HasAllKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_COMMS);
-        HasAllKeyFlagSelectionStrategy.SecretKey s_encryptCommsEncryptStorage =
-                new HasAllKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE);
-        HasAnyKeyFlagSelectionStrategy.SecretKey s_anyEncryptCommsEncryptStorage =
-                new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE);
-
-        assertTrue(s_certifyOther.accept(s_primaryKey));
-        assertFalse(s_certifyOther.accept(s_encryptionKey));
-        assertFalse(s_certifyOther.accept(s_signingKey));
-
-        assertTrue(s_encryptComms.accept(s_encryptionKey));
-        assertFalse(s_encryptComms.accept(s_primaryKey));
-        assertFalse(s_encryptComms.accept(s_signingKey));
-
-        assertFalse(s_encryptCommsEncryptStorage.accept(s_encryptionKey),
-                "Must not accept the key, as it only carries ENCRYPT_COMMS, but not ENCRYPT_STORAGE");
-        assertFalse(s_encryptCommsEncryptStorage.accept(s_primaryKey));
-        assertFalse(s_encryptCommsEncryptStorage.accept(s_signingKey));
-
-        assertTrue(s_anyEncryptCommsEncryptStorage.accept(s_encryptionKey));
-        assertFalse(s_anyEncryptCommsEncryptStorage.accept(s_primaryKey));
-        assertFalse(s_anyEncryptCommsEncryptStorage.accept(s_signingKey));
-
-        PGPPublicKey p_primaryKey = s_primaryKey.getPublicKey();
-        PGPPublicKey p_encryptionKey = s_encryptionKey.getPublicKey();
-        PGPPublicKey p_signingKey = s_signingKey.getPublicKey();
-
-        HasAllKeyFlagSelectionStrategy.PublicKey p_certifyOther =
-                new HasAllKeyFlagSelectionStrategy.PublicKey(KeyFlag.CERTIFY_OTHER);
-        HasAllKeyFlagSelectionStrategy.PublicKey p_encryptComms =
-                new HasAllKeyFlagSelectionStrategy.PublicKey(KeyFlag.ENCRYPT_COMMS);
-        HasAllKeyFlagSelectionStrategy.PublicKey p_encryptCommsEncryptStorage =
-                new HasAllKeyFlagSelectionStrategy.PublicKey(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE);
-        HasAnyKeyFlagSelectionStrategy.PublicKey p_anyEncryptCommsEncryptStorage =
-                new HasAnyKeyFlagSelectionStrategy.PublicKey(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE);
-
-        assertTrue(p_certifyOther.accept(p_primaryKey));
-        assertFalse(p_certifyOther.accept(p_encryptionKey));
-        assertFalse(p_certifyOther.accept(p_signingKey));
-
-        assertTrue(p_encryptComms.accept(p_encryptionKey));
-        assertFalse(p_encryptComms.accept(p_primaryKey));
-        assertFalse(p_encryptComms.accept(p_signingKey));
-
-        assertFalse(p_encryptCommsEncryptStorage.accept(p_encryptionKey),
-                "Must not accept the key, as it only carries ENCRYPT_COMMS, but not ENCRYPT_STORAGE");
-        assertFalse(p_encryptCommsEncryptStorage.accept(p_primaryKey));
-        assertFalse(p_encryptCommsEncryptStorage.accept(p_signingKey));
-
-        assertTrue(p_anyEncryptCommsEncryptStorage.accept(p_encryptionKey));
-        assertFalse(p_anyEncryptCommsEncryptStorage.accept(p_primaryKey));
-        assertFalse(p_anyEncryptCommsEncryptStorage.accept(p_signingKey));
-    }
-
-    @Test
-    public void testSelectKeysFromKeyRing() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
-        PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
-                .withSubKey(KeySpec.getBuilder(KeyType.ECDSA(EllipticCurve._P256))
-                        .withKeyFlags(KeyFlag.SIGN_DATA)
-                        .withDefaultAlgorithms())
-                .withSubKey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
-                        .withKeyFlags(KeyFlag.ENCRYPT_COMMS)
-                        .withDefaultAlgorithms())
-                .withPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
-                        .withKeyFlags(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.AUTHENTICATION)
-                        .withDefaultAlgorithms())
-                .withPrimaryUserId("test@test.test")
-                .withoutPassphrase().build();
-
-        HasAnyKeyFlagSelectionStrategy.SecretKey secSelection =
-                new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.SIGN_DATA);
-
-        Set<PGPSecretKey> secSigningKeys = secSelection.selectKeysFromKeyRing(secretKeys);
-        assertEquals(2, secSigningKeys.size());
-
-        PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
-
-        HasAnyKeyFlagSelectionStrategy.PublicKey pubSelection =
-                new HasAnyKeyFlagSelectionStrategy.PublicKey(KeyFlag.SIGN_DATA);
-
-        Set<PGPPublicKey> pubSigningKeys = pubSelection.selectKeysFromKeyRing(publicKeys);
-        assertEquals(2, pubSigningKeys.size());
-
-        List<Long> ids = new ArrayList<>();
-        for (PGPSecretKey secretKey : secSigningKeys) {
-            ids.add(secretKey.getKeyID());
-        }
-
-        for (PGPPublicKey publicKey : pubSigningKeys) {
-            assertTrue(ids.contains(publicKey.getKeyID()));
-        }
-
-        secSelection = new HasAnyKeyFlagSelectionStrategy.SecretKey(KeyFlag.ENCRYPT_STORAGE);
-        assertTrue(secSelection.selectKeysFromKeyRing(secretKeys).isEmpty());
-    }
-}

From 259f629b3cf4b2656e06dcbe937efd33e984dd0e Mon Sep 17 00:00:00 2001
From: Paul Schaub <vanitasvitae@fsfe.org>
Date: Wed, 23 Jun 2021 19:39:10 +0200
Subject: [PATCH 8/8] Further deletion of unused selection classes

---
 .../selection/key/KeySelectionStrategy.java   | 32 -------
 .../key/PublicKeySelectionStrategy.java       | 41 ---------
 .../key/SecretKeySelectionStrategy.java       | 42 ---------
 .../util/selection/key/package-info.java      | 19 ----
 .../util/selection/keyring/impl/Email.java    | 32 -------
 .../selection/keyring/impl/PartialUserId.java | 67 --------------
 .../EmailKeyRingSelectionStrategyTest.java    | 91 -------------------
 7 files changed, 324 deletions(-)
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/KeySelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/PublicKeySelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SecretKeySelectionStrategy.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/key/package-info.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/Email.java
 delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/PartialUserId.java
 delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/EmailKeyRingSelectionStrategyTest.java

diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/KeySelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/KeySelectionStrategy.java
deleted file mode 100644
index 68884e92..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/KeySelectionStrategy.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-
-/**
- * Interface that describes a selection strategy for OpenPGP keys.
- * @param <K> Type of the Key
- * @param <R> Type of the PGPKeyRing
- */
-public interface KeySelectionStrategy<K, R> {
-
-    boolean accept(K key);
-
-    Set<K> selectKeysFromKeyRing(@Nonnull R ring);
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/PublicKeySelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/PublicKeySelectionStrategy.java
deleted file mode 100644
index 5e0f22bb..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/PublicKeySelectionStrategy.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPPublicKeyRing;
-
-/**
- * Key Selection Strategy which accepts {@link PGPPublicKey}s that are accepted by the abstract method
- * {@link #accept(Object)}.
- */
-public abstract class PublicKeySelectionStrategy implements KeySelectionStrategy<PGPPublicKey, PGPPublicKeyRing> {
-
-    @Override
-    public Set<PGPPublicKey> selectKeysFromKeyRing(@Nonnull PGPPublicKeyRing ring) {
-        Set<PGPPublicKey> keys = new HashSet<>();
-        for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) {
-            PGPPublicKey key = i.next();
-            if (accept(key)) keys.add(key);
-        }
-        return keys;
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SecretKeySelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SecretKeySelectionStrategy.java
deleted file mode 100644
index 49e40a0d..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/SecretKeySelectionStrategy.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.key;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.bouncycastle.openpgp.PGPSecretKeyRing;
-
-/**
- * Key Selection Strategy which accepts {@link PGPSecretKey}s that are accepted by the abstract method
- * {@link #accept(Object)}.
- *
- */
-public abstract class SecretKeySelectionStrategy implements KeySelectionStrategy<PGPSecretKey, PGPSecretKeyRing> {
-
-    @Override
-    public Set<PGPSecretKey> selectKeysFromKeyRing(@Nonnull PGPSecretKeyRing ring) {
-        Set<PGPSecretKey> keys = new HashSet<>();
-        for (Iterator<PGPSecretKey> i = ring.getSecretKeys(); i.hasNext(); ) {
-            PGPSecretKey key = i.next();
-            if (accept(key)) keys.add(key);
-        }
-        return keys;
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/package-info.java
deleted file mode 100644
index 971fa41c..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/key/package-info.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-/**
- * Different Key Selection Strategies.
- */
-package org.pgpainless.util.selection.key;
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/Email.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/Email.java
deleted file mode 100644
index 059907df..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/Email.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.keyring.impl;
-
-public class Email {
-
-    public static class PubRingSelectionStrategy extends PartialUserId.PubRingSelectionStrategy {
-
-        public PubRingSelectionStrategy(String email) {
-            super(email.matches("^<.+>$") ? email : '<' + email + '>');
-        }
-    }
-
-    public static class SecRingSelectionStrategy extends PartialUserId.SecRingSelectionStrategy {
-        public SecRingSelectionStrategy(String email) {
-            super(email.matches("^<.+>$") ? email : '<' + email + '>');
-        }
-    }
-}
diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/PartialUserId.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/PartialUserId.java
deleted file mode 100644
index 816aca3a..00000000
--- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/impl/PartialUserId.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2018 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.keyring.impl;
-
-import javax.annotation.Nonnull;
-import java.util.Iterator;
-
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.pgpainless.util.selection.key.PublicKeySelectionStrategy;
-import org.pgpainless.util.selection.key.SecretKeySelectionStrategy;
-
-public class PartialUserId {
-
-    public static class PubRingSelectionStrategy extends PublicKeySelectionStrategy {
-
-        protected final String identifier;
-
-        public PubRingSelectionStrategy(String identifier) {
-            this.identifier = identifier;
-        }
-
-        @Override
-        public boolean accept(@Nonnull PGPPublicKey key) {
-            for (Iterator<String> userIds = key.getUserIDs(); userIds.hasNext(); ) {
-                String userId = userIds.next();
-                if (userId.contains(identifier)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    public static class SecRingSelectionStrategy extends SecretKeySelectionStrategy {
-
-        protected final String partialUserId;
-
-        public SecRingSelectionStrategy(String partialUserId) {
-            this.partialUserId = partialUserId;
-        }
-
-        @Override
-        public boolean accept(@Nonnull PGPSecretKey key) {
-            for (Iterator<String> userIds = key.getUserIDs(); userIds.hasNext(); ) {
-                String userId = userIds.next();
-                if (userId.contains(partialUserId)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-}
diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/EmailKeyRingSelectionStrategyTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/EmailKeyRingSelectionStrategyTest.java
deleted file mode 100644
index 655af7b8..00000000
--- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/EmailKeyRingSelectionStrategyTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2020 Paul Schaub.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.pgpainless.util.selection.keyring;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.IOException;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
-import org.junit.jupiter.api.Test;
-import org.pgpainless.key.TestKeys;
-import org.pgpainless.util.selection.keyring.impl.Email;
-
-public class EmailKeyRingSelectionStrategyTest {
-
-    @Test
-    public void testMatchingEmailUIDAcceptedOnPubKey() throws IOException {
-        String uid = "<emil@email.user>";
-        PGPPublicKey key = TestKeys.getEmilPublicKeyRing().getPublicKey();
-
-        Email.PubRingSelectionStrategy pubKeySelectionStrategy = new Email.PubRingSelectionStrategy(uid);
-
-        assertTrue(pubKeySelectionStrategy.accept(key));
-    }
-
-    @Test
-    public void testAddressIsFormattedToMatchOnPubKey() throws IOException {
-        String uid = "emil@email.user";
-        PGPPublicKey key = TestKeys.getEmilPublicKeyRing().getPublicKey();
-
-        Email.PubRingSelectionStrategy pubKeySelectionStrategy = new Email.PubRingSelectionStrategy(uid);
-
-        assertTrue(pubKeySelectionStrategy.accept(key));
-    }
-
-    @Test
-    public void testPubKeyWithDifferentUIDIsRejected() throws IOException {
-        String wrongUid = "emilia@email.user";
-        PGPPublicKey key = TestKeys.getEmilPublicKeyRing().getPublicKey();
-
-        Email.PubRingSelectionStrategy pubKeySelectionStrategy = new Email.PubRingSelectionStrategy(wrongUid);
-
-        assertFalse(pubKeySelectionStrategy.accept(key));
-    }
-
-    @Test
-    public void testMatchingEmailUIDAcceptedOnSecKey() throws IOException, PGPException {
-        String uid = "<emil@email.user>";
-        PGPSecretKey key = TestKeys.getEmilSecretKeyRing().getSecretKey();
-
-        Email.SecRingSelectionStrategy secKeySelectionStrategy = new Email.SecRingSelectionStrategy(uid);
-
-        assertTrue(secKeySelectionStrategy.accept(key));
-    }
-
-    @Test
-    public void testAddressIsFormattedToMatchOnSecKey() throws IOException, PGPException {
-        String uid = "emil@email.user";
-        PGPSecretKey key = TestKeys.getEmilSecretKeyRing().getSecretKey();
-
-        Email.SecRingSelectionStrategy secKeySelectionStrategy = new Email.SecRingSelectionStrategy(uid);
-
-        assertTrue(secKeySelectionStrategy.accept(key));
-    }
-
-    @Test
-    public void testSecKeyWithDifferentUIDIsRejected() throws IOException, PGPException {
-        String wrongUid = "emilia@email.user";
-        PGPSecretKey key = TestKeys.getEmilSecretKeyRing().getSecretKey();
-
-        Email.SecRingSelectionStrategy secKeySelectionStrategy = new Email.SecRingSelectionStrategy(wrongUid);
-
-        assertFalse(secKeySelectionStrategy.accept(key));
-    }
-}