From 02f6e37c4ff625f626a922c3fce1f23dcb2e5a6a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Jan 2024 18:47:27 +0100 Subject: [PATCH] Further progress --- .../key/generation/BaseOpenPgpKeyBuilder.kt | 28 +--- .../key/generation/OpenPgpKeyBuilder.kt | 153 ++++++++++-------- .../key/generation/OpenPgpKeyBuilderTest.kt | 7 +- 3 files changed, 96 insertions(+), 92 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/BaseOpenPgpKeyBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/BaseOpenPgpKeyBuilder.kt index b409ccdf..5d3540aa 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/BaseOpenPgpKeyBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/BaseOpenPgpKeyBuilder.kt @@ -68,11 +68,8 @@ class BaseOpenPgpKeyBuilder { class BaseV4PrimaryKeyBuilder(type: KeyType, creationTime: Date, policy: Policy) : BaseV4KeyBuilder(type, creationTime, policy = policy) { - internal fun isWithoutUserIds() = !key.publicKey.userIDs.hasNext() - fun userId( userId: CharSequence, - algorithmSuite: AlgorithmSuite = policy.keyGenerationAlgorithmSuite, certificationType: CertificationType = CertificationType.POSITIVE, bindingTime: Date = creationTime, hashAlgorithm: HashAlgorithm = @@ -81,12 +78,7 @@ class BaseOpenPgpKeyBuilder { ) = apply { val sig = buildCertificationFor( - userId, - algorithmSuite, - certificationType, - bindingTime, - hashAlgorithm, - subpacketsCallback) + userId, certificationType, bindingTime, hashAlgorithm, subpacketsCallback) key = PGPKeyPair( PGPPublicKey.addCertification(key.publicKey, userId.toString(), sig), @@ -95,7 +87,6 @@ class BaseOpenPgpKeyBuilder { fun buildCertificationFor( userId: CharSequence, - algorithmSuite: AlgorithmSuite, certificationType: CertificationType, bindingTime: Date, hashAlgorithm: HashAlgorithm, @@ -104,19 +95,13 @@ class BaseOpenPgpKeyBuilder { val builder = SelfSignatureBuilder( key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm) - builder.hashedSubpackets.apply { - setSignatureCreationTime(bindingTime) - setPreferredHashAlgorithms(algorithmSuite.hashAlgorithms) - setPreferredSymmetricKeyAlgorithms(algorithmSuite.symmetricKeyAlgorithms) - setPreferredCompressionAlgorithms(algorithmSuite.compressionAlgorithms) - } + builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) } builder.applyCallback(subpacketsCallback) return builder.build(userId) } fun userAttribute( userAttribute: PGPUserAttributeSubpacketVector, - algorithmSuite: AlgorithmSuite = policy.keyGenerationAlgorithmSuite, certificationType: CertificationType = CertificationType.POSITIVE, bindingTime: Date = creationTime, hashAlgorithm: HashAlgorithm = @@ -126,7 +111,6 @@ class BaseOpenPgpKeyBuilder { val sig = buildCertificationFor( userAttribute, - algorithmSuite, certificationType, bindingTime, hashAlgorithm, @@ -139,7 +123,6 @@ class BaseOpenPgpKeyBuilder { fun buildCertificationFor( userAttribute: PGPUserAttributeSubpacketVector, - algorithmSuite: AlgorithmSuite, certificationType: CertificationType, bindingTime: Date, hashAlgorithm: HashAlgorithm, @@ -148,12 +131,7 @@ class BaseOpenPgpKeyBuilder { val builder = SelfSignatureBuilder( key.privateKey, key.publicKey, certificationType.signatureType, hashAlgorithm) - builder.hashedSubpackets.apply { - setSignatureCreationTime(bindingTime) - setPreferredHashAlgorithms(algorithmSuite.hashAlgorithms) - setPreferredSymmetricKeyAlgorithms(algorithmSuite.symmetricKeyAlgorithms) - setPreferredCompressionAlgorithms(algorithmSuite.compressionAlgorithms) - } + builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) } builder.applyCallback(subpacketsCallback) return builder.build(userAttribute) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilder.kt index bcb21ac0..012aea80 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilder.kt @@ -17,79 +17,130 @@ import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.policy.Policy import org.pgpainless.signature.subpackets.SelfSignatureSubpackets +/** + * OpenPGP key builder. This implementation supersedes the old [KeyRingBuilder]. + * + * @param policy algorithm policy, which is consulted to determine suitable algorithms + * @param referenceTime reference time for key generation + * @param preferences set of preferred algorithms and enabled features + */ open class OpenPgpKeyBuilder( protected val policy: Policy, protected val referenceTime: Date = Date(), - protected val keyGenerationPolicy: AlgorithmSuite = policy.keyGenerationAlgorithmSuite + protected val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite ) { + /** + * Build an OpenPGP v4 key. The result will be a key compliant to RFC4880, RFC6637. + * + * @param keyType type of the primary key + * @param flags key flags for the primary key. Defaults to [KeyFlag.CERTIFY_OTHER]. + * @return [V4OpenPgpKeyBuilder] which can be further modified, e.g. add subkeys, user-ids etc. + */ fun buildV4Key( keyType: KeyType, - ): V4OpenPgpKeyBuilder = - V4OpenPgpKeyBuilder(keyType, policy, referenceTime, keyGenerationPolicy) + flags: List? = listOf(KeyFlag.CERTIFY_OTHER) + ): V4OpenPgpKeyBuilder = V4OpenPgpKeyBuilder(keyType, flags, policy, referenceTime, preferences) - class V4OpenPgpKeyBuilder( - keyType: KeyType, + /** + * Builder for version 4 OpenPGP keys. + * + * @param primaryKeyType type of the primary key + * @param primaryFlags list of key-flags for the primary key. Can be `null`. + * @param policy algorithm policy + * @param referenceTime reference time for key generation + * @param preferences set of algorithm preferences and enabled features for the key + */ + class V4OpenPgpKeyBuilder + internal constructor( + primaryKeyType: KeyType, + primaryFlags: List?, policy: Policy, referenceTime: Date, - keyGenerationPolicy: AlgorithmSuite - ) : OpenPgpKeyBuilder(policy, referenceTime, keyGenerationPolicy) { + preferences: AlgorithmSuite + ) : OpenPgpKeyBuilder(policy, referenceTime, preferences) { private val primaryKey = - BaseOpenPgpKeyBuilder.BaseV4PrimaryKeyBuilder(keyType, referenceTime, policy) - + BaseOpenPgpKeyBuilder.BaseV4PrimaryKeyBuilder(primaryKeyType, referenceTime, policy) private val subkeys = mutableListOf() + private val preferencesCallback = + SelfSignatureSubpackets.applyHashed { + setPreferredSymmetricKeyAlgorithms(preferences.symmetricKeyAlgorithms) + setPreferredHashAlgorithms(preferences.hashAlgorithms) + setPreferredCompressionAlgorithms(preferences.compressionAlgorithms) + setFeatures(*preferences.features.toTypedArray()) + primaryFlags?.let { setKeyFlags(*it.toTypedArray()) } + } + + /** + * Add a user-id to the key. The subpackets of the binding signature are prepopulated, + * setting algorithm preferences and features. However, the subpackets can still be modified + * using the provided [subpacketsCallback]. + * + * @param userId user-id to add + * @param subpacketsCallback callback to modify the user-id binding signatures subpackets. + * @return this + */ fun addUserId( userId: CharSequence, - algorithmSuite: AlgorithmSuite = keyGenerationPolicy, subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop() ) = apply { - primaryKey.userId(userId, algorithmSuite, subpacketsCallback = subpacketsCallback) + primaryKey.userId( + userId, subpacketsCallback = preferencesCallback.then(subpacketsCallback)) } + /** + * Add a user-attribute to the key. The subpackets of the binding signature are + * prepopulated, setting algorithm preferences and features. However, the subpackets can + * still be modified using the provided [subpacketsCallback]. + * + * @param attribute user-attribute to add + * @param subpacketsCallback callback to modify the user-attribute binding signatures + * subpackets. + * @return this + */ fun addUserAttribute( attribute: PGPUserAttributeSubpacketVector, - algorithmSuite: AlgorithmSuite = keyGenerationPolicy, subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop() ) = apply { primaryKey.userAttribute( - attribute, algorithmSuite, subpacketsCallback = subpacketsCallback) + attribute, subpacketsCallback = preferencesCallback.then(subpacketsCallback)) } + /** + * Add a subkey to the key. The subpackets of the binding signature will be populated with + * issuer information, the passed in [bindingTime] as signature creation time and given + * key-flags (if non-null). You can further manipulate the subpackets by passing in an + * appropriate [subpacketsCallback]. + * + * @param keyType type of the key + * @param creationTime creation time of the key. Defaults to [referenceTime] + * @param bindingTime creation time of the binding signature. Defaults to [creationTime] + * @param keyFlags list of key-flags for the subkey. + * @param subpacketsCallback callback to modify the subpackets of the binding signature + */ fun addSubkey( keyType: KeyType, creationTime: Date = referenceTime, bindingTime: Date = creationTime, - keyFlags: List? + keyFlags: List? = null, + subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop() ) = addSubkey( BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder( keyType, creationTime, policy, primaryKey), - bindingTime, - keyFlags) + SelfSignatureSubpackets.applyHashed { + setSignatureCreationTime(bindingTime) + keyFlags?.let { setKeyFlags(it) } + } + .then(subpacketsCallback)) fun addSubkey( subkeyBuilder: BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder, - bindingTime: Date = subkeyBuilder.creationTime, - keyFlags: List? + subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop() ) = apply { - subkeys.add( - subkeyBuilder.also { - it.bindingSignature( - bindingTime, - subpacketsCallback = - object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets( - hashedSubpackets: SelfSignatureSubpackets - ) { - hashedSubpackets.setSignatureCreationTime(bindingTime) - keyFlags?.let { flagList -> - hashedSubpackets.setKeyFlags(flagList) - } - } - }) - }) + subkeys.add(subkeyBuilder.bindingSignature(subpacketsCallback = subpacketsCallback)) } fun addEncryptionSubkey( @@ -113,44 +164,18 @@ open class OpenPgpKeyBuilder( protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() ): PGPSecretKeyRing { - // Add DK sig in case of no user-id - if (primaryKey.isWithoutUserIds()) { - primaryKey.directKeySignature( - subpacketsCallback = defaultPrimarySubpacketsCallback()) - } + // add a direct key sig with preferences + primaryKey.directKeySignature(subpacketsCallback = preferencesCallback) return PGPSecretKeyRing( mutableListOf( - PGPSecretKey( - primaryKey.key.privateKey, - primaryKey.key.publicKey, - ImplementationFactory.getInstance().v4FingerprintCalculator, - true, - protector.getEncryptor(primaryKey.key.keyID))) + toSecretKey(primaryKey, true, protector.getEncryptor(primaryKey.key.keyID))) .plus( subkeys.map { - PGPSecretKey( - it.key.privateKey, - it.key.publicKey, - ImplementationFactory.getInstance().v4FingerprintCalculator, - false, - protector.getEncryptor(it.key.keyID)) + toSecretKey(it, false, protector.getEncryptor(it.key.keyID)) })) } - private fun defaultPrimarySubpacketsCallback(): SelfSignatureSubpackets.Callback = - object : SelfSignatureSubpackets.Callback { - override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { - hashedSubpackets.apply { - setPreferredHashAlgorithms(keyGenerationPolicy.hashAlgorithms) - setPreferredSymmetricKeyAlgorithms( - keyGenerationPolicy.symmetricKeyAlgorithms) - setPreferredCompressionAlgorithms(keyGenerationPolicy.compressionAlgorithms) - setKeyFlags(KeyFlag.CERTIFY_OTHER) - } - } - } - private fun toSecretKey( key: BaseOpenPgpKeyBuilder.BaseV4KeyBuilder<*>, isPrimaryKey: Boolean, diff --git a/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilderTest.kt b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilderTest.kt index a3a581dd..5c5c3338 100644 --- a/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilderTest.kt +++ b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyBuilderTest.kt @@ -4,6 +4,7 @@ import org.bouncycastle.bcpg.attr.ImageAttribute import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator import org.junit.jupiter.api.Test import org.pgpainless.PGPainless +import org.pgpainless.algorithm.KeyFlag import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.eddsa.EdDSACurve import org.pgpainless.key.generation.type.xdh.XDHSpec @@ -18,7 +19,7 @@ class OpenPgpKeyBuilderTest { val date = DateUtil.parseUTCDate("2020-04-01 10:00:00 UTC") val key = OpenPgpKeyBuilder(Policy.getInstance(), date) - .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519)) + .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) .addUserId("Alice") .addUserAttribute( PGPUserAttributeSubpacketVectorGenerator() @@ -34,7 +35,7 @@ class OpenPgpKeyBuilderTest { fun minimal() { val key = OpenPgpKeyBuilder(Policy.getInstance()) - .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519)) + .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) .build() println(PGPainless.asciiArmor(key)) } @@ -43,7 +44,7 @@ class OpenPgpKeyBuilderTest { fun minimalWithUserId() { val key = OpenPgpKeyBuilder(Policy.getInstance()) - .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519)) + .buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) .addUserId("Alice ") .build() println(PGPainless.asciiArmor(key))