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}