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:
parent
33ea12adbf
commit
8623352bf2
3 changed files with 111 additions and 34 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue