001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.encryption_signing;
006
007import java.util.Collections;
008import java.util.Date;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.LinkedHashSet;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015
016import javax.annotation.Nonnull;
017
018import org.bouncycastle.openpgp.PGPPublicKey;
019import org.bouncycastle.openpgp.PGPPublicKeyRing;
020import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
021import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
022import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator;
023import org.pgpainless.algorithm.EncryptionPurpose;
024import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
025import org.pgpainless.implementation.ImplementationFactory;
026import org.pgpainless.key.OpenPgpFingerprint;
027import org.pgpainless.key.SubkeyIdentifier;
028import org.pgpainless.key.info.KeyAccessor;
029import org.pgpainless.key.info.KeyRingInfo;
030import org.pgpainless.util.Passphrase;
031
032/**
033 * Options for the encryption process.
034 * This class can be used to set encryption parameters, like encryption keys and passphrases, algorithms etc.
035 *
036 * A typical use might look like follows:
037 * <pre>
038 * {@code
039 * EncryptionOptions opt = new EncryptionOptions();
040 * opt.addRecipient(aliceKey, "Alice <alice@wonderland.lit>");
041 * opt.addPassphrase(Passphrase.fromPassword("AdditionalDecryptionPassphrase123"));
042 * }
043 * </pre>
044 *
045 * To use a custom symmetric encryption algorithm, use {@link #overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}.
046 * This will cause PGPainless to use the provided algorithm for message encryption, instead of negotiating an algorithm
047 * by inspecting the provided recipient keys.
048 *
049 * By default, PGPainless will only encrypt to a single encryption capable subkey per recipient key.
050 * This behavior can be changed, e.g. by calling
051 * <pre>
052 * {@code
053 * opt.addRecipient(aliceKey, EncryptionOptions.encryptToAllCapableSubkeys());
054 * }
055 * </pre>
056 * when adding the recipient key.
057 */
058public class EncryptionOptions {
059
060    private final EncryptionPurpose purpose;
061    private final Set<PGPKeyEncryptionMethodGenerator> encryptionMethods = new LinkedHashSet<>();
062    private final Set<SubkeyIdentifier> encryptionKeys = new LinkedHashSet<>();
063    private final Map<SubkeyIdentifier, KeyRingInfo> keyRingInfo = new HashMap<>();
064    private final Map<SubkeyIdentifier, KeyAccessor> keyViews = new HashMap<>();
065    private final EncryptionKeySelector encryptionKeySelector = encryptToAllCapableSubkeys();
066
067    private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null;
068
069    /**
070     * Encrypt to keys both carrying the key flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}
071     * or {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
072     */
073    public EncryptionOptions() {
074        this(EncryptionPurpose.ANY);
075    }
076
077    public EncryptionOptions(EncryptionPurpose purpose) {
078        this.purpose = purpose;
079    }
080
081    /**
082     * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
083     * which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
084     *
085     * @return encryption options
086     */
087    public static EncryptionOptions encryptCommunications() {
088        return new EncryptionOptions(EncryptionPurpose.COMMUNICATIONS);
089    }
090
091    /**
092     * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
093     * which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
094     *
095     * @return encryption options
096     */
097    public static EncryptionOptions encryptDataAtRest() {
098        return new EncryptionOptions(EncryptionPurpose.STORAGE);
099    }
100
101    /**
102     * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
103     *
104     * @param keys keys
105     * @return this
106     */
107    public EncryptionOptions addRecipients(Iterable<PGPPublicKeyRing> keys) {
108        for (PGPPublicKeyRing key : keys) {
109            addRecipient(key);
110        }
111        return this;
112    }
113
114    /**
115     * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
116     * Per key ring, the selector is applied to select one or more encryption subkeys.
117     *
118     * @param keys keys
119     * @param selector encryption key selector
120     * @return this
121     */
122    public EncryptionOptions addRecipients(@Nonnull Iterable<PGPPublicKeyRing> keys, @Nonnull EncryptionKeySelector selector) {
123        for (PGPPublicKeyRing key : keys) {
124            addRecipient(key, selector);
125        }
126        return this;
127    }
128
129    /**
130     * Add a recipient by providing a key and recipient user-id.
131     * The user-id is used to determine the recipients preferences (algorithms etc.).
132     *
133     * @param key key ring
134     * @param userId user id
135     * @return this
136     */
137    public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId) {
138        return addRecipient(key, userId, encryptionKeySelector);
139    }
140
141    /**
142     * Add a recipient by providing a key and recipient user-id, as well as a strategy for selecting one or multiple
143     * encryption capable subkeys from the key.
144     *
145     * @param key key
146     * @param userId user-id
147     * @param encryptionKeySelectionStrategy strategy to select one or more encryption subkeys to encrypt to
148     * @return this
149     */
150    public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId, EncryptionKeySelector encryptionKeySelectionStrategy) {
151        KeyRingInfo info = new KeyRingInfo(key, new Date());
152
153        List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
154                .selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose));
155        if (encryptionSubkeys.isEmpty()) {
156            throw new IllegalArgumentException("Key has no suitable encryption subkeys.");
157        }
158
159        for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
160            SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
161            keyRingInfo.put(keyId, info);
162            keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId));
163            addRecipientKey(key, encryptionSubkey);
164        }
165
166        return this;
167    }
168
169    /**
170     * Add a recipient by providing a key.
171     *
172     * @param key key ring
173     * @return this
174     */
175    public EncryptionOptions addRecipient(PGPPublicKeyRing key) {
176        return addRecipient(key, encryptionKeySelector);
177    }
178
179    /**
180     * Add a recipient by providing a key and an encryption key selection strategy.
181     *
182     * @param key key ring
183     * @param encryptionKeySelectionStrategy strategy used to select one or multiple encryption subkeys.
184     * @return this
185     */
186    public EncryptionOptions addRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy) {
187        KeyRingInfo info = new KeyRingInfo(key, new Date());
188        Date primaryKeyExpiration = info.getPrimaryKeyExpirationDate();
189        if (primaryKeyExpiration != null && primaryKeyExpiration.before(new Date())) {
190            throw new IllegalArgumentException("Provided key " + OpenPgpFingerprint.of(key) + " is expired: " + primaryKeyExpiration);
191        }
192        List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
193                .selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose));
194        if (encryptionSubkeys.isEmpty()) {
195            throw new IllegalArgumentException("Key has no suitable encryption subkeys.");
196        }
197
198        for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
199            SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
200            keyRingInfo.put(keyId, info);
201            keyViews.put(keyId, new KeyAccessor.ViaKeyId(info, keyId));
202            addRecipientKey(key, encryptionSubkey);
203        }
204
205        return this;
206    }
207
208    private void addRecipientKey(PGPPublicKeyRing keyRing, PGPPublicKey key) {
209        encryptionKeys.add(new SubkeyIdentifier(keyRing, key.getKeyID()));
210        PGPKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
211                .getInstance().getPublicKeyKeyEncryptionMethodGenerator(key);
212        addEncryptionMethod(encryptionMethod);
213    }
214
215    /**
216     * Add a symmetric passphrase which the message will be encrypted to.
217     *
218     * @param passphrase passphrase
219     * @return this
220     */
221    public EncryptionOptions addPassphrase(Passphrase passphrase) {
222        if (passphrase.isEmpty()) {
223            throw new IllegalArgumentException("Passphrase must not be empty.");
224        }
225        PBEKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
226                .getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
227        return addEncryptionMethod(encryptionMethod);
228    }
229
230    /**
231     * Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
232     * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase)
233     * or {@link PGPKeyEncryptionMethodGenerator} (public key).
234     *
235     * This method is intended for advanced users to allow encryption for specific subkeys.
236     * This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless.
237     *
238     * @param encryptionMethod encryption method
239     * @return this
240     */
241    public EncryptionOptions addEncryptionMethod(PGPKeyEncryptionMethodGenerator encryptionMethod) {
242        encryptionMethods.add(encryptionMethod);
243        return this;
244    }
245
246    Set<PGPKeyEncryptionMethodGenerator> getEncryptionMethods() {
247        return new HashSet<>(encryptionMethods);
248    }
249
250    Map<SubkeyIdentifier, KeyRingInfo> getKeyRingInfo() {
251        return new HashMap<>(keyRingInfo);
252    }
253
254    Set<SubkeyIdentifier> getEncryptionKeyIdentifiers() {
255        return new HashSet<>(encryptionKeys);
256    }
257
258    Map<SubkeyIdentifier, KeyAccessor> getKeyViews() {
259        return new HashMap<>(keyViews);
260    }
261
262    SymmetricKeyAlgorithm getEncryptionAlgorithmOverride() {
263        return encryptionAlgorithmOverride;
264    }
265
266    /**
267     * Override the used symmetric encryption algorithm.
268     * The symmetric encryption algorithm is used to encrypt the message itself,
269     * while the used symmetric key will be encrypted to all recipients using public key
270     * cryptography.
271     *
272     * If the algorithm is not overridden, a suitable algorithm will be negotiated.
273     *
274     * @param encryptionAlgorithm encryption algorithm override
275     */
276    public EncryptionOptions overrideEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
277        if (encryptionAlgorithm == SymmetricKeyAlgorithm.NULL) {
278            throw new IllegalArgumentException("Plaintext encryption can only be used to denote unencrypted secret keys.");
279        }
280        this.encryptionAlgorithmOverride = encryptionAlgorithm;
281        return this;
282    }
283
284    public interface EncryptionKeySelector {
285        List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys);
286    }
287
288    /**
289     * Only encrypt to the first valid encryption capable subkey we stumble upon.
290     *
291     * @return encryption key selector
292     */
293    public static EncryptionKeySelector encryptToFirstSubkey() {
294        return new EncryptionKeySelector() {
295            @Override
296            public List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys) {
297                return encryptionCapableKeys.isEmpty() ? Collections.emptyList() : Collections.singletonList(encryptionCapableKeys.get(0));
298            }
299        };
300    }
301
302    /**
303     * Encrypt to any valid, encryption capable subkey on the key ring.
304     *
305     * @return encryption key selector
306     */
307    public static EncryptionKeySelector encryptToAllCapableSubkeys() {
308        return new EncryptionKeySelector() {
309            @Override
310            public List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys) {
311                return encryptionCapableKeys;
312            }
313        };
314    }
315
316    // TODO: Create encryptToBestSubkey() method
317}