001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.policy;
006
007import java.util.Arrays;
008import java.util.Collections;
009import java.util.EnumMap;
010import java.util.List;
011import java.util.Map;
012
013import javax.annotation.Nonnull;
014
015import org.pgpainless.algorithm.AlgorithmSuite;
016import org.pgpainless.algorithm.CompressionAlgorithm;
017import org.pgpainless.algorithm.HashAlgorithm;
018import org.pgpainless.algorithm.PublicKeyAlgorithm;
019import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
020import org.pgpainless.util.NotationRegistry;
021
022/**
023 * Policy class used to configure acceptable algorithm suites etc.
024 */
025public final class Policy {
026
027    private static Policy INSTANCE;
028
029    private HashAlgorithmPolicy signatureHashAlgorithmPolicy =
030            HashAlgorithmPolicy.defaultSignatureAlgorithmPolicy();
031    private HashAlgorithmPolicy revocationSignatureHashAlgorithmPolicy =
032            HashAlgorithmPolicy.defaultRevocationSignatureHashAlgorithmPolicy();
033    private SymmetricKeyAlgorithmPolicy symmetricKeyEncryptionAlgorithmPolicy =
034            SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyEncryptionAlgorithmPolicy();
035    private SymmetricKeyAlgorithmPolicy symmetricKeyDecryptionAlgorithmPolicy =
036            SymmetricKeyAlgorithmPolicy.defaultSymmetricKeyDecryptionAlgorithmPolicy();
037    private CompressionAlgorithmPolicy compressionAlgorithmPolicy =
038            CompressionAlgorithmPolicy.defaultCompressionAlgorithmPolicy();
039    private PublicKeyAlgorithmPolicy publicKeyAlgorithmPolicy =
040            PublicKeyAlgorithmPolicy.defaultPublicKeyAlgorithmPolicy();
041    private final NotationRegistry notationRegistry = new NotationRegistry();
042
043    private AlgorithmSuite keyGenerationAlgorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite();
044
045    Policy() {
046    }
047
048    /**
049     * Return the singleton instance of PGPainless' policy.
050     *
051     * @return singleton instance
052     */
053    public static Policy getInstance() {
054        if (INSTANCE == null) {
055            INSTANCE = new Policy();
056        }
057        return INSTANCE;
058    }
059
060    /**
061     * Return the hash algorithm policy for signatures.
062     * @return hash algorithm policy
063     */
064    public HashAlgorithmPolicy getSignatureHashAlgorithmPolicy() {
065        return signatureHashAlgorithmPolicy;
066    }
067
068    /**
069     * Set a custom hash algorithm policy for signatures.
070     *
071     * @param policy custom policy
072     */
073    public void setSignatureHashAlgorithmPolicy(HashAlgorithmPolicy policy) {
074        if (policy == null) {
075            throw new NullPointerException("Policy cannot be null.");
076        }
077        this.signatureHashAlgorithmPolicy = policy;
078    }
079
080    /**
081     * Return the hash algorithm policy for revocations.
082     * This policy is separate from {@link #getSignatureHashAlgorithmPolicy()}, as PGPainless by default uses a
083     * less strict policy when it comes to acceptable algorithms.
084     *
085     * @return revocation signature hash algorithm policy
086     */
087    public HashAlgorithmPolicy getRevocationSignatureHashAlgorithmPolicy() {
088        return revocationSignatureHashAlgorithmPolicy;
089    }
090
091    /**
092     * Set a custom hash algorithm policy for revocations.
093     *
094     * @param policy custom policy
095     */
096    public void setRevocationSignatureHashAlgorithmPolicy(HashAlgorithmPolicy policy) {
097        if (policy == null) {
098            throw new NullPointerException("Policy cannot be null.");
099        }
100        this.revocationSignatureHashAlgorithmPolicy = policy;
101    }
102
103    /**
104     * Return the symmetric encryption algorithm policy for encryption.
105     * This policy defines which symmetric algorithms are acceptable when producing encrypted messages.
106     *
107     * @return symmetric algorithm policy for encryption
108     */
109    public SymmetricKeyAlgorithmPolicy getSymmetricKeyEncryptionAlgorithmPolicy() {
110        return symmetricKeyEncryptionAlgorithmPolicy;
111    }
112
113    /**
114     * Return the symmetric encryption algorithm policy for decryption.
115     * This policy defines which symmetric algorithms are acceptable when decrypting encrypted messages.
116     *
117     * @return symmetric algorithm policy for decryption
118     */
119    public SymmetricKeyAlgorithmPolicy getSymmetricKeyDecryptionAlgorithmPolicy() {
120        return symmetricKeyDecryptionAlgorithmPolicy;
121    }
122
123    /**
124     * Set a custom symmetric encryption algorithm policy for encrypting messages.
125     *
126     * @param policy custom policy
127     */
128    public void setSymmetricKeyEncryptionAlgorithmPolicy(SymmetricKeyAlgorithmPolicy policy) {
129        if (policy == null) {
130            throw new NullPointerException("Policy cannot be null.");
131        }
132        this.symmetricKeyEncryptionAlgorithmPolicy = policy;
133    }
134
135    /**
136     * Set a custom symmetric encryption algorithm policy for decrypting messages.
137     *
138     * @param policy custom policy
139     */
140    public void setSymmetricKeyDecryptionAlgorithmPolicy(SymmetricKeyAlgorithmPolicy policy) {
141        if (policy == null) {
142            throw new NullPointerException("Policy cannot be null.");
143        }
144        this.symmetricKeyDecryptionAlgorithmPolicy = policy;
145    }
146
147    public CompressionAlgorithmPolicy getCompressionAlgorithmPolicy() {
148        return compressionAlgorithmPolicy;
149    }
150
151    public void setCompressionAlgorithmPolicy(CompressionAlgorithmPolicy policy) {
152        if (policy == null) {
153            throw new NullPointerException("Compression policy cannot be null.");
154        }
155        this.compressionAlgorithmPolicy = policy;
156    }
157
158    /**
159     * Return the current public key algorithm policy.
160     *
161     * @return public key algorithm policy
162     */
163    public PublicKeyAlgorithmPolicy getPublicKeyAlgorithmPolicy() {
164        return publicKeyAlgorithmPolicy;
165    }
166
167    /**
168     * Set a custom public key algorithm policy.
169     *
170     * @param publicKeyAlgorithmPolicy custom policy
171     */
172    public void setPublicKeyAlgorithmPolicy(PublicKeyAlgorithmPolicy publicKeyAlgorithmPolicy) {
173        if (publicKeyAlgorithmPolicy == null) {
174            throw new NullPointerException("Public key algorithm policy cannot be null.");
175        }
176        this.publicKeyAlgorithmPolicy = publicKeyAlgorithmPolicy;
177    }
178
179    public static final class SymmetricKeyAlgorithmPolicy {
180
181        private final SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm;
182        private final List<SymmetricKeyAlgorithm> acceptableSymmetricKeyAlgorithms;
183
184        public SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm defaultSymmetricKeyAlgorithm, List<SymmetricKeyAlgorithm> acceptableSymmetricKeyAlgorithms) {
185            this.defaultSymmetricKeyAlgorithm = defaultSymmetricKeyAlgorithm;
186            this.acceptableSymmetricKeyAlgorithms = Collections.unmodifiableList(acceptableSymmetricKeyAlgorithms);
187        }
188
189        /**
190         * Return the default symmetric key algorithm.
191         * This algorithm is used as a fallback when no consensus about symmetric algorithms can be reached.
192         *
193         * @return default symmetric encryption algorithm
194         */
195        public SymmetricKeyAlgorithm getDefaultSymmetricKeyAlgorithm() {
196            return defaultSymmetricKeyAlgorithm;
197        }
198
199        /**
200         * Return true if the given symmetric encryption algorithm is acceptable by this policy.
201         *
202         * @param algorithm algorithm
203         * @return true if algorithm is acceptable, false otherwise
204         */
205        public boolean isAcceptable(SymmetricKeyAlgorithm algorithm) {
206            return acceptableSymmetricKeyAlgorithms.contains(algorithm);
207        }
208
209        /**
210         * Return true if the given symmetric encryption algorithm is acceptable by this policy.
211         *
212         * @param algorithmId algorithm
213         * @return true if algorithm is acceptable, false otherwise
214         */
215        public boolean isAcceptable(int algorithmId) {
216            SymmetricKeyAlgorithm algorithm = SymmetricKeyAlgorithm.fromId(algorithmId);
217            return isAcceptable(algorithm);
218        }
219
220        /**
221         * The default symmetric encryption algorithm policy of PGPainless.
222         *
223         * @return default symmetric encryption algorithm policy
224         */
225        public static SymmetricKeyAlgorithmPolicy defaultSymmetricKeyEncryptionAlgorithmPolicy() {
226            return new SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm.AES_256, Arrays.asList(
227                    // Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish
228                    SymmetricKeyAlgorithm.AES_256,
229                    SymmetricKeyAlgorithm.AES_192,
230                    SymmetricKeyAlgorithm.AES_128,
231                    SymmetricKeyAlgorithm.TWOFISH,
232                    SymmetricKeyAlgorithm.CAMELLIA_256,
233                    SymmetricKeyAlgorithm.CAMELLIA_192,
234                    SymmetricKeyAlgorithm.CAMELLIA_128
235            ));
236        }
237
238        /**
239         * The default symmetric decryption algorithm policy of PGPainless.
240         *
241         * @return default symmetric decryption algorithm policy
242         */
243        public static SymmetricKeyAlgorithmPolicy defaultSymmetricKeyDecryptionAlgorithmPolicy() {
244            return new SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm.AES_256, Arrays.asList(
245                    // Reject: Unencrypted, IDEA, TripleDES, Blowfish
246                    SymmetricKeyAlgorithm.CAST5,
247                    SymmetricKeyAlgorithm.AES_256,
248                    SymmetricKeyAlgorithm.AES_192,
249                    SymmetricKeyAlgorithm.AES_128,
250                    SymmetricKeyAlgorithm.TWOFISH,
251                    SymmetricKeyAlgorithm.CAMELLIA_256,
252                    SymmetricKeyAlgorithm.CAMELLIA_192,
253                    SymmetricKeyAlgorithm.CAMELLIA_128
254            ));
255        }
256
257        /**
258         * Select the best acceptable algorithm from the options list.
259         * The best algorithm is the first algorithm we encounter in our list of acceptable algorithms that
260         * is also contained in the list of options.
261         *
262         *
263         * @param options list of algorithm options
264         * @return best
265         */
266        public SymmetricKeyAlgorithm selectBest(List<SymmetricKeyAlgorithm> options) {
267            for (SymmetricKeyAlgorithm acceptable : acceptableSymmetricKeyAlgorithms) {
268                if (options.contains(acceptable)) {
269                    return acceptable;
270                }
271            }
272            return null;
273        }
274    }
275
276    public static final class HashAlgorithmPolicy {
277
278        private final HashAlgorithm defaultHashAlgorithm;
279        private final List<HashAlgorithm> acceptableHashAlgorithms;
280
281        public HashAlgorithmPolicy(HashAlgorithm defaultHashAlgorithm, List<HashAlgorithm> acceptableHashAlgorithms) {
282            this.defaultHashAlgorithm = defaultHashAlgorithm;
283            this.acceptableHashAlgorithms = Collections.unmodifiableList(acceptableHashAlgorithms);
284        }
285
286        /**
287         * Return the default hash algorithm.
288         * This algorithm is used as a fallback when no consensus about hash algorithms can be reached.
289         *
290         * @return default hash algorithm
291         */
292        public HashAlgorithm defaultHashAlgorithm() {
293            return defaultHashAlgorithm;
294        }
295
296        /**
297         * Return true if the given hash algorithm is acceptable by this policy.
298         *
299         * @param hashAlgorithm hash algorithm
300         * @return true if the hash algorithm is acceptable, false otherwise
301         */
302        public boolean isAcceptable(HashAlgorithm hashAlgorithm) {
303            return acceptableHashAlgorithms.contains(hashAlgorithm);
304        }
305
306        /**
307         * Return true if the given hash algorithm is acceptable by this policy.
308         *
309         * @param algorithmId hash algorithm
310         * @return true if the hash algorithm is acceptable, false otherwise
311         */
312        public boolean isAcceptable(int algorithmId) {
313            HashAlgorithm algorithm = HashAlgorithm.fromId(algorithmId);
314            return isAcceptable(algorithm);
315        }
316
317        /**
318         * The default signature hash algorithm policy of PGPainless.
319         * Note that this policy is only used for non-revocation signatures.
320         * For revocation signatures {@link #defaultRevocationSignatureHashAlgorithmPolicy()} is used instead.
321         *
322         * @return default signature hash algorithm policy
323         */
324        public static HashAlgorithmPolicy defaultSignatureAlgorithmPolicy() {
325            return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList(
326                    HashAlgorithm.SHA224,
327                    HashAlgorithm.SHA256,
328                    HashAlgorithm.SHA384,
329                    HashAlgorithm.SHA512
330            ));
331        }
332
333        /**
334         * The default revocation signature hash algorithm policy of PGPainless.
335         *
336         * @return default revocation signature hash algorithm policy
337         */
338        public static HashAlgorithmPolicy defaultRevocationSignatureHashAlgorithmPolicy() {
339            return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList(
340                    HashAlgorithm.RIPEMD160,
341                    HashAlgorithm.SHA1,
342                    HashAlgorithm.SHA224,
343                    HashAlgorithm.SHA256,
344                    HashAlgorithm.SHA384,
345                    HashAlgorithm.SHA512
346            ));
347        }
348    }
349
350    public static final class CompressionAlgorithmPolicy {
351
352        private final CompressionAlgorithm defaultCompressionAlgorithm;
353        private final List<CompressionAlgorithm> acceptableCompressionAlgorithms;
354
355        public CompressionAlgorithmPolicy(CompressionAlgorithm defaultCompressionAlgorithm,
356                                          List<CompressionAlgorithm> acceptableCompressionAlgorithms) {
357            this.defaultCompressionAlgorithm = defaultCompressionAlgorithm;
358            this.acceptableCompressionAlgorithms = Collections.unmodifiableList(acceptableCompressionAlgorithms);
359        }
360
361        public CompressionAlgorithm defaultCompressionAlgorithm() {
362            return defaultCompressionAlgorithm;
363        }
364
365        public boolean isAcceptable(int compressionAlgorithmTag) {
366            return isAcceptable(CompressionAlgorithm.fromId(compressionAlgorithmTag));
367        }
368
369        public boolean isAcceptable(CompressionAlgorithm compressionAlgorithm) {
370            return acceptableCompressionAlgorithms.contains(compressionAlgorithm);
371        }
372
373        public static CompressionAlgorithmPolicy defaultCompressionAlgorithmPolicy() {
374            return new CompressionAlgorithmPolicy(CompressionAlgorithm.ZIP, Arrays.asList(
375                    CompressionAlgorithm.UNCOMPRESSED,
376                    CompressionAlgorithm.ZIP,
377                    CompressionAlgorithm.BZIP2,
378                    CompressionAlgorithm.ZLIB
379            ));
380        }
381    }
382
383    public static final class PublicKeyAlgorithmPolicy {
384
385        private final Map<PublicKeyAlgorithm, Integer> algorithmStrengths = new EnumMap<>(PublicKeyAlgorithm.class);
386
387        public PublicKeyAlgorithmPolicy(Map<PublicKeyAlgorithm, Integer> minimalAlgorithmBitStrengths) {
388            this.algorithmStrengths.putAll(minimalAlgorithmBitStrengths);
389        }
390
391        public boolean isAcceptable(int algorithmId, int bitStrength) {
392            return isAcceptable(PublicKeyAlgorithm.fromId(algorithmId), bitStrength);
393        }
394
395        public boolean isAcceptable(PublicKeyAlgorithm algorithm, int bitStrength) {
396            if (!algorithmStrengths.containsKey(algorithm)) {
397                return false;
398            }
399
400            int minStrength = algorithmStrengths.get(algorithm);
401            return bitStrength >= minStrength;
402        }
403
404        /**
405         * Return PGPainless' default public key algorithm policy.
406         * This policy is based upon recommendations made by the German Federal Office for Information Security (BSI).
407         *
408         * Basically this policy requires keys based on elliptic curves to have a bit strength of at least 250,
409         * and keys based on prime number factorization / discrete logarithm problems to have a strength of at least 2000 bits.
410         *
411         * @see <a href="https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf">
412         *     BSI - Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths (2021-01)</a>
413         * @see <a href="https://www.keylength.com/">BlueKrypt | Cryptographic Key Length Recommendation</a>
414         *
415         * @return default algorithm policy
416         */
417        public static PublicKeyAlgorithmPolicy defaultPublicKeyAlgorithmPolicy() {
418            Map<PublicKeyAlgorithm, Integer> minimalBitStrengths = new EnumMap<>(PublicKeyAlgorithm.class);
419            // §5.4.1
420            minimalBitStrengths.put(PublicKeyAlgorithm.RSA_GENERAL, 2000);
421            minimalBitStrengths.put(PublicKeyAlgorithm.RSA_SIGN, 2000);
422            minimalBitStrengths.put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000);
423            // Note: ElGamal is not mentioned in the BSI document.
424            //  We assume that the requirements are similar to other DH algorithms
425            minimalBitStrengths.put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000);
426            minimalBitStrengths.put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000);
427            // §5.4.2
428            minimalBitStrengths.put(PublicKeyAlgorithm.DSA, 2000);
429            // §5.4.3
430            minimalBitStrengths.put(PublicKeyAlgorithm.ECDSA, 250);
431            // Note: EdDSA is not mentioned in the BSI document.
432            //  We assume that the requirements are similar to other EC algorithms.
433            minimalBitStrengths.put(PublicKeyAlgorithm.EDDSA, 250);
434            // §7.2.1
435            minimalBitStrengths.put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000);
436            // §7.2.2
437            minimalBitStrengths.put(PublicKeyAlgorithm.ECDH, 250);
438            minimalBitStrengths.put(PublicKeyAlgorithm.EC, 250);
439
440            return new PublicKeyAlgorithmPolicy(minimalBitStrengths);
441        }
442    }
443
444    /**
445     * Return the {@link NotationRegistry} of PGPainless.
446     * The notation registry is used to decide, whether a Notation is known or not.
447     * Background: Critical unknown notations render signatures invalid.
448     *
449     * @return Notation registry
450     */
451    public NotationRegistry getNotationRegistry() {
452        return notationRegistry;
453    }
454
455    /**
456     * Return the current {@link AlgorithmSuite} which defines preferred algorithms used during key generation.
457     * @return current algorithm suite
458     */
459    public @Nonnull AlgorithmSuite getKeyGenerationAlgorithmSuite() {
460        return keyGenerationAlgorithmSuite;
461    }
462
463    /**
464     * Set a custom {@link AlgorithmSuite} which defines preferred algorithms used during key generation.
465     *
466     * @param algorithmSuite custom algorithm suite
467     */
468    public void setKeyGenerationAlgorithmSuite(@Nonnull AlgorithmSuite algorithmSuite) {
469        this.keyGenerationAlgorithmSuite = algorithmSuite;
470    }
471}