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}