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
65e2de8186
commit
a575f46867
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.operator.PGPKeyEncryptionMethodGenerator
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.algorithm.AEADAlgorithm
|
||||
import org.pgpainless.algorithm.AEADCipherMode
|
||||
import org.pgpainless.algorithm.EncryptionPurpose
|
||||
import org.pgpainless.algorithm.Feature
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.algorithm.negotiation.EncryptionMechanismNegotiator
|
||||
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
|
||||
import org.pgpainless.authentication.CertificateAuthority
|
||||
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.info.KeyAccessor
|
||||
import org.pgpainless.key.info.KeyRingInfo
|
||||
|
@ -427,42 +427,26 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api:
|
|||
|
||||
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 {
|
||||
if (encryptionMechanismOverride != null) {
|
||||
return encryptionMechanismOverride!!
|
||||
}
|
||||
|
||||
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 aeadPrefs = keysAndAccessors.values.map { it.preferredAEADCipherSuites }.toList()
|
||||
val counted = mutableMapOf<AEADCipherMode, Int>()
|
||||
for (pref in aeadPrefs) {
|
||||
for (mode in pref) {
|
||||
counted[mode] = counted.getOrDefault(mode, 0) + 1
|
||||
}
|
||||
}
|
||||
val max: AEADCipherMode =
|
||||
counted.maxByOrNull { it.value }?.key
|
||||
?: AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128)
|
||||
return MessageEncryptionMechanism.aead(
|
||||
max.ciphermode.algorithmId, max.aeadAlgorithm.algorithmId)
|
||||
} else {
|
||||
return MessageEncryptionMechanism.integrityProtected(
|
||||
negotiateSymmetricEncryptionAlgorithm().algorithmId)
|
||||
}
|
||||
val mechanism =
|
||||
EncryptionMechanismNegotiator.modificationDetectionOrBetter(byPopularity())
|
||||
.negotiate(
|
||||
api.algorithmPolicy,
|
||||
encryptionMechanismOverride,
|
||||
features,
|
||||
aeadAlgorithms,
|
||||
symmetricKeyAlgorithms)
|
||||
|
||||
return mechanism
|
||||
}
|
||||
|
||||
fun interface EncryptionKeySelector {
|
||||
|
|
|
@ -79,11 +79,15 @@ public class MechanismNegotiationTest {
|
|||
.build()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Here, we fall back to SEIPD1(AES128), as that is the policy fallback mechanism.
|
||||
*/
|
||||
@TestTemplate
|
||||
@ExtendWith(TestAllImplementations.class)
|
||||
public void testEncryptToV6SEIPD1CertAndV6SEIPD2Cert() throws IOException, PGPException {
|
||||
testEncryptDecryptAndCheckExpectations(
|
||||
MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()),
|
||||
MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_128.getAlgorithmId()),
|
||||
|
||||
new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder()
|
||||
.overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue