// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.policy import java.util.* import org.pgpainless.algorithm.* import org.pgpainless.util.DateUtil import org.pgpainless.util.NotationRegistry class Policy( var certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, var dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, var compressionAlgorithmPolicy: CompressionAlgorithmPolicy, var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, var notationRegistry: NotationRegistry ) { constructor() : this( HashAlgorithmPolicy.smartCertificationSignatureHashAlgorithmPolicy(), HashAlgorithmPolicy.smartCertificationSignatureHashAlgorithmPolicy(), HashAlgorithmPolicy.smartDataSignatureHashAlgorithmPolicy(), SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(), SymmetricKeyAlgorithmPolicy.symmetricKeyDecryptionPolicy2022(), CompressionAlgorithmPolicy.anyCompressionAlgorithmPolicy(), PublicKeyAlgorithmPolicy.bsi2021PublicKeyAlgorithmPolicy(), NotationRegistry()) var keyGenerationAlgorithmSuite = AlgorithmSuite.defaultAlgorithmSuite var signerUserIdValidationLevel = SignerUserIdValidationLevel.DISABLED var enableKeyParameterValidation = false fun isEnableKeyParameterValidation() = enableKeyParameterValidation /** * Create a HashAlgorithmPolicy which accepts all [HashAlgorithms][HashAlgorithm] from the given * map, if the queried usage date is BEFORE the respective termination date. A termination date * value of
null
means no termination, resulting in the algorithm being acceptable, * regardless of usage date. * * @param defaultHashAlgorithm default hash algorithm * @param algorithmTerminationDates map of acceptable algorithms and their termination dates */ class HashAlgorithmPolicy( val defaultHashAlgorithm: HashAlgorithm, val acceptableHashAlgorithmsAndTerminationDates: Map ) { /** * Create a [HashAlgorithmPolicy] which accepts all [HashAlgorithms][HashAlgorithm] listed * in the given list, regardless of usage date. * * @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation * fails) * @param acceptableHashAlgorithms list of acceptable hash algorithms */ constructor( defaultHashAlgorithm: HashAlgorithm, acceptableHashAlgorithms: List ) : this(defaultHashAlgorithm, acceptableHashAlgorithms.associateWith { null }) fun isAcceptable(hashAlgorithm: HashAlgorithm) = isAcceptable(hashAlgorithm, Date()) /** * Return true, if the given algorithm is acceptable for the given usage date. * * @param hashAlgorithm algorithm * @param referenceTime usage date (e.g. signature creation time) * @return acceptance */ fun isAcceptable(hashAlgorithm: HashAlgorithm, referenceTime: Date): Boolean { if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) return false val terminationDate = acceptableHashAlgorithmsAndTerminationDates[hashAlgorithm] ?: return true return terminationDate > referenceTime } fun isAcceptable(algorithmId: Int) = isAcceptable(algorithmId, Date()) fun isAcceptable(algorithmId: Int, referenceTime: Date): Boolean { val algorithm = HashAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm, referenceTime) } fun defaultHashAlgorithm() = defaultHashAlgorithm companion object { // https://sequoia-pgp.org/blog/2023/02/01/202302-happy-sha1-day/ // signature data which is not attacker-controlled is acceptable before 2023-02-01 @JvmStatic fun smartCertificationSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( HashAlgorithm.SHA512, buildMap { put(HashAlgorithm.SHA3_512, null) put(HashAlgorithm.SHA3_512, null) put(HashAlgorithm.SHA3_256, null) put(HashAlgorithm.SHA512, null) put(HashAlgorithm.SHA384, null) put(HashAlgorithm.SHA256, null) put(HashAlgorithm.SHA224, null) put( HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2023-02-01 00:00:00 UTC")) put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2023-02-01 00:00:00 UTC")) put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) }) @JvmStatic fun smartDataSignatureHashAlgorithmPolicy() = smartSignatureHashAlgorithmPolicy() @JvmStatic fun smartSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( HashAlgorithm.SHA512, buildMap { put(HashAlgorithm.SHA3_512, null) put(HashAlgorithm.SHA3_256, null) put(HashAlgorithm.SHA512, null) put(HashAlgorithm.SHA384, null) put(HashAlgorithm.SHA256, null) put(HashAlgorithm.SHA224, null) put( HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")) put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) }) /** * [HashAlgorithmPolicy] which only accepts signatures made using algorithms which are * acceptable according to 2022 standards. * * Particularly this policy only accepts algorithms from the SHA2 and SHA3 families. * * @return static signature algorithm policy */ @JvmStatic fun static2022SignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( HashAlgorithm.SHA512, listOf( HashAlgorithm.SHA3_512, HashAlgorithm.SHA3_256, HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256, HashAlgorithm.SHA224)) /** * Hash algorithm policy for revocation signatures, which accepts SHA1 and SHA2 * algorithms, as well as RIPEMD160. * * @return static revocation signature hash algorithm policy */ @JvmStatic fun static2022RevocationSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( HashAlgorithm.SHA512, listOf( HashAlgorithm.SHA3_512, HashAlgorithm.SHA3_256, HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256, HashAlgorithm.SHA224, HashAlgorithm.SHA1, HashAlgorithm.RIPEMD160)) } } class SymmetricKeyAlgorithmPolicy( val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm, val acceptableSymmetricKeyAlgorithms: List ) { fun isAcceptable(algorithm: SymmetricKeyAlgorithm) = acceptableSymmetricKeyAlgorithms.contains(algorithm) fun isAcceptable(algorithmId: Int): Boolean { val algorithm = SymmetricKeyAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm) } fun selectBest(options: List): SymmetricKeyAlgorithm? { for (acceptable in acceptableSymmetricKeyAlgorithms) { if (options.contains(acceptable)) { return acceptable } } return null } companion object { /** * The default symmetric encryption algorithm policy of PGPainless. * * @return default symmetric encryption algorithm policy * @deprecated not expressive - will be removed in a future release */ @JvmStatic @Deprecated( "Not expressive - will be removed in a future release", ReplaceWith("symmetricKeyEncryptionPolicy2022")) fun defaultSymmetricKeyEncryptionAlgorithmPolicy() = symmetricKeyEncryptionPolicy2022() /** * Policy for symmetric encryption algorithms in the context of message production * (encryption). This suite contains algorithms that are deemed safe to use in 2022. * * @return 2022 symmetric key encryption algorithm policy */ @JvmStatic fun symmetricKeyEncryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( SymmetricKeyAlgorithm.AES_128, // Reject: Unencrypted, IDEA, TripleDES, CAST5, Blowfish listOf( SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_128, SymmetricKeyAlgorithm.TWOFISH, SymmetricKeyAlgorithm.CAMELLIA_256, SymmetricKeyAlgorithm.CAMELLIA_192, SymmetricKeyAlgorithm.CAMELLIA_128)) /** * The default symmetric decryption algorithm policy of PGPainless. * * @return default symmetric decryption algorithm policy * @deprecated not expressive - will be removed in a future update */ @JvmStatic @Deprecated( "not expressive - will be removed in a future update", ReplaceWith("symmetricKeyDecryptionPolicy2022()")) fun defaultSymmetricKeyDecryptionAlgorithmPolicy() = symmetricKeyDecryptionPolicy2022() /** * Policy for symmetric key encryption algorithms in the context of message consumption * (decryption). This suite contains algorithms that are deemed safe to use in 2022. * * @return 2022 symmetric key decryption algorithm policy */ @JvmStatic fun symmetricKeyDecryptionPolicy2022() = SymmetricKeyAlgorithmPolicy( SymmetricKeyAlgorithm.AES_128, // Reject: Unencrypted, IDEA, TripleDES, Blowfish listOf( SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_128, SymmetricKeyAlgorithm.TWOFISH, SymmetricKeyAlgorithm.CAMELLIA_256, SymmetricKeyAlgorithm.CAMELLIA_192, SymmetricKeyAlgorithm.CAMELLIA_128, SymmetricKeyAlgorithm.CAST5)) } } class CompressionAlgorithmPolicy( val defaultCompressionAlgorithm: CompressionAlgorithm, val acceptableCompressionAlgorithms: List ) { fun isAcceptable(algorithm: CompressionAlgorithm) = acceptableCompressionAlgorithms.contains(algorithm) fun isAcceptable(algorithmId: Int): Boolean { val algorithm = CompressionAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm) } fun defaultCompressionAlgorithm() = defaultCompressionAlgorithm companion object { /** * Default {@link CompressionAlgorithmPolicy} of PGPainless. The default compression * algorithm policy accepts any compression algorithm. * * @return default algorithm policy * @deprecated not expressive - might be removed in a future release */ @JvmStatic @Deprecated( "not expressive - might be removed in a future release", ReplaceWith("anyCompressionAlgorithmPolicy()")) fun defaultCompressionAlgorithmPolicy() = anyCompressionAlgorithmPolicy() /** * Policy that accepts any known compression algorithm and offers * [CompressionAlgorithm.ZIP] as default algorithm. * * @return compression algorithm policy */ @JvmStatic fun anyCompressionAlgorithmPolicy() = CompressionAlgorithmPolicy( CompressionAlgorithm.ZIP, listOf( CompressionAlgorithm.UNCOMPRESSED, CompressionAlgorithm.ZIP, CompressionAlgorithm.BZIP2, CompressionAlgorithm.ZLIB)) } } class PublicKeyAlgorithmPolicy(private val algorithmStrengths: Map) { fun isAcceptable(algorithm: PublicKeyAlgorithm, bitStrength: Int): Boolean { return bitStrength >= (algorithmStrengths[algorithm] ?: return false) } fun isAcceptable(algorithmId: Int, bitStrength: Int): Boolean { val algorithm = PublicKeyAlgorithm.fromId(algorithmId) ?: return false return isAcceptable(algorithm, bitStrength) } companion object { /** * Return PGPainless' default public key algorithm policy. This policy is based upon * recommendations made by the German Federal Office for Information Security (BSI). * * @return default algorithm policy * @deprecated not expressive - might be removed in a future release */ @JvmStatic @Deprecated( "not expressive - might be removed in a future release", ReplaceWith("bsi2021PublicKeyAlgorithmPolicy()")) fun defaultPublicKeyAlgorithmPolicy() = bsi2021PublicKeyAlgorithmPolicy() /** * This policy is based upon recommendations made by the German Federal Office for * Information Security (BSI). * * Basically this policy requires keys based on elliptic curves to have a bit strength * of at least 250, and keys based on prime number factorization / discrete logarithm * problems to have a strength of at least 2000 bits. * * @return default algorithm policy * @see BSI - * Technical Guideline - Cryptographic Mechanisms: Recommendations and Key Lengths * (2021-01) * @see BlueKrypt | Cryptographic Key Length * Recommendation */ @JvmStatic fun bsi2021PublicKeyAlgorithmPolicy() = PublicKeyAlgorithmPolicy( buildMap { // §5.4.1 put(PublicKeyAlgorithm.RSA_GENERAL, 2000) put(PublicKeyAlgorithm.RSA_SIGN, 2000) put(PublicKeyAlgorithm.RSA_ENCRYPT, 2000) // Note: ElGamal is not mentioned in the BSI document. // We assume that the requirements are similar to other DH algorithms put(PublicKeyAlgorithm.ELGAMAL_ENCRYPT, 2000) put(PublicKeyAlgorithm.ELGAMAL_GENERAL, 2000) // §5.4.2 put(PublicKeyAlgorithm.DSA, 2000) // §5.4.3 put(PublicKeyAlgorithm.ECDSA, 250) // Note: EdDSA is not mentioned in the BSI document. // We assume that the requirements are similar to other EC algorithms. put(PublicKeyAlgorithm.EDDSA, 250) // §7.2.1 put(PublicKeyAlgorithm.DIFFIE_HELLMAN, 2000) // §7.2.2 put(PublicKeyAlgorithm.ECDH, 250) }) } } enum class SignerUserIdValidationLevel { /** * PGPainless will verify {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets in * signatures strictly. This means, that signatures with Signer's User-ID subpackets * containing a value that does not match the signer key's user-id exactly, will be * rejected. E.g. Signer's user-id "alice@pgpainless.org", User-ID: "Alice * <alice@pgpainless.org>" does not match exactly and is therefore rejected. */ STRICT, /** * PGPainless will ignore {@link org.bouncycastle.bcpg.sig.SignerUserID} subpackets on * signature. */ DISABLED } companion object { @Volatile private var INSTANCE: Policy? = null @JvmStatic fun getInstance() = INSTANCE ?: synchronized(this) { INSTANCE ?: Policy().also { INSTANCE = it } } } }