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}