From 7a33e84497f4a3adfe3b72bfba0645a62a87e49b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 5 May 2025 10:58:24 +0200 Subject: [PATCH] Rework KeyAccessor --- .../pgpainless/algorithm/AlgorithmSuite.kt | 91 ++++++++++++++++--- .../OpenPGPKeyGeneratorExtensions.kt | 4 +- .../MessageMetadata.kt | 16 ++-- .../encryption_signing/EncryptionOptions.kt | 8 +- .../key/generation/KeyRingBuilder.kt | 75 +++++++++++---- .../key/generation/KeyRingBuilderInterface.kt | 3 + .../org/pgpainless/key/generation/KeySpec.kt | 19 ++-- .../key/generation/KeySpecBuilder.kt | 37 +++----- .../org/pgpainless/key/info/KeyAccessor.kt | 83 +++++++---------- .../secretkeyring/SecretKeyRingEditor.kt | 20 +++- .../EncryptDecryptTest.java | 17 ++-- .../util/GuessPreferredHashAlgorithmTest.java | 13 ++- 12 files changed, 247 insertions(+), 139 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt index 801e674c..71a618a2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt @@ -4,22 +4,91 @@ package org.pgpainless.algorithm -class AlgorithmSuite( - symmetricKeyAlgorithms: List?, - hashAlgorithms: List?, - compressionAlgorithms: List?, - aeadAlgorithms: List?, - features: List +class AlgorithmSuite +private constructor( + val symmetricKeyAlgorithms: Set?, + val hashAlgorithms: Set?, + val compressionAlgorithms: Set?, + val aeadAlgorithms: Set?, + val features: Set? ) { - val symmetricKeyAlgorithms: Set? = symmetricKeyAlgorithms?.toSet() - val hashAlgorithms: Set? = hashAlgorithms?.toSet() - val compressionAlgorithms: Set? = compressionAlgorithms?.toSet() - val aeadAlgorithms: Set? = aeadAlgorithms?.toSet() - val features: Set = features.toSet() + constructor( + symmetricKeyAlgorithms: List?, + hashAlgorithms: List?, + compressionAlgorithms: List?, + aeadAlgorithms: List?, + features: List? + ) : this( + symmetricKeyAlgorithms?.toSet(), + hashAlgorithms?.toSet(), + compressionAlgorithms?.toSet(), + aeadAlgorithms?.toSet(), + features?.toSet()) + + fun modify(): Builder = Builder(this) + + class Builder(suite: AlgorithmSuite? = null) { + private var symmetricKeyAlgorithms: Set? = + suite?.symmetricKeyAlgorithms + private var hashAlgorithms: Set? = suite?.hashAlgorithms + private var compressionAlgorithms: Set? = suite?.compressionAlgorithms + private var aeadAlgorithms: Set? = suite?.aeadAlgorithms + private var features: Set? = suite?.features + + fun overrideSymmetricKeyAlgorithms( + vararg symmetricKeyAlgorithms: SymmetricKeyAlgorithm + ): Builder = overrideSymmetricKeyAlgorithms(symmetricKeyAlgorithms.toSet()) + + fun overrideSymmetricKeyAlgorithms( + symmetricKeyAlgorithms: Collection? + ): Builder = apply { this.symmetricKeyAlgorithms = symmetricKeyAlgorithms?.toSet() } + + fun overrideHashAlgorithms(vararg hashAlgorithms: HashAlgorithm): Builder = + overrideHashAlgorithms(hashAlgorithms.toSet()) + + fun overrideHashAlgorithms(hashAlgorithms: Collection?): Builder = apply { + this.hashAlgorithms = hashAlgorithms?.toSet() + } + + fun overrideCompressionAlgorithms( + vararg compressionAlgorithms: CompressionAlgorithm + ): Builder = overrideCompressionAlgorithms(compressionAlgorithms.toSet()) + + fun overrideCompressionAlgorithms( + compressionAlgorithms: Collection? + ): Builder = apply { this.compressionAlgorithms = compressionAlgorithms?.toSet() } + + fun overrideAeadAlgorithms(vararg aeadAlgorithms: AEADCipherMode): Builder = + overrideAeadAlgorithms(aeadAlgorithms.toSet()) + + fun overrideAeadAlgorithms(aeadAlgorithms: Collection?): Builder = apply { + this.aeadAlgorithms = aeadAlgorithms?.toSet() + } + + fun overrideFeatures(vararg features: Feature): Builder = overrideFeatures(features.toSet()) + + fun overrideFeatures(features: Collection?): Builder = apply { + this.features = features?.toSet() + } + + fun build(): AlgorithmSuite { + return AlgorithmSuite( + symmetricKeyAlgorithms, + hashAlgorithms, + compressionAlgorithms, + aeadAlgorithms, + features) + } + } companion object { + @JvmStatic + fun emptyBuilder(): Builder { + return Builder() + } + @JvmStatic val defaultSymmetricKeyAlgorithms = listOf( diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyGeneratorExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyGeneratorExtensions.kt index 05a812d0..c085c08d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyGeneratorExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyGeneratorExtensions.kt @@ -32,10 +32,10 @@ fun OpenPGPKeyGenerator.setAlgorithmSuite(algorithms: AlgorithmSuite): OpenPGPKe fun OpenPGPKeyGenerator.setDefaultFeatures( critical: Boolean = true, - features: Set + features: Set? ): OpenPGPKeyGenerator { this.setDefaultFeatures { - val b = Feature.toBitmask(*features.toTypedArray()) + val b = features?.let { f -> Feature.toBitmask(*f.toTypedArray()) } ?: 0 it.apply { setFeature(critical, b) } } return this diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt index d36159ee..bb96e117 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -33,9 +33,9 @@ class MessageMetadata(val message: Message) { * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is * unencrypted. */ - @Deprecated("Deprecated in favor of encryptionMechanism", - replaceWith = ReplaceWith("encryptionMechanism") - ) + @Deprecated( + "Deprecated in favor of encryptionMechanism", + replaceWith = ReplaceWith("encryptionMechanism")) val encryptionAlgorithm: SymmetricKeyAlgorithm? get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null } @@ -48,9 +48,9 @@ class MessageMetadata(val message: Message) { * item that of the next nested encrypted data packet and so on. The iterator might also be * empty, in case of an unencrypted message. */ - @Deprecated("Deprecated in favor of encryptionMechanisms", - replaceWith = ReplaceWith("encryptionMechanisms") - ) + @Deprecated( + "Deprecated in favor of encryptionMechanisms", + replaceWith = ReplaceWith("encryptionMechanisms")) val encryptionAlgorithms: Iterator get() = encryptionLayers.asSequence().map { it.algorithm }.iterator() @@ -60,7 +60,9 @@ class MessageMetadata(val message: Message) { val isEncrypted: Boolean get() = if (encryptionMechanism == null) false - else encryptionMechanism!!.symmetricKeyAlgorithm != SymmetricKeyAlgorithm.NULL.algorithmId + else + encryptionMechanism!!.symmetricKeyAlgorithm != + SymmetricKeyAlgorithm.NULL.algorithmId fun isEncryptedFor(cert: OpenPGPCertificate): Boolean { return encryptionLayers.asSequence().any { 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 d6aef95d..7dd6b3fb 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 @@ -24,7 +24,6 @@ import org.pgpainless.util.Passphrase class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: PGPainless) { private val _encryptionMethods: MutableSet = mutableSetOf() private val _encryptionKeys: MutableSet = mutableSetOf() - private val _encryptionKeyIdentifiers: MutableSet = mutableSetOf() private val _keyRingInfo: MutableMap = mutableMapOf() private val _keyViews: MutableMap = mutableMapOf() private val encryptionKeySelector: EncryptionKeySelector = encryptToAllCapableSubkeys() @@ -37,7 +36,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: get() = _encryptionMethods.toSet() val encryptionKeyIdentifiers - get() = _encryptionKeyIdentifiers.toSet() + get() = _encryptionKeys.map { SubkeyIdentifier(it) } val encryptionKeys get() = _encryptionKeys.toSet() @@ -201,7 +200,7 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: for (subkey in subkeys) { val keyId = SubkeyIdentifier(subkey) _keyRingInfo[keyId] = info - _keyViews[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString()) + _keyViews[keyId] = KeyAccessor.ViaUserId(subkey, cert.getUserId(userId.toString())) addRecipientKey(subkey, false) } } @@ -320,14 +319,13 @@ class EncryptionOptions(private val purpose: EncryptionPurpose, private val api: for (subkey in encryptionSubkeys) { val keyId = SubkeyIdentifier(subkey) _keyRingInfo[keyId] = info - _keyViews[keyId] = KeyAccessor.ViaKeyId(info, keyId) + _keyViews[keyId] = KeyAccessor.ViaKeyIdentifier(subkey) addRecipientKey(subkey, wildcardKeyId) } } private fun addRecipientKey(key: OpenPGPComponentKey, wildcardRecipient: Boolean) { _encryptionKeys.add(key) - _encryptionKeyIdentifiers.add(SubkeyIdentifier(key)) addEncryptionMethod( api.implementation.publicKeyKeyEncryptionMethodGenerator(key.pgpPublicKey).also { it.setUseWildcardRecipient(wildcardRecipient) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index 6d6481bd..2f25fc7a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -14,6 +14,7 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder import org.bouncycastle.util.Strings import org.pgpainless.PGPainless +import org.pgpainless.algorithm.AlgorithmSuite import org.pgpainless.algorithm.KeyFlag import org.pgpainless.algorithm.OpenPGPKeyVersion import org.pgpainless.algorithm.SignatureType @@ -33,6 +34,11 @@ class KeyRingBuilder(private val version: OpenPGPKeyVersion, private val api: PG private val userIds = mutableMapOf() private var passphrase = Passphrase.emptyPassphrase() private var expirationDate: Date? = Date(System.currentTimeMillis() + (5 * MILLIS_IN_YEAR)) + private var algorithmSuite: AlgorithmSuite = api.algorithmPolicy.keyGenerationAlgorithmSuite + + override fun withPreferences(preferences: AlgorithmSuite): KeyRingBuilder = apply { + algorithmSuite = preferences + } override fun setPrimaryKey(keySpec: KeySpec): KeyRingBuilder = apply { verifyKeySpecCompliesToPolicy(keySpec, api.algorithmPolicy) @@ -95,14 +101,32 @@ class KeyRingBuilder(private val version: OpenPGPKeyVersion, private val api: PG val signer = buildContentSigner(certKey) val signatureGenerator = PGPSignatureGenerator(signer, certKey.publicKey) - val hashedSubPacketGenerator = primaryKeySpec!!.subpacketGenerator - hashedSubPacketGenerator.setAppropriateIssuerInfo(certKey.publicKey, version) - expirationDate?.let { hashedSubPacketGenerator.setKeyExpirationTime(certKey.publicKey, it) } + val hashedSignatureSubpackets: SignatureSubpackets = + SignatureSubpackets.createHashedSubpackets(certKey.publicKey).apply { + setKeyFlags(primaryKeySpec!!.keyFlags) + (primaryKeySpec!!.preferredHashAlgorithmsOverride ?: algorithmSuite.hashAlgorithms) + ?.let { setPreferredHashAlgorithms(it) } + (primaryKeySpec!!.preferredCompressionAlgorithmsOverride + ?: algorithmSuite.compressionAlgorithms) + ?.let { setPreferredCompressionAlgorithms(it) } + (primaryKeySpec!!.preferredSymmetricAlgorithmsOverride + ?: algorithmSuite.symmetricKeyAlgorithms) + ?.let { setPreferredSymmetricKeyAlgorithms(it) } + (primaryKeySpec!!.preferredAEADAlgorithmsOverride ?: algorithmSuite.aeadAlgorithms) + ?.let { setPreferredAEADCiphersuites(it) } + (primaryKeySpec!!.featuresOverride ?: algorithmSuite.features)?.let { + setFeatures(*it.toTypedArray()) + } + } + + expirationDate?.let { + hashedSignatureSubpackets.setKeyExpirationTime(certKey.publicKey, it) + } if (userIds.isNotEmpty()) { - hashedSubPacketGenerator.setPrimaryUserId() + hashedSignatureSubpackets.setPrimaryUserId() } - val hashedSubPackets = hashedSubPacketGenerator.subpacketsGenerator.generate() + val hashedSubPackets = hashedSignatureSubpackets.subpacketsGenerator.generate() val ringGenerator = if (userIds.isEmpty()) { PGPKeyRingGenerator( @@ -138,7 +162,7 @@ class KeyRingBuilder(private val version: OpenPGPKeyVersion, private val api: PG val callback = additionalUserId.value val subpackets = if (callback == null) { - hashedSubPacketGenerator.also { it.setPrimaryUserId(null) } + hashedSignatureSubpackets.also { it.setPrimaryUserId(null) } } else { SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) @@ -167,21 +191,34 @@ class KeyRingBuilder(private val version: OpenPGPKeyVersion, private val api: PG private fun addSubKeys(primaryKey: PGPKeyPair, ringGenerator: PGPKeyRingGenerator) { for (subKeySpec in subKeySpecs) { val subKey = generateKeyPair(subKeySpec, version, api.implementation) - if (subKeySpec.isInheritedSubPackets) { - ringGenerator.addSubKey(subKey) - } else { - var hashedSubpackets = subKeySpec.subpackets - try { - hashedSubpackets = - addPrimaryKeyBindingSignatureIfNecessary( - primaryKey, subKey, hashedSubpackets) - } catch (e: IOException) { - throw PGPException( - "Exception while adding primary key binding signature to signing subkey.", - e) + var hashedSignatureSubpackets: SignatureSubpackets = + SignatureSubpackets.createHashedSubpackets(subKey.publicKey).apply { + setKeyFlags(subKeySpec.keyFlags) + subKeySpec.preferredHashAlgorithmsOverride?.let { + setPreferredHashAlgorithms(it) + } + subKeySpec.preferredCompressionAlgorithmsOverride?.let { + setPreferredCompressionAlgorithms(it) + } + subKeySpec.preferredSymmetricAlgorithmsOverride?.let { + setPreferredSymmetricKeyAlgorithms(it) + } + subKeySpec.preferredAEADAlgorithmsOverride?.let { + setPreferredAEADCiphersuites(it) + } + subKeySpec.featuresOverride?.let { setFeatures(*it.toTypedArray()) } } - ringGenerator.addSubKey(subKey, hashedSubpackets, null) + + var hashedSubpackets: PGPSignatureSubpacketVector = + hashedSignatureSubpackets.subpacketsGenerator.generate() + try { + hashedSubpackets = + addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets) + } catch (e: IOException) { + throw PGPException( + "Exception while adding primary key binding signature to signing subkey.", e) } + ringGenerator.addSubKey(subKey, hashedSubpackets, null) } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt index 8004d9a3..3bf95996 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt @@ -9,10 +9,13 @@ import java.security.NoSuchAlgorithmException import java.util.* import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.api.OpenPGPKey +import org.pgpainless.algorithm.AlgorithmSuite import org.pgpainless.util.Passphrase interface KeyRingBuilderInterface> { + fun withPreferences(preferences: AlgorithmSuite): B + fun setPrimaryKey(keySpec: KeySpec): B fun setPrimaryKey(builder: KeySpecBuilder): B = setPrimaryKey(builder.build()) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt index f616a7f2..66f5f9e0 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpec.kt @@ -5,22 +5,25 @@ package org.pgpainless.key.generation import java.util.* -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector +import org.pgpainless.algorithm.AEADCipherMode +import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.Feature +import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.key.generation.type.KeyType -import org.pgpainless.signature.subpackets.SignatureSubpackets -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper data class KeySpec( val keyType: KeyType, - val subpacketGenerator: SignatureSubpackets, - val isInheritedSubPackets: Boolean, + val keyFlags: List, + val preferredCompressionAlgorithmsOverride: Set?, + val preferredHashAlgorithmsOverride: Set?, + val preferredSymmetricAlgorithmsOverride: Set?, + val preferredAEADAlgorithmsOverride: Set?, + val featuresOverride: Set?, val keyCreationDate: Date? ) { - val subpackets: PGPSignatureSubpacketVector - get() = SignatureSubpacketsHelper.toVector(subpacketGenerator) - companion object { @JvmStatic fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt index 21cf5ec0..59d9efe7 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt @@ -5,11 +5,8 @@ package org.pgpainless.key.generation import java.util.* -import org.pgpainless.PGPainless import org.pgpainless.algorithm.* import org.pgpainless.key.generation.type.KeyType -import org.pgpainless.signature.subpackets.SelfSignatureSubpackets -import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil class KeySpecBuilder( @@ -17,16 +14,11 @@ class KeySpecBuilder( private val keyFlags: List, ) : KeySpecBuilderInterface { - private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets() - private val algorithmSuite: AlgorithmSuite = - PGPainless.getInstance().algorithmPolicy.keyGenerationAlgorithmSuite - private var preferredCompressionAlgorithms: Set? = - algorithmSuite.compressionAlgorithms - private var preferredHashAlgorithms: Set? = algorithmSuite.hashAlgorithms - private var preferredSymmetricAlgorithms: Set? = - algorithmSuite.symmetricKeyAlgorithms - private var preferredAEADAlgorithms: Set? = algorithmSuite.aeadAlgorithms - private var features: Set? = algorithmSuite.features + private var preferredCompressionAlgorithms: Set? = null + private var preferredHashAlgorithms: Set? = null + private var preferredSymmetricAlgorithms: Set? = null + private var preferredAEADAlgorithms: Set? = null + private var features: Set? = null private var keyCreationDate: Date? = null constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags)) @@ -70,15 +62,14 @@ class KeySpecBuilder( } override fun build(): KeySpec { - return hashedSubpackets - .apply { - setKeyFlags(keyFlags) - preferredCompressionAlgorithms?.let { setPreferredCompressionAlgorithms(it) } - preferredHashAlgorithms?.let { setPreferredHashAlgorithms(it) } - preferredSymmetricAlgorithms?.let { setPreferredSymmetricKeyAlgorithms(it) } - preferredAEADAlgorithms?.let { setPreferredAEADCiphersuites(it) } - features?.let { setFeatures(*it.toTypedArray()) } - } - .let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) } + return KeySpec( + type, + keyFlags, + preferredCompressionAlgorithms, + preferredHashAlgorithms, + preferredSymmetricAlgorithms, + preferredAEADAlgorithms, + features, + keyCreationDate) } } 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 735d4490..a5c2627e 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 @@ -4,80 +4,67 @@ package org.pgpainless.key.info -import org.bouncycastle.openpgp.PGPSignature +import java.util.* +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPCertificateComponent +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPIdentityComponent import org.pgpainless.algorithm.AEADCipherMode import org.pgpainless.algorithm.CompressionAlgorithm +import org.pgpainless.algorithm.Feature import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm -import org.pgpainless.key.SubkeyIdentifier -import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil +import org.pgpainless.bouncycastle.extensions.toAEADCipherModes +import org.pgpainless.bouncycastle.extensions.toCompressionAlgorithms +import org.pgpainless.bouncycastle.extensions.toHashAlgorithms +import org.pgpainless.bouncycastle.extensions.toSymmetricKeyAlgorithms -abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: SubkeyIdentifier) { +abstract class KeyAccessor( + protected val key: OpenPGPComponentKey, + private val referenceTime: Date +) { - /** - * Depending on the way we address the key (key-id or user-id), return the respective - * [PGPSignature] which contains the algorithm preferences we are going to use. - * - *

- * If we address a key via its user-id, we want to rely on the algorithm preferences in the - * user-id certification, while we would instead rely on those in the direct-key signature if - * we'd address the key by key-id. - * - * @return signature - */ - abstract val signatureWithPreferences: PGPSignature + abstract val component: OpenPGPCertificateComponent - /** Preferred symmetric key encryption algorithms. */ val preferredSymmetricKeyAlgorithms: Set get() = - SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences) + component.getSymmetricCipherPreferences(referenceTime)?.toSymmetricKeyAlgorithms() + ?: setOf() - /** Preferred hash algorithms. */ val preferredHashAlgorithms: Set - get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences) + get() = component.getHashAlgorithmPreferences(referenceTime)?.toHashAlgorithms() ?: setOf() - /** Preferred compression algorithms. */ val preferredCompressionAlgorithms: Set get() = - SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) + component.getCompressionAlgorithmPreferences(referenceTime)?.toCompressionAlgorithms() + ?: setOf() - /** Preferred AEAD algorithm suites. */ val preferredAEADCipherSuites: Set - get() = SignatureSubpacketsUtil.parsePreferredAEADCipherSuites(signatureWithPreferences) + get() = + component.getAEADCipherSuitePreferences(referenceTime)?.toAEADCipherModes() ?: setOf() + + val features: Set + get() = + Feature.fromBitmask(component.getFeatures(referenceTime)?.features?.toInt() ?: 0) + .toSet() /** * Address the key via a user-id (e.g. `Alice `). In this case we are * sourcing preferred algorithms from the user-id certification first. */ - class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence) : - KeyAccessor(info, key) { - override val signatureWithPreferences: PGPSignature - get() = - checkNotNull(info.getLatestUserIdCertification(userId.toString())) { - "No valid user-id certification signature found for '$userId'." - } + class ViaUserId( + key: OpenPGPComponentKey, + userId: OpenPGPIdentityComponent, + referenceTime: Date = Date() + ) : KeyAccessor(key, referenceTime) { + override val component: OpenPGPCertificateComponent = userId } /** * Address the key via key-id. In this case we are sourcing preferred algorithms from the keys * direct-key signature first. */ - class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) { - override val signatureWithPreferences: PGPSignature - get() { - if (key.isPrimaryKey) { - // If the key is located by Key ID, the algorithm of the primary User ID of the - // key - // provides the - // preferred symmetric algorithm. - info.primaryUserId?.let { userId -> - info.getLatestUserIdCertification(userId).let { if (it != null) return it } - } - } - - return info.getCurrentSubkeyBindingSignature(key.keyIdentifier) - ?: throw NoSuchElementException( - "Key does not carry acceptable self-signature signature.") - } + class ViaKeyIdentifier(key: OpenPGPComponentKey, referenceTime: Date = Date()) : + KeyAccessor(key, referenceTime) { + override val component: OpenPGPCertificateComponent = key } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt index cece7657..8cf5338d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.kt @@ -250,8 +250,22 @@ class SecretKeyRingEditor( val callback = object : SelfSignatureSubpackets.Callback { override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - SignatureSubpacketsHelper.applyFrom( - keySpec.subpackets, hashedSubpackets as SignatureSubpackets) + hashedSubpackets.apply { + setKeyFlags(keySpec.keyFlags) + keySpec.preferredHashAlgorithmsOverride?.let { + setPreferredHashAlgorithms(it) + } + keySpec.preferredCompressionAlgorithmsOverride?.let { + setPreferredCompressionAlgorithms(it) + } + keySpec.preferredSymmetricAlgorithmsOverride?.let { + setPreferredSymmetricKeyAlgorithms(it) + } + keySpec.preferredAEADAlgorithmsOverride?.let { + setPreferredAEADCiphersuites(it) + } + keySpec.featuresOverride?.let { setFeatures(*it.toTypedArray()) } + } hashedSubpackets.setSignatureCreationTime(referenceTime) } } @@ -268,7 +282,7 @@ class SecretKeyRingEditor( val keyPair = KeyRingBuilder.generateKeyPair(keySpec, version, api.implementation) val subkeyProtector = PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyIdentifier, subkeyPassphrase) - val keyFlags = KeyFlag.fromBitmask(keySpec.subpackets.keyFlags).toMutableList() + val keyFlags = keySpec.keyFlags.toMutableList() return addSubKey( keyPair, callback, diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java index 8f8b2b67..b5713345 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.AEADAlgorithm; import org.pgpainless.algorithm.AEADCipherMode; +import org.pgpainless.algorithm.AlgorithmSuite; import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.Feature; import org.pgpainless.algorithm.KeyFlag; @@ -323,9 +324,11 @@ public class EncryptDecryptTest { public void testEncryptToOnlyV4CertWithOnlySEIPD1Feature() throws PGPException, IOException { PGPainless api = PGPainless.getInstance(); OpenPGPKey v4Key = api.buildKey(OpenPGPKeyVersion.v4) - .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA) - .overridePreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192) - .overrideFeatures(Feature.MODIFICATION_DETECTION)) // the key only supports SEIPD1 + .withPreferences(AlgorithmSuite.emptyBuilder() + .overrideSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192) + .overrideFeatures(Feature.MODIFICATION_DETECTION) + .build()) + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) .addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) .build(); @@ -358,9 +361,11 @@ public class EncryptDecryptTest { public void testEncryptToOnlyV6CertWithOnlySEIPD2Features() throws IOException, PGPException { PGPainless api = PGPainless.getInstance(); OpenPGPKey v6Key = api.buildKey(OpenPGPKeyVersion.v6) - .setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA) - .overridePreferredAEADAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128)) - .overrideFeatures(Feature.MODIFICATION_DETECTION_2)) // the key only supports SEIPD2 + .withPreferences(AlgorithmSuite.emptyBuilder() + .overrideFeatures(Feature.MODIFICATION_DETECTION_2) + .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_192)) + .build()) + .setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) .addSubkey(KeySpec.getBuilder(KeyType.X25519(), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) .build(); diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/GuessPreferredHashAlgorithmTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/GuessPreferredHashAlgorithmTest.java index b6ebd8a2..136f0eef 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/GuessPreferredHashAlgorithmTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/GuessPreferredHashAlgorithmTest.java @@ -12,10 +12,10 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; +import org.pgpainless.algorithm.AlgorithmSuite; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.algorithm.OpenPGPKeyVersion; import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve; @@ -25,12 +25,11 @@ public class GuessPreferredHashAlgorithmTest { @Test public void guessPreferredHashAlgorithmsAssumesHashAlgoUsedBySelfSig() { - PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing() + PGPainless api = PGPainless.getInstance(); + PGPSecretKeyRing secretKeys = api.buildKey(OpenPGPKeyVersion.v4) + .withPreferences(AlgorithmSuite.emptyBuilder().build()) .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), - KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA) - .overridePreferredHashAlgorithms(new HashAlgorithm[] {}) - .overridePreferredSymmetricKeyAlgorithms(new SymmetricKeyAlgorithm[] {}) - .overridePreferredCompressionAlgorithms(new CompressionAlgorithm[] {})) + KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) .addUserId("test@test.test") .build() .getPGPSecretKeyRing();