001/* 002 * Copyright 2018 Paul Schaub. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.pgpainless.encryption_signing; 017 018import javax.annotation.Nonnull; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.util.HashSet; 022import java.util.Iterator; 023import java.util.Set; 024 025import org.bouncycastle.openpgp.PGPException; 026import org.bouncycastle.openpgp.PGPPrivateKey; 027import org.bouncycastle.openpgp.PGPPublicKey; 028import org.bouncycastle.openpgp.PGPPublicKeyRing; 029import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 030import org.bouncycastle.openpgp.PGPSecretKey; 031import org.bouncycastle.openpgp.PGPSecretKeyRing; 032import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 033import org.pgpainless.algorithm.CompressionAlgorithm; 034import org.pgpainless.algorithm.HashAlgorithm; 035import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 036import org.pgpainless.key.protection.SecretKeyRingProtector; 037import org.pgpainless.key.selection.key.PublicKeySelectionStrategy; 038import org.pgpainless.key.selection.key.SecretKeySelectionStrategy; 039import org.pgpainless.key.selection.key.impl.And; 040import org.pgpainless.key.selection.key.impl.EncryptionKeySelectionStrategy; 041import org.pgpainless.key.selection.key.impl.NoRevocation; 042import org.pgpainless.key.selection.key.impl.SignatureKeySelectionStrategy; 043import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy; 044import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy; 045import org.pgpainless.util.MultiMap; 046 047public class EncryptionBuilder implements EncryptionBuilderInterface { 048 049 private OutputStream outputStream; 050 private final Set<PGPPublicKey> encryptionKeys = new HashSet<>(); 051 private final Set<PGPSecretKey> signingKeys = new HashSet<>(); 052 private SecretKeyRingProtector signingKeysDecryptor; 053 private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128; 054 private HashAlgorithm hashAlgorithm = HashAlgorithm.SHA256; 055 private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; 056 private boolean asciiArmor = false; 057 058 @Override 059 public ToRecipients onOutputStream(@Nonnull OutputStream outputStream) { 060 this.outputStream = outputStream; 061 return new ToRecipientsImpl(); 062 } 063 064 class ToRecipientsImpl implements ToRecipients { 065 066 @Override 067 public WithAlgorithms toRecipients(@Nonnull PGPPublicKey... keys) { 068 for (PGPPublicKey k : keys) { 069 if (encryptionKeySelector().accept(null, k)) { 070 EncryptionBuilder.this.encryptionKeys.add(k); 071 } else { 072 throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); 073 } 074 } 075 076 if (EncryptionBuilder.this.encryptionKeys.isEmpty()) { 077 throw new IllegalStateException("No valid encryption keys found!"); 078 } 079 080 return new WithAlgorithmsImpl(); 081 } 082 083 @Override 084 public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRing... keys) { 085 for (PGPPublicKeyRing ring : keys) { 086 for (PGPPublicKey k : ring) { 087 if (encryptionKeySelector().accept(null, k)) { 088 EncryptionBuilder.this.encryptionKeys.add(k); 089 } 090 } 091 } 092 093 if (EncryptionBuilder.this.encryptionKeys.isEmpty()) { 094 throw new IllegalStateException("No valid encryption keys found!"); 095 } 096 097 return new WithAlgorithmsImpl(); 098 } 099 100 @Override 101 public WithAlgorithms toRecipients(@Nonnull PGPPublicKeyRingCollection... keys) { 102 for (PGPPublicKeyRingCollection collection : keys) { 103 for (PGPPublicKeyRing ring : collection) { 104 for (PGPPublicKey k : ring) { 105 if (encryptionKeySelector().accept(null, k)) { 106 EncryptionBuilder.this.encryptionKeys.add(k); 107 } 108 } 109 } 110 } 111 112 if (EncryptionBuilder.this.encryptionKeys.isEmpty()) { 113 throw new IllegalStateException("No valid encryption keys found!"); 114 } 115 116 return new WithAlgorithmsImpl(); 117 } 118 119 @Override 120 public <O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy, 121 @Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) { 122 if (keys.isEmpty()) { 123 throw new IllegalArgumentException("Recipient map MUST NOT be empty."); 124 } 125 MultiMap<O, PGPPublicKeyRing> acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys); 126 for (O identifier : acceptedKeyRings.keySet()) { 127 Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier); 128 for (PGPPublicKeyRing ring : acceptedSet) { 129 for (PGPPublicKey k : ring) { 130 if (encryptionKeySelector().accept(null, k)) { 131 EncryptionBuilder.this.encryptionKeys.add(k); 132 } 133 } 134 } 135 } 136 137 if (EncryptionBuilder.this.encryptionKeys.isEmpty()) { 138 throw new IllegalStateException("No valid encryption keys found!"); 139 } 140 141 return new WithAlgorithmsImpl(); 142 } 143 144 @Override 145 public SignWith doNotEncrypt() { 146 return new SignWithImpl(); 147 } 148 } 149 150 class WithAlgorithmsImpl implements WithAlgorithms { 151 152 @Override 153 public WithAlgorithms andToSelf(@Nonnull PGPPublicKey... keys) { 154 if (keys.length == 0) { 155 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 156 } 157 for (PGPPublicKey k : keys) { 158 if (encryptionKeySelector().accept(null, k)) { 159 EncryptionBuilder.this.encryptionKeys.add(k); 160 } else { 161 throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); 162 } 163 } 164 return this; 165 } 166 167 @Override 168 public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRing... keys) { 169 if (keys.length == 0) { 170 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 171 } 172 for (PGPPublicKeyRing ring : keys) { 173 for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) { 174 PGPPublicKey key = i.next(); 175 if (encryptionKeySelector().accept(null, key)) { 176 EncryptionBuilder.this.encryptionKeys.add(key); 177 } 178 } 179 } 180 return this; 181 } 182 183 @Override 184 public WithAlgorithms andToSelf(@Nonnull PGPPublicKeyRingCollection keys) { 185 for (PGPPublicKeyRing ring : keys) { 186 for (Iterator<PGPPublicKey> i = ring.getPublicKeys(); i.hasNext(); ) { 187 PGPPublicKey key = i.next(); 188 if (encryptionKeySelector().accept(null, key)) { 189 EncryptionBuilder.this.encryptionKeys.add(key); 190 } 191 } 192 } 193 return this; 194 } 195 196 public <O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> ringSelectionStrategy, 197 @Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys) { 198 if (keys.isEmpty()) { 199 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 200 } 201 MultiMap<O, PGPPublicKeyRing> acceptedKeyRings = 202 ringSelectionStrategy.selectKeyRingsFromCollections(keys); 203 for (O identifier : acceptedKeyRings.keySet()) { 204 Set<PGPPublicKeyRing> acceptedSet = acceptedKeyRings.get(identifier); 205 for (PGPPublicKeyRing k : acceptedSet) { 206 for (Iterator<PGPPublicKey> i = k.getPublicKeys(); i.hasNext(); ) { 207 PGPPublicKey key = i.next(); 208 if (encryptionKeySelector().accept(null, key)) { 209 EncryptionBuilder.this.encryptionKeys.add(key); 210 } 211 } 212 } 213 } 214 return this; 215 } 216 217 @Override 218 public SignWith usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm, 219 @Nonnull HashAlgorithm hashAlgorithm, 220 @Nonnull CompressionAlgorithm compressionAlgorithm) { 221 222 EncryptionBuilder.this.symmetricKeyAlgorithm = symmetricKeyAlgorithm; 223 EncryptionBuilder.this.hashAlgorithm = hashAlgorithm; 224 EncryptionBuilder.this.compressionAlgorithm = compressionAlgorithm; 225 226 return new SignWithImpl(); 227 } 228 229 @Override 230 public SignWith usingSecureAlgorithms() { 231 EncryptionBuilder.this.symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_256; 232 EncryptionBuilder.this.hashAlgorithm = HashAlgorithm.SHA512; 233 EncryptionBuilder.this.compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED; 234 235 return new SignWithImpl(); 236 } 237 } 238 239 class SignWithImpl implements SignWith { 240 241 @Override 242 public <O> Armor signWith(@Nonnull SecretKeyRingProtector decryptor, 243 @Nonnull PGPSecretKey... keys) { 244 if (keys.length == 0) { 245 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 246 } 247 for (PGPSecretKey s : keys) { 248 if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) { 249 signingKeys.add(s); 250 } else { 251 throw new IllegalArgumentException("Key " + s.getKeyID() + " is not a valid signing key."); 252 } 253 } 254 EncryptionBuilder.this.signingKeysDecryptor = decryptor; 255 return new ArmorImpl(); 256 } 257 258 @Override 259 public <O> Armor signWith(@Nonnull SecretKeyRingProtector decryptor, 260 @Nonnull PGPSecretKeyRing... keys) { 261 if (keys.length == 0) { 262 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 263 } 264 for (PGPSecretKeyRing key : keys) { 265 for (Iterator<PGPSecretKey> i = key.getSecretKeys(); i.hasNext(); ) { 266 PGPSecretKey s = i.next(); 267 if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) { 268 EncryptionBuilder.this.signingKeys.add(s); 269 } 270 } 271 } 272 EncryptionBuilder.this.signingKeysDecryptor = decryptor; 273 return new ArmorImpl(); 274 } 275 276 @Override 277 public <O> Armor signWith(@Nonnull SecretKeyRingSelectionStrategy<O> ringSelectionStrategy, 278 @Nonnull SecretKeyRingProtector decryptor, 279 @Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys) { 280 if (keys.isEmpty()) { 281 throw new IllegalArgumentException("Recipient list MUST NOT be empty."); 282 } 283 MultiMap<O, PGPSecretKeyRing> acceptedKeyRings = 284 ringSelectionStrategy.selectKeyRingsFromCollections(keys); 285 for (O identifier : acceptedKeyRings.keySet()) { 286 Set<PGPSecretKeyRing> acceptedSet = acceptedKeyRings.get(identifier); 287 for (PGPSecretKeyRing k : acceptedSet) { 288 for (Iterator<PGPSecretKey> i = k.getSecretKeys(); i.hasNext(); ) { 289 PGPSecretKey s = i.next(); 290 if (EncryptionBuilder.this.<O>signingKeySelector().accept(null, s)) { 291 EncryptionBuilder.this.signingKeys.add(s); 292 } 293 } 294 } 295 } 296 return new ArmorImpl(); 297 } 298 299 @Override 300 public Armor doNotSign() { 301 return new ArmorImpl(); 302 } 303 } 304 305 class ArmorImpl implements Armor { 306 307 @Override 308 public EncryptionStream asciiArmor() throws IOException, PGPException { 309 EncryptionBuilder.this.asciiArmor = true; 310 return build(); 311 } 312 313 @Override 314 public EncryptionStream noArmor() throws IOException, PGPException { 315 EncryptionBuilder.this.asciiArmor = false; 316 return build(); 317 } 318 319 private EncryptionStream build() throws IOException, PGPException { 320 321 Set<PGPPrivateKey> privateKeys = new HashSet<>(); 322 for (PGPSecretKey secretKey : signingKeys) { 323 privateKeys.add(secretKey.extractPrivateKey(signingKeysDecryptor.getDecryptor(secretKey.getKeyID()))); 324 } 325 326 return new EncryptionStream( 327 EncryptionBuilder.this.outputStream, 328 EncryptionBuilder.this.encryptionKeys, 329 privateKeys, 330 EncryptionBuilder.this.symmetricKeyAlgorithm, 331 EncryptionBuilder.this.hashAlgorithm, 332 EncryptionBuilder.this.compressionAlgorithm, 333 EncryptionBuilder.this.asciiArmor); 334 } 335 } 336 337 <O> PublicKeySelectionStrategy<O> encryptionKeySelector() { 338 return new And.PubKeySelectionStrategy<>( 339 new NoRevocation.PubKeySelectionStrategy<>(), 340 new EncryptionKeySelectionStrategy<>()); 341 } 342 343 <O> SecretKeySelectionStrategy<O> signingKeySelector() { 344 return new And.SecKeySelectionStrategy<>( 345 new NoRevocation.SecKeySelectionStrategy<>(), 346 new SignatureKeySelectionStrategy<>()); 347 } 348}