From 6eaa4836506009859044a313c79d445f8d6e4a1f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 24 Feb 2025 13:47:53 +0100 Subject: [PATCH] Improve KeyRingInfos getPreferences implementations --- .../pgpainless/algorithm/AEADCipherMode.kt | 15 +++ .../OpenPGPCertificateExtensions.kt | 4 + .../extensions/OpenPGPKeyExtensions.kt | 4 + .../PreferredAEADCipherSuitesExtensions.kt | 12 +++ .../PreferredAlgorithmsExtensions.kt | 25 +++++ .../org/pgpainless/key/info/KeyAccessor.kt | 4 + .../org/pgpainless/key/info/KeyRingInfo.kt | 93 +++++++++++++------ .../subpackets/SignatureSubpacketsUtil.kt | 9 ++ 8 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADCipherMode.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADCipherMode.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADCipherMode.kt new file mode 100644 index 00000000..5cd29e6c --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AEADCipherMode.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm + +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites.Combination + +data class AEADCipherMode(val aeadAlgorithm: AEADAlgorithm, val ciphermode: SymmetricKeyAlgorithm) { + constructor( + combination: Combination + ) : this( + AEADAlgorithm.requireFromId(combination.aeadAlgorithm), + SymmetricKeyAlgorithm.requireFromId(combination.symmetricAlgorithm)) +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt index 31e4fae6..8cc954fa 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.bouncycastle.extensions import org.bouncycastle.openpgp.PGPOnePassSignature diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt index c78a79db..6be48f4a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.bouncycastle.extensions import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt new file mode 100644 index 00000000..fec2dcc1 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAEADCipherSuitesExtensions.kt @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.bouncycastle.extensions + +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites +import org.pgpainless.algorithm.AEADCipherMode + +fun PreferredAEADCiphersuites?.toAEADCipherModes(): Set { + return this?.algorithms?.asSequence()?.map { AEADCipherMode(it) }?.toSet() ?: setOf() +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt new file mode 100644 index 00000000..e78e7568 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PreferredAlgorithmsExtensions.kt @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.bouncycastle.extensions + +import org.bouncycastle.bcpg.sig.PreferredAlgorithms +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.SymmetricKeyAlgorithm + +fun PreferredAlgorithms?.toHashAlgorithms(): Set { + return this?.preferences?.asSequence()?.map { HashAlgorithm.requireFromId(it) }?.toSet() + ?: setOf() +} + +fun PreferredAlgorithms?.toSymmetricKeyAlgorithms(): Set { + return this?.preferences?.asSequence()?.map { SymmetricKeyAlgorithm.requireFromId(it) }?.toSet() + ?: setOf() +} + +fun PreferredAlgorithms?.toCompressionAlgorithms(): Set { + return this?.preferences?.asSequence()?.map { CompressionAlgorithm.requireFromId(it) }?.toSet() + ?: setOf() +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt index 935c4f48..b16c5b3b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyAccessor.kt @@ -5,6 +5,7 @@ package org.pgpainless.key.info import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.algorithm.AEADCipherMode import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm @@ -40,6 +41,9 @@ abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: S get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) + val preferredAEADCipherSuites: Set + get() = SignatureSubpacketsUtil.parsePreferredAEADCipherSuites(signatureWithPreferences) + /** * Address the key via a user-id (e.g. `Alice `). In this case we are * sourcing preferred algorithms from the user-id certification first. diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt index 0d1480fa..35aa03e5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt @@ -5,6 +5,7 @@ package org.pgpainless.key.info import java.util.* +import kotlin.NoSuchElementException import openpgp.openPgpKeyId import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.* @@ -249,6 +250,11 @@ class KeyRingInfo( primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyIdentifier) + val preferredAEADCipherSuites: Set + get() = + primaryUserId?.let { getPreferredAEADCipherSuites(it) } + ?: getPreferredAEADCipherSuites(keyIdentifier) + /** * Return the expiration date of the subkey with the provided fingerprint. * @@ -649,70 +655,103 @@ class KeyRingInfo( /** [HashAlgorithm] preferences of the given user-ID. */ fun getPreferredHashAlgorithms(userId: CharSequence): Set { - return getKeyAccessor(userId, keyIdentifier).preferredHashAlgorithms + return keys + .getUserId(userId.toString()) + ?.getHashAlgorithmPreferences(referenceDate) + ?.toHashAlgorithms() + ?: throw NoSuchElementException("No user-id '$userId' found on this key.") } fun getPreferredHashAlgorithms(keyIdentifier: KeyIdentifier): Set { - return getPreferredHashAlgorithms(keyIdentifier.keyId) + return keys + .getKey(keyIdentifier) + ?.getHashAlgorithmPreferences(referenceDate) + ?.toHashAlgorithms() + ?: throw NoSuchElementException( + "No subkey with key-id $keyIdentifier found on this key.") } /** [HashAlgorithm] preferences of the given key. */ + @Deprecated("Pass KeyIdentifier instead.") fun getPreferredHashAlgorithms(keyId: Long): Set { - return KeyAccessor.SubKey(this, SubkeyIdentifier(keys.pgpKeyRing, keyId)) - .preferredHashAlgorithms + return getPreferredHashAlgorithms(KeyIdentifier(keyId)) } /** [SymmetricKeyAlgorithm] preferences of the given user-ID. */ fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set { - return getKeyAccessor(userId, keyIdentifier).preferredSymmetricKeyAlgorithms + return keys + .getUserId(userId.toString()) + ?.getSymmetricCipherPreferences(referenceDate) + ?.toSymmetricKeyAlgorithms() + ?: throw NoSuchElementException("No user-id '$userId' found on this key.") } fun getPreferredSymmetricKeyAlgorithms( keyIdentifier: KeyIdentifier ): Set { - return getPreferredSymmetricKeyAlgorithms(keyIdentifier.keyId) + return keys + .getKey(keyIdentifier) + ?.getSymmetricCipherPreferences(referenceDate) + ?.toSymmetricKeyAlgorithms() + ?: throw NoSuchElementException( + "No subkey with key-id $keyIdentifier found on this key.") } /** [SymmetricKeyAlgorithm] preferences of the given key. */ + @Deprecated("Pass KeyIdentifier instead.") fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set { - return KeyAccessor.SubKey(this, SubkeyIdentifier(keys.pgpKeyRing, keyId)) - .preferredSymmetricKeyAlgorithms + return getPreferredSymmetricKeyAlgorithms(KeyIdentifier(keyId)) } /** [CompressionAlgorithm] preferences of the given user-ID. */ fun getPreferredCompressionAlgorithms(userId: CharSequence): Set { - return getKeyAccessor(userId, keyIdentifier).preferredCompressionAlgorithms + return keys + .getUserId(userId.toString()) + ?.getCompressionAlgorithmPreferences(referenceDate) + ?.toCompressionAlgorithms() + ?: throw NoSuchElementException("No user-id '$userId' found on this key.") } fun getPreferredCompressionAlgorithms(keyIdentifier: KeyIdentifier): Set { - return getPreferredCompressionAlgorithms(keyIdentifier.keyId) + return keys + .getKey(keyIdentifier) + ?.getCompressionAlgorithmPreferences(referenceDate) + ?.toCompressionAlgorithms() + ?: throw NoSuchElementException( + "No subkey with key-id $keyIdentifier found on this key.") } /** [CompressionAlgorithm] preferences of the given key. */ fun getPreferredCompressionAlgorithms(keyId: Long): Set { - return KeyAccessor.SubKey(this, SubkeyIdentifier(keys.pgpKeyRing, keyId)) - .preferredCompressionAlgorithms + return getPreferredCompressionAlgorithms(KeyIdentifier(keyId)) + } + + fun getPreferredAEADCipherSuites(userId: CharSequence): Set { + return keys + .getUserId(userId.toString()) + ?.getAEADCipherSuitePreferences(referenceDate) + ?.toAEADCipherModes() + ?: throw NoSuchElementException("No user-id '$userId' found on this key.") + } + + fun getPreferredAEADCipherSuites(keyIdentifier: KeyIdentifier): Set { + return keys + .getKey(keyIdentifier) + ?.getAEADCipherSuitePreferences(referenceDate) + ?.toAEADCipherModes() + ?: throw NoSuchElementException( + "No subkey with key-id $keyIdentifier found on this key.") + } + + @Deprecated("Pass KeyIdentifier instead.") + fun getPreferredAEADCipherSuites(keyId: Long): Set { + return getPreferredAEADCipherSuites(KeyIdentifier(keyId)) } val isUsableForThirdPartyCertification: Boolean = isKeyValidlyBound(keyIdentifier) && getKeyFlagsOf(keyIdentifier).contains(KeyFlag.CERTIFY_OTHER) - private fun getKeyAccessor(userId: CharSequence?, keyIdentifier: KeyIdentifier): KeyAccessor { - if (getPublicKey(keyIdentifier) == null) { - throw NoSuchElementException("No subkey with key-id $keyIdentifier found on this key.") - } - if (userId != null && !userIds.contains(userId)) { - throw NoSuchElementException("No user-id '$userId' found on this key.") - } - return if (userId != null) { - KeyAccessor.ViaUserId( - this, SubkeyIdentifier(keys.pgpKeyRing, keyIdentifier.keyId), userId) - } else { - KeyAccessor.ViaKeyId(this, SubkeyIdentifier(keys.pgpKeyRing, keyIdentifier.keyId)) - } - } - companion object { /** Evaluate the key for the given signature. */ diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt index dcc85630..15ee2897 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpacketsUtil.kt @@ -269,6 +269,15 @@ class SignatureSubpacketsUtil { ?.toSet() ?: setOf() + @JvmStatic + fun parsePreferredAEADCipherSuites(signature: PGPSignature): Set = + getPreferredAeadAlgorithms(signature) + ?.algorithms + ?.asSequence() + ?.map { AEADCipherMode(it) } + ?.toSet() + ?: setOf() + @JvmStatic fun getPreferredAeadAlgorithms(signature: PGPSignature): PreferredAEADCiphersuites? = hashed(signature, SignatureSubpacket.preferredAEADAlgorithms)