From 5c0cdfd494f09c791064fd1ba797f4d58149c1d9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 20 May 2025 15:05:24 +0200 Subject: [PATCH] WIP: EncryptionMechanismPolicy --- .../pgpainless/bouncycastle/PolicyAdapter.kt | 7 +- .../OpenPgpMessageInputStream.kt | 3 +- .../encryption_signing/EncryptionOptions.kt | 6 +- .../kotlin/org/pgpainless/policy/Policy.kt | 254 ++++++++++++++++-- 4 files changed, 241 insertions(+), 29 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/PolicyAdapter.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/PolicyAdapter.kt index 7a6f4bcb..64e7cc41 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/PolicyAdapter.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/PolicyAdapter.kt @@ -92,7 +92,8 @@ class PolicyAdapter(val policy: Policy) : OpenPGPPolicy { * @return boolean indicating, whether the encryption algorithm is acceptable */ override fun isAcceptableSymmetricKeyAlgorithm(algorithmId: Int): Boolean { - return policy.symmetricKeyEncryptionAlgorithmPolicy.isAcceptable(algorithmId) + return policy.messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy.isAcceptable( + algorithmId) } /** * Return the default symmetric encryption algorithm. This algorithm is used as fallback to @@ -101,7 +102,9 @@ class PolicyAdapter(val policy: Policy) : OpenPGPPolicy { * @return default symmetric encryption algorithm */ override fun getDefaultSymmetricKeyAlgorithm(): Int { - return policy.symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm.algorithmId + return policy.messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy + .defaultSymmetricKeyAlgorithm + .algorithmId } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 15695fd6..30ee3cd7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -739,7 +739,8 @@ class OpenPgpMessageInputStream( } private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean = - api.algorithmPolicy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm) + api.algorithmPolicy.messageDecryptionAlgorithmPolicy.symmetricAlgorithmPolicy.isAcceptable( + algorithm) private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) { if (!isAcceptable(algorithm)) { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index 16333df4..e971189d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -404,8 +404,8 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: fun overrideEncryptionMechanism(encryptionMechanism: MessageEncryptionMechanism) = apply { require( - api.algorithmPolicy.symmetricKeyEncryptionAlgorithmPolicy.isAcceptable( - encryptionMechanism.symmetricKeyAlgorithm)) { + api.algorithmPolicy.messageEncryptionAlgorithmPolicy.isAcceptable( + encryptionMechanism)) { "Provided symmetric encryption algorithm is not acceptable." } _encryptionMechanismOverride = encryptionMechanism @@ -431,7 +431,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: val algorithm = byPopularity() .negotiate( - api.algorithmPolicy.symmetricKeyEncryptionAlgorithmPolicy, + api.algorithmPolicy.messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy, encryptionAlgorithmOverride, preferences) return algorithm diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt index 0e343cbf..f6e1a4f0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt @@ -5,23 +5,77 @@ package org.pgpainless.policy import java.util.* +import org.bouncycastle.openpgp.api.EncryptedDataPacketType +import org.bouncycastle.openpgp.api.MessageEncryptionMechanism import org.pgpainless.algorithm.* import org.pgpainless.key.protection.KeyRingProtectionSettings import org.pgpainless.util.DateUtil import org.pgpainless.util.NotationRegistry -class Policy( - val certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, - val revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, - val dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, - val symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, - val symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, - val compressionAlgorithmPolicy: CompressionAlgorithmPolicy, - val publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, - val keyProtectionSettings: KeyRingProtectionSettings, - val notationRegistry: NotationRegistry, +class Policy { + + val certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy + val revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy + val dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy + val messageEncryptionAlgorithmPolicy: MessageEncryptionMechanismPolicy + val messageDecryptionAlgorithmPolicy: MessageEncryptionMechanismPolicy + val compressionAlgorithmPolicy: CompressionAlgorithmPolicy + val publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy + val keyProtectionSettings: KeyRingProtectionSettings + val notationRegistry: NotationRegistry val keyGenerationAlgorithmSuite: AlgorithmSuite -) { + + constructor( + certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + messageEncryptionMechanismPolicy: MessageEncryptionMechanismPolicy, + messageDecryptionMechanismPolicy: MessageEncryptionMechanismPolicy, + compressionAlgorithmPolicy: CompressionAlgorithmPolicy, + publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, + keyProtectionSettings: KeyRingProtectionSettings, + notationRegistry: NotationRegistry, + keyGenerationAlgorithmSuite: AlgorithmSuite + ) { + this.certificationSignatureHashAlgorithmPolicy = certificationSignatureHashAlgorithmPolicy + this.revocationSignatureHashAlgorithmPolicy = revocationSignatureHashAlgorithmPolicy + this.dataSignatureHashAlgorithmPolicy = dataSignatureHashAlgorithmPolicy + this.messageEncryptionAlgorithmPolicy = messageEncryptionMechanismPolicy + this.messageDecryptionAlgorithmPolicy = messageDecryptionMechanismPolicy + this.compressionAlgorithmPolicy = compressionAlgorithmPolicy + this.publicKeyAlgorithmPolicy = publicKeyAlgorithmPolicy + this.keyProtectionSettings = keyProtectionSettings + this.notationRegistry = notationRegistry + this.keyGenerationAlgorithmSuite = keyGenerationAlgorithmSuite + } + + constructor( + certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + compressionAlgorithmPolicy: CompressionAlgorithmPolicy, + publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy, + keyProtectionSettings: KeyRingProtectionSettings, + notationRegistry: NotationRegistry, + keyGenerationAlgorithmSuite: AlgorithmSuite + ) { + this.certificationSignatureHashAlgorithmPolicy = certificationSignatureHashAlgorithmPolicy + this.revocationSignatureHashAlgorithmPolicy = revocationSignatureHashAlgorithmPolicy + this.dataSignatureHashAlgorithmPolicy = dataSignatureHashAlgorithmPolicy + this.messageEncryptionAlgorithmPolicy = + MessageEncryptionMechanismPolicy.rfc4880Plus9580PlusLibrePGP( + symmetricKeyEncryptionAlgorithmPolicy) + this.messageDecryptionAlgorithmPolicy = + MessageEncryptionMechanismPolicy.rfc4880Plus9580PlusLibrePGP( + symmetricKeyDecryptionAlgorithmPolicy) + this.compressionAlgorithmPolicy = compressionAlgorithmPolicy + this.publicKeyAlgorithmPolicy = publicKeyAlgorithmPolicy + this.keyProtectionSettings = keyProtectionSettings + this.notationRegistry = notationRegistry + this.keyGenerationAlgorithmSuite = keyGenerationAlgorithmSuite + } constructor() : this( @@ -36,6 +90,14 @@ class Policy( NotationRegistry(), AlgorithmSuite.defaultAlgorithmSuite) + @Deprecated("Deprecated in favor of messageEncryptionAlgorithmPolicy") + val symmetricKeyEncryptionAlgorithmPolicy + get() = messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy + + @Deprecated("Deprecated in favor of messageDecryptionAlgorithmPolicy") + val symmetricKeyDecryptionAlgorithmPolicy + get() = messageDecryptionAlgorithmPolicy.symmetricAlgorithmPolicy + /** * Decide, whether to sanitize public key parameters when unlocking OpenPGP secret keys. OpenPGP * v4 keys are susceptible to a class of attacks, where an attacker with access to the locked @@ -189,6 +251,138 @@ class Policy( } } + abstract class MessageEncryptionMechanismPolicy( + val symmetricAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, + val asymmetricFallbackMechanism: MessageEncryptionMechanism, + val symmetricFallbackMechanism: MessageEncryptionMechanism = asymmetricFallbackMechanism + ) { + abstract fun isAcceptable(encryptionMechanism: MessageEncryptionMechanism): Boolean + + companion object { + + @JvmStatic + fun rfc4880( + symAlgPolicy: SymmetricKeyAlgorithmPolicy + ): MessageEncryptionMechanismPolicy { + return object : + MessageEncryptionMechanismPolicy( + symAlgPolicy, + MessageEncryptionMechanism.integrityProtected( + symAlgPolicy.defaultSymmetricKeyAlgorithm.algorithmId)) { + override fun isAcceptable( + encryptionMechanism: MessageEncryptionMechanism + ): Boolean { + return encryptionMechanism.mode == EncryptedDataPacketType.SEIPDv1 && + symAlgPolicy.isAcceptable(encryptionMechanism.symmetricKeyAlgorithm) + } + } + } + + @JvmStatic + fun rfc9580( + symAlgPolicy: SymmetricKeyAlgorithmPolicy + ): MessageEncryptionMechanismPolicy { + return object : + MessageEncryptionMechanismPolicy( + symAlgPolicy, + MessageEncryptionMechanism.aead( + symAlgPolicy.defaultSymmetricKeyAlgorithm.algorithmId, + AEADAlgorithm.OCB.algorithmId)) { + val acceptableAEADAlgorithms = + listOf(AEADAlgorithm.OCB, AEADAlgorithm.GCM, AEADAlgorithm.EAX).map { + it.algorithmId + } + + override fun isAcceptable( + encryptionMechanism: MessageEncryptionMechanism + ): Boolean { + return when (encryptionMechanism.mode) { + EncryptedDataPacketType.SEIPDv1 -> + symAlgPolicy.isAcceptable(encryptionMechanism.symmetricKeyAlgorithm) + EncryptedDataPacketType.SEIPDv2 -> + symAlgPolicy.isAcceptable( + encryptionMechanism.symmetricKeyAlgorithm) && + acceptableAEADAlgorithms.contains( + encryptionMechanism.aeadAlgorithm) + else -> false + } + } + } + } + + @JvmStatic + fun librePgp( + symAlgPolicy: SymmetricKeyAlgorithmPolicy + ): MessageEncryptionMechanismPolicy { + return object : + MessageEncryptionMechanismPolicy( + symAlgPolicy, + MessageEncryptionMechanism.integrityProtected( + symAlgPolicy.defaultSymmetricKeyAlgorithm.algorithmId)) { + val acceptableAEADAlgorithms = listOf(AEADAlgorithm.OCB).map { it.algorithmId } + + override fun isAcceptable( + encryptionMechanism: MessageEncryptionMechanism + ): Boolean { + return when (encryptionMechanism.mode) { + EncryptedDataPacketType.SEIPDv1 -> + symAlgPolicy.isAcceptable(encryptionMechanism.symmetricKeyAlgorithm) + EncryptedDataPacketType.LIBREPGP_OED -> + symAlgPolicy.isAcceptable( + encryptionMechanism.symmetricKeyAlgorithm) && + acceptableAEADAlgorithms.contains( + encryptionMechanism.aeadAlgorithm) + else -> false + } + } + } + } + + @JvmStatic + fun rfc4880Plus9580( + symAlgPolicy: SymmetricKeyAlgorithmPolicy + ): MessageEncryptionMechanismPolicy { + val rfc4880 = rfc4880(symAlgPolicy) + val rfc9580 = rfc9580(symAlgPolicy) + return object : + MessageEncryptionMechanismPolicy( + symAlgPolicy, + rfc4880.asymmetricFallbackMechanism, + rfc4880.symmetricFallbackMechanism) { + override fun isAcceptable( + encryptionMechanism: MessageEncryptionMechanism + ): Boolean { + return rfc9580.isAcceptable(encryptionMechanism) || + rfc4880.isAcceptable(encryptionMechanism) + } + } + } + + @JvmStatic + fun rfc4880Plus9580PlusLibrePGP( + symAlgPolicy: SymmetricKeyAlgorithmPolicy + ): MessageEncryptionMechanismPolicy { + return object : + MessageEncryptionMechanismPolicy( + symAlgPolicy, + MessageEncryptionMechanism.integrityProtected( + symAlgPolicy.defaultSymmetricKeyAlgorithm.algorithmId)) { + override fun isAcceptable( + encryptionMechanism: MessageEncryptionMechanism + ): Boolean { + val rfc4480 = rfc4880(symAlgPolicy) + val rfc9580 = rfc9580(symAlgPolicy) + val librePgp = librePgp(symAlgPolicy) + + return rfc4480.isAcceptable(encryptionMechanism) || + rfc9580.isAcceptable(encryptionMechanism) || + librePgp.isAcceptable(encryptionMechanism) + } + } + } + } + } + class SymmetricKeyAlgorithmPolicy( val defaultSymmetricKeyAlgorithm: SymmetricKeyAlgorithm, val acceptableSymmetricKeyAlgorithms: List @@ -440,10 +634,10 @@ class Policy( origin.revocationSignatureHashAlgorithmPolicy private var dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy = origin.dataSignatureHashAlgorithmPolicy - private var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy = - origin.symmetricKeyEncryptionAlgorithmPolicy - private var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy = - origin.symmetricKeyDecryptionAlgorithmPolicy + private var messageEncryptionMechanismPolicy: MessageEncryptionMechanismPolicy = + origin.messageEncryptionAlgorithmPolicy + private var messageDecryptionMechanismPolicy: MessageEncryptionMechanismPolicy = + origin.messageDecryptionAlgorithmPolicy private var compressionAlgorithmPolicy: CompressionAlgorithmPolicy = origin.compressionAlgorithmPolicy private var publicKeyAlgorithmPolicy: PublicKeyAlgorithmPolicy = @@ -469,17 +663,31 @@ class Policy( dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy ) = apply { this.dataSignatureHashAlgorithmPolicy = dataSignatureHashAlgorithmPolicy } + @Deprecated( + "Usage of SymmetricKeyAlgorithmPolicy is deprecated in favor of MessageEncryptionMechanismPolicy.") fun withSymmetricKeyEncryptionAlgorithmPolicy( symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy - ) = apply { - this.symmetricKeyEncryptionAlgorithmPolicy = symmetricKeyEncryptionAlgorithmPolicy - } + ) = + withMessageEncryptionAlgorithmPolicy( + MessageEncryptionMechanismPolicy.rfc4880Plus9580PlusLibrePGP( + symmetricKeyEncryptionAlgorithmPolicy)) + @Deprecated( + "Usage of SymmetricKeyAlgorithmPolicy is deprecated in favor of MessageEncryptionMechanismPolicy.") fun withSymmetricKeyDecryptionAlgorithmPolicy( symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy - ) = apply { - this.symmetricKeyDecryptionAlgorithmPolicy = symmetricKeyDecryptionAlgorithmPolicy - } + ) = + withMessageDecryptionAlgorithmPolicy( + MessageEncryptionMechanismPolicy.rfc4880Plus9580PlusLibrePGP( + symmetricKeyDecryptionAlgorithmPolicy)) + + fun withMessageEncryptionAlgorithmPolicy( + encryptionMechanismPolicy: MessageEncryptionMechanismPolicy + ) = apply { messageEncryptionMechanismPolicy = encryptionMechanismPolicy } + + fun withMessageDecryptionAlgorithmPolicy( + decryptionMechanismPolicy: MessageEncryptionMechanismPolicy + ) = apply { messageDecryptionMechanismPolicy = decryptionMechanismPolicy } fun withCompressionAlgorithmPolicy(compressionAlgorithmPolicy: CompressionAlgorithmPolicy) = apply { @@ -508,8 +716,8 @@ class Policy( certificationSignatureHashAlgorithmPolicy, revocationSignatureHashAlgorithmPolicy, dataSignatureHashAlgorithmPolicy, - symmetricKeyEncryptionAlgorithmPolicy, - symmetricKeyDecryptionAlgorithmPolicy, + messageEncryptionMechanismPolicy, + messageDecryptionMechanismPolicy, compressionAlgorithmPolicy, publicKeyAlgorithmPolicy, keyProtectionSettings,