1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Move EncryptionMechanismNegotiator into own interface, improve negotiation

This commit is contained in:
Paul Schaub 2025-05-25 16:27:49 +02:00
parent 33ea12adbf
commit 8623352bf2
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
3 changed files with 111 additions and 34 deletions

View file

@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm.negotiation
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.pgpainless.algorithm.AEADAlgorithm
import org.pgpainless.algorithm.AEADCipherMode
import org.pgpainless.algorithm.Feature
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.policy.Policy
fun interface EncryptionMechanismNegotiator {
fun negotiate(
policy: Policy,
override: MessageEncryptionMechanism?,
features: List<Set<Feature>>,
aeadAlgorithmPreferences: List<Set<AEADCipherMode>>,
symmetricAlgorithmPreferences: List<Set<SymmetricKeyAlgorithm>>
): MessageEncryptionMechanism
companion object {
@JvmStatic
fun modificationDetectionOrBetter(
symmetricKeyAlgorithmNegotiator: SymmetricKeyAlgorithmNegotiator
): EncryptionMechanismNegotiator =
object : EncryptionMechanismNegotiator {
override fun negotiate(
policy: Policy,
override: MessageEncryptionMechanism?,
features: List<Set<Feature>>,
aeadAlgorithmPreferences: List<Set<AEADCipherMode>>,
symmetricAlgorithmPreferences: List<Set<SymmetricKeyAlgorithm>>
): MessageEncryptionMechanism {
// If the user supplied an override, use that
if (override != null) {
return override
}
// If all support SEIPD2, use SEIPD2
if (features.all { it.contains(Feature.MODIFICATION_DETECTION_2) }) {
// Find best supported algorithm combination
val counted = mutableMapOf<AEADCipherMode, Int>()
for (pref in aeadAlgorithmPreferences) {
for (mode in pref) {
counted[mode] = counted.getOrDefault(mode, 0) + 1
}
}
// filter for supported combinations and find most widely supported
val bestSupportedMode: AEADCipherMode =
counted
.filter {
policy.messageEncryptionAlgorithmPolicy.isAcceptable(
MessageEncryptionMechanism.aead(
it.key.ciphermode.algorithmId,
it.key.aeadAlgorithm.algorithmId))
}
.maxByOrNull { it.value }
?.key
?: AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128)
// return best supported mode or symmetric key fallback mechanism
return MessageEncryptionMechanism.aead(
bestSupportedMode.ciphermode.algorithmId,
bestSupportedMode.aeadAlgorithm.algorithmId)
}
// If all support SEIPD1, negotiate SEIPD1 using symmetricKeyAlgorithmNegotiator
else if (features.all { it.contains(Feature.MODIFICATION_DETECTION) }) {
return MessageEncryptionMechanism.integrityProtected(
symmetricKeyAlgorithmNegotiator
.negotiate(
policy.messageEncryptionAlgorithmPolicy
.symmetricAlgorithmPolicy,
null,
symmetricAlgorithmPreferences)
.algorithmId)
}
// Else fall back to fallback mechanism from policy
else {
return policy.messageEncryptionAlgorithmPolicy.asymmetricFallbackMechanism
}
}
}
}
}

View file

@ -11,15 +11,15 @@ import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.AEADAlgorithm
import org.pgpainless.algorithm.AEADCipherMode
import org.pgpainless.algorithm.EncryptionPurpose import org.pgpainless.algorithm.EncryptionPurpose
import org.pgpainless.algorithm.Feature
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.algorithm.negotiation.EncryptionMechanismNegotiator
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
import org.pgpainless.authentication.CertificateAuthority import org.pgpainless.authentication.CertificateAuthority
import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector
import org.pgpainless.exception.KeyException.* import org.pgpainless.exception.KeyException.ExpiredKeyException
import org.pgpainless.exception.KeyException.UnacceptableEncryptionKeyException
import org.pgpainless.exception.KeyException.UnacceptableSelfSignatureException
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.key.info.KeyAccessor import org.pgpainless.key.info.KeyAccessor
import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.key.info.KeyRingInfo
@ -427,42 +427,26 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
internal fun negotiateSymmetricEncryptionAlgorithm(): SymmetricKeyAlgorithm {
val preferences =
keysAndAccessors.values.map { it.preferredSymmetricKeyAlgorithms }.toList()
val algorithm =
byPopularity()
.negotiate(
api.algorithmPolicy.messageEncryptionAlgorithmPolicy.symmetricAlgorithmPolicy,
encryptionAlgorithmOverride,
preferences)
return algorithm
}
internal fun negotiateEncryptionMechanism(): MessageEncryptionMechanism { internal fun negotiateEncryptionMechanism(): MessageEncryptionMechanism {
if (encryptionMechanismOverride != null) { if (encryptionMechanismOverride != null) {
return encryptionMechanismOverride!! return encryptionMechanismOverride!!
} }
val features = keysAndAccessors.values.map { it.features }.toList() val features = keysAndAccessors.values.map { it.features }.toList()
val aeadAlgorithms = keysAndAccessors.values.map { it.preferredAEADCipherSuites }.toList()
val symmetricKeyAlgorithms =
keysAndAccessors.values.map { it.preferredSymmetricKeyAlgorithms }.toList()
if (features.all { it.contains(Feature.MODIFICATION_DETECTION_2) }) { val mechanism =
val aeadPrefs = keysAndAccessors.values.map { it.preferredAEADCipherSuites }.toList() EncryptionMechanismNegotiator.modificationDetectionOrBetter(byPopularity())
val counted = mutableMapOf<AEADCipherMode, Int>() .negotiate(
for (pref in aeadPrefs) { api.algorithmPolicy,
for (mode in pref) { encryptionMechanismOverride,
counted[mode] = counted.getOrDefault(mode, 0) + 1 features,
} aeadAlgorithms,
} symmetricKeyAlgorithms)
val max: AEADCipherMode =
counted.maxByOrNull { it.value }?.key return mechanism
?: AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128)
return MessageEncryptionMechanism.aead(
max.ciphermode.algorithmId, max.aeadAlgorithm.algorithmId)
} else {
return MessageEncryptionMechanism.integrityProtected(
negotiateSymmetricEncryptionAlgorithm().algorithmId)
}
} }
fun interface EncryptionKeySelector { fun interface EncryptionKeySelector {

View file

@ -79,11 +79,15 @@ public class MechanismNegotiationTest {
.build())); .build()));
} }
/**
* Here, we fall back to SEIPD1(AES128), as that is the policy fallback mechanism.
*/
@TestTemplate @TestTemplate
@ExtendWith(TestAllImplementations.class) @ExtendWith(TestAllImplementations.class)
public void testEncryptToV6SEIPD1CertAndV6SEIPD2Cert() throws IOException, PGPException { public void testEncryptToV6SEIPD1CertAndV6SEIPD2Cert() throws IOException, PGPException {
testEncryptDecryptAndCheckExpectations( testEncryptDecryptAndCheckExpectations(
MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()), MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_128.getAlgorithmId()),
new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder() new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder()
.overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256)) .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256))