diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyGenerator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyGenerator.kt new file mode 100644 index 00000000..5fe5a0ca --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyGenerator.kt @@ -0,0 +1,569 @@ +package org.pgpainless.key.generation + +import org.bouncycastle.openpgp.PGPKeyPair +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSecretKey +import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector +import org.pgpainless.PGPainless +import org.pgpainless.algorithm.AlgorithmSuite +import org.pgpainless.algorithm.CertificationType +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.KeyFlag +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.eddsa.EdDSACurve +import org.pgpainless.key.generation.type.xdh.XDHSpec +import org.pgpainless.policy.Policy +import org.pgpainless.provider.ProviderFactory +import org.pgpainless.signature.builder.DirectKeySelfSignatureBuilder +import org.pgpainless.signature.builder.SelfSignatureBuilder +import org.pgpainless.signature.builder.SubkeyBindingSignatureBuilder +import org.pgpainless.signature.subpackets.SelfSignatureSubpackets +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.util.Date + +/** + * Build a version 4 OpenPGP secret key. + * + * @param policy policy to ensure algorithm compliance and to determine default algorithms + * @param creationTime creation time for the secret key + */ +fun buildV4( + policy: Policy = PGPainless.getPolicy(), + creationTime: Date = Date(), + preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite +): OpinionatedDefinePrimaryKey.V4 { + return OpinionatedDefinePrimaryKey.V4(policy, creationTime, preferences) +} + +/** + * Build a version 6 OpenPGP secret key. + * + * @param policy policy to ensure algorithm compliance and to determine default algorithms + * @param creationTime creation time for the secret key + */ +fun buildV6( + policy: Policy = PGPainless.getPolicy(), + creationTime: Date = Date(), + preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite +): OpinionatedDefinePrimaryKey.V6 { + return OpinionatedDefinePrimaryKey.V6(policy, creationTime, preferences) +} + +/** + * Builder that allows the user to define the primary key of the OpenPGP key. + * + * @param policy algorithm policy + * @param creationTime default value for the creation time of the primary key. + */ +abstract class DefinePrimaryKey>( + val policy: Policy, + val creationTime: Date, +) { + + /** + * Define the primary key of the OpenPGP key. + * + * @param type primary key type + * @param creationTime creation time of the primary key. + * Defaults to the [DefinePrimaryKey]'s [creationTime]. + * @param applyToPrimaryKey function that gets applied to the primary key. + * Is used to add binding signatures, UserIDs and user-attributes on the primary key. + * + * @return next step key builder + */ + abstract fun setPrimaryKey( + type: KeyType, + creationTime: Date = this.creationTime, + applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? = null // default: do nothing + ): B +} + +/** + * Opinionated implementation of [DefinePrimaryKey]. + * Contrary to an unopinionated implementation, an opinionated [DefinePrimaryKey] will + * sanity check user-provided parameters and make sure, required signatures are placed + * on the key etc. + * + * @param policy policy to sanity check algorithms, key strengths etc. and to determine + * fallback algorithms with + * @param creationTime creation time of the primary key + * @param unopinionated unopinionated implementation + * @param B opinionated builder type + * @param U unopinionated builder type + */ +abstract class OpinionatedDefinePrimaryKey( + policy: Policy, + creationTime: Date, + protected val unopinionated: UnopinionatedDefinePrimaryKey +) : DefinePrimaryKey(policy, creationTime) { + + /** + * Turn this builder into an unopinionated one by returning the underlying unopinionated + * implementation. + * + * @return unopinionated implementation + */ + abstract fun unopinionated(): UnopinionatedDefinePrimaryKey + + /** + * Implementation of an [OpinionatedDefinePrimaryKey] for OpenPGP v4 keys. + * + * @param policy policy for algorithm compliance and fallbacks + * @param creationTime creation time of the primary key + */ + class V4( + policy: Policy, + creationTime: Date, + preferences: AlgorithmSuite + ) : OpinionatedDefinePrimaryKey( + policy, + creationTime, + UnopinionatedDefinePrimaryKey.V4(policy, creationTime) + ) { + + override fun unopinionated(): UnopinionatedDefinePrimaryKey.V4 = + unopinionated as UnopinionatedDefinePrimaryKey.V4 + + override fun setPrimaryKey( + type: KeyType, + creationTime: Date, + applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? + ): OpinionatedDefineSubkeys.V4 { + + val applier = applyToPrimaryKey ?: { + addDirectKeySignature() + } + + val unopinionatedSubkeys = unopinionated().setPrimaryKey( + type, creationTime, applier) + return OpinionatedDefineSubkeys.V4( + unopinionatedSubkeys.primaryKey, policy, creationTime, unopinionatedSubkeys) + } + + } + + /** + * Implementation of an [OpinionatedDefinePrimaryKey] for OpenPGP v6 keys. + * + * @param policy policy for algorithm compliance and fallbacks + * @param creationTime creation time of the primary key + */ + class V6( + policy: Policy, + creationTime: Date, + preferences: AlgorithmSuite + ) : OpinionatedDefinePrimaryKey( + policy, + creationTime, + UnopinionatedDefinePrimaryKey.V6(policy, creationTime) + ) { + + override fun unopinionated(): UnopinionatedDefinePrimaryKey.V6 = + unopinionated as UnopinionatedDefinePrimaryKey.V6 + + override fun setPrimaryKey( + type: KeyType, + creationTime: Date, + applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? + ): OpinionatedDefineSubkeys.V6 { + val applier = applyToPrimaryKey ?: { + addDirectKeySignature() + } + return OpinionatedDefineSubkeys.V6( + policy, + creationTime, + unopinionated.setPrimaryKey(type, creationTime, applier) as UnopinionatedDefineSubkeys.V6) + } + } +} + +/** + * Unopinionated implementation of [DefinePrimaryKey]. + * An unopinionated [DefinePrimaryKey] will not perform any sanity checks on user-provided + * algorithms. + * + * @param creationTime creation time of the primary key + * @param U unopinionated builder type + */ +abstract class UnopinionatedDefinePrimaryKey( + policy: Policy, + creationTime: Date, +) : DefinePrimaryKey( + policy, + creationTime +) { + + /** + * Implementation of an [UnopinionatedDefinePrimaryKey] for OpenPGP v4 keys. + * + * @param creationTime creation time of the primary key + */ + class V4(policy: Policy, creationTime: Date) : UnopinionatedDefinePrimaryKey(policy, creationTime) { + + override fun setPrimaryKey( + type: KeyType, + creationTime: Date, + applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? + ): UnopinionatedDefineSubkeys.V4 { + var primaryKey = OpenPgpKeyPairGenerator.V4().generatePrimaryKey(type, creationTime) + val applier = ApplyToV4PrimaryKey(primaryKey, this) + if (applyToPrimaryKey != null) { + primaryKey = applier.applyToPrimaryKey() + } + return UnopinionatedDefineSubkeys.V4(primaryKey, policy, creationTime) + } + + + private class ApplyToV4PrimaryKey(keyPair: PGPKeyPair, builder: DefinePrimaryKey<*>) : ApplyToPrimaryKey(keyPair, builder) { + + override fun addUserId( + userId: CharSequence, + subpacketsCallback: SelfSignatureSubpackets.Callback, + certificationType: CertificationType, + hashAlgorithm: HashAlgorithm, + bindingTime: Date + ): PGPKeyPair { + val sig = buildCertificationFor( + keyPair, + userId, + certificationType, + bindingTime, + hashAlgorithm, + subpacketsCallback) + + keyPair = PGPKeyPair( + PGPPublicKey.addCertification(keyPair.publicKey, userId.toString(), sig), + keyPair.privateKey + ) + return keyPair + } + + override fun addDirectKeySignature( + subpacketsCallback: SelfSignatureSubpackets.Callback, + hashAlgorithm: HashAlgorithm, + bindingTime: Date + ): PGPKeyPair { + val sig = buildDirectKeySignature(keyPair, bindingTime, Policy.getInstance().keyGenerationAlgorithmSuite, hashAlgorithm, subpacketsCallback) + keyPair = PGPKeyPair( + PGPPublicKey.addCertification(keyPair.publicKey, sig), + keyPair.privateKey + ) + return keyPair + } + + fun buildCertificationFor( + pair: PGPKeyPair, + userId: CharSequence, + certificationType: CertificationType, + bindingTime: Date, + hashAlgorithm: HashAlgorithm, + subpacketsCallback: SelfSignatureSubpackets.Callback + ): PGPSignature { + val builder = + SelfSignatureBuilder( + pair.privateKey, pair.publicKey, certificationType.signatureType, hashAlgorithm) + builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) } + builder.applyCallback(subpacketsCallback) + return builder.build(userId) + } + + fun buildCertificationFor( + pair: PGPKeyPair, + userAttribute: PGPUserAttributeSubpacketVector, + certificationType: CertificationType, + bindingTime: Date, + hashAlgorithm: HashAlgorithm, + subpacketsCallback: SelfSignatureSubpackets.Callback + ): PGPSignature { + val builder = + SelfSignatureBuilder( + pair.privateKey, pair.publicKey, certificationType.signatureType, hashAlgorithm) + builder.hashedSubpackets.apply { setSignatureCreationTime(bindingTime) } + builder.applyCallback(subpacketsCallback) + return builder.build(userAttribute) + } + + fun buildDirectKeySignature( + pair: PGPKeyPair, + bindingTime: Date, + algorithmSuite: AlgorithmSuite, + hashAlgorithm: HashAlgorithm, + subpacketsCallback: SelfSignatureSubpackets.Callback + ): PGPSignature { + val builder = + DirectKeySelfSignatureBuilder(pair.privateKey, pair.publicKey, hashAlgorithm) + + builder.hashedSubpackets.apply { + setSignatureCreationTime(bindingTime) + setPreferredHashAlgorithms(algorithmSuite.hashAlgorithms) + setPreferredSymmetricKeyAlgorithms(algorithmSuite.symmetricKeyAlgorithms) + setPreferredCompressionAlgorithms(algorithmSuite.compressionAlgorithms) + } + + builder.applyCallback(subpacketsCallback) + + return builder.build() + } + + } + + class ApplyToV4Subkey( + primaryKey: PGPKeyPair, + subkey: PGPKeyPair, + builder: DefineSubkeys<*> + ): ApplyToSubkey(primaryKey, subkey, builder) { + + override fun addBindingSignature( + subpacketsCallback: SelfSignatureSubpackets.Callback, + hashAlgorithm: HashAlgorithm, + bindingTime: Date + ): PGPKeyPair { + val sig = buildBindingSignature(primaryKey, subkey, hashAlgorithm, bindingTime, subpacketsCallback) + + return PGPKeyPair( + PGPPublicKey.addCertification(subkey.publicKey, sig), + subkey.privateKey + ) + } + + fun buildBindingSignature(primaryKey: PGPKeyPair, + subkey: PGPKeyPair, + hashAlgorithm: HashAlgorithm, + bindingTime: Date, + subpacketsCallback: SelfSignatureSubpackets.Callback + ): PGPSignature { + return SubkeyBindingSignatureBuilder(primaryKey.privateKey, primaryKey.publicKey, hashAlgorithm) + .applyCallback(subpacketsCallback.then(SelfSignatureSubpackets.applyHashed { setSignatureCreationTime(bindingTime) })) + .build(subkey.publicKey) + } + + } + } + + /** + * Implementation of an [UnopinionatedDefinePrimaryKey] for OpenPGP v6 keys. + * + * @param creationTime creation time of the primary key + */ + class V6(policy: Policy, creationTime: Date) + : UnopinionatedDefinePrimaryKey(policy, creationTime) { + override fun setPrimaryKey( + type: KeyType, + creationTime: Date, + applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? + ): UnopinionatedDefineSubkeys.V6 { + return UnopinionatedDefineSubkeys.V6(policy, creationTime) + } + } +} + +/** + * Interface for key builder that can + */ +abstract class DefineSubkeys>( + val policy: Policy, + val creationTime: Date +) { + + /** + * Add a subkey to the OpenPGP key. + * + * @param type subkey type + * @param creationTime creation time of the subkey + * @param function function to apply to the subkey. Used to add binding signatures. + * + * @return this + */ + abstract fun addSubkey(type: KeyType, creationTime: Date = this.creationTime, function: (ApplyToSubkey.() -> PGPKeyPair)? = null): B + + /** + * Finish the key generation and return the OpenPGP [PGPSecretKeyRing]. + * + * @return finished [PGPSecretKeyRing] + */ + abstract fun build(): PGPSecretKeyRing +} + +abstract class OpinionatedDefineSubkeys( + policy: Policy, + creationTime: Date +) : DefineSubkeys(policy, creationTime) { + + abstract val unopinionated: UnopinionatedDefineSubkeys + + override fun build(): PGPSecretKeyRing = unopinionated.build() + + class V4( + primaryKey: PGPKeyPair, + policy: Policy, + creationTime: Date, + override val unopinionated: UnopinionatedDefineSubkeys.V4 = UnopinionatedDefineSubkeys.V4(primaryKey, policy, creationTime) + ) : OpinionatedDefineSubkeys(policy, creationTime) { + + override fun addSubkey( + type: KeyType, + creationTime: Date, + applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)? + ): V4 = apply { + unopinionated.addSubkey(type, creationTime, applyToSubkey) + } + + } + + class V6( + policy: Policy, + creationTime: Date, + override val unopinionated: UnopinionatedDefineSubkeys.V6 = UnopinionatedDefineSubkeys.V6(policy, creationTime) + ) : OpinionatedDefineSubkeys(policy, creationTime) { + + override fun addSubkey( + type: KeyType, + creationTime: Date, + function: (ApplyToSubkey.() -> PGPKeyPair)? + ): V6 = apply { + unopinionated.addSubkey(type, creationTime, function) + } + } +} + +abstract class UnopinionatedDefineSubkeys( + policy: Policy, + creationTime: Date +) : DefineSubkeys(policy, creationTime) { + + class V4(val primaryKey: PGPKeyPair, policy: Policy, creationTime: Date) : UnopinionatedDefineSubkeys(policy, creationTime) { + + val subkeys: MutableList = mutableListOf() + + override fun addSubkey( + type: KeyType, + creationTime: Date, + applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)? + ): V4 = apply { + val subkey = OpenPgpKeyPairGenerator.V4().generateSubkey(type, creationTime) + val applier = UnopinionatedDefinePrimaryKey.V4.ApplyToV4Subkey(primaryKey, subkey, this) + if (applyToSubkey != null) { + subkeys.add(applier.applyToSubkey()) + } + } + + override fun build(): PGPSecretKeyRing { + val primary = PGPSecretKey(primaryKey.privateKey, primaryKey.publicKey, ImplementationFactory.getInstance().v4FingerprintCalculator, true, null) + return PGPSecretKeyRing(buildList { + add(primary) + subkeys.forEach { + add(PGPSecretKey(it.privateKey, it.publicKey, ImplementationFactory.getInstance().v4FingerprintCalculator, false, null)) + } + }) + } + } + + class V6(policy: Policy, creationTime: Date) : UnopinionatedDefineSubkeys(policy, creationTime) { + + override fun addSubkey( + type: KeyType, + creationTime: Date, + function: (ApplyToSubkey.() -> PGPKeyPair)? + ): V6 = apply { + // Add Key + } + + override fun build(): PGPSecretKeyRing { + TODO("Not yet implemented") + } + } +} + +/** + * Function that can be applied to the primary key. + */ +abstract class ApplyToPrimaryKey(var keyPair: PGPKeyPair, val builder: DefinePrimaryKey<*>) { + + /** + * Add a UserID to the primary key. + * + * @param userId UserID to be bound to the primary key + * @param subpacketsCallback callback to modify the binding signatures subpackets + */ + abstract fun addUserId( + userId: CharSequence, + subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop(), + certificationType: CertificationType = CertificationType.POSITIVE, + hashAlgorithm: HashAlgorithm = builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm, + bindingTime: Date = builder.creationTime): PGPKeyPair + + /** + * Add a DirectKeySignature to the primary key. + * Such a signature usually carries information that applies to the whole OpenPGP key, + * such as algorithm preferences etc. + */ + abstract fun addDirectKeySignature( + subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop(), + hashAlgorithm: HashAlgorithm = builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm, + bindingTime: Date = builder.creationTime + ): PGPKeyPair +} + +/** + * Function that can be applied to subkeys. + */ +abstract class ApplyToSubkey( + val primaryKey: PGPKeyPair, + val subkey: PGPKeyPair, + val builder: DefineSubkeys<*>) { + + /** + * Add a binding signature to the subkey. + * + * @param subpacketsCallback callback to modify the binding signatures subpackets + */ + abstract fun addBindingSignature( + subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop(), + hashAlgorithm: HashAlgorithm = builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm, + bindingTime: Date = subkey.publicKey.creationTime + ): PGPKeyPair +} + +internal fun generateKeyPair(type: KeyType): KeyPair = + KeyPairGenerator.getInstance(type.name, ProviderFactory.provider) + .also { it.initialize(type.algorithmSpec) } + .generateKeyPair() + +class Templates { + + class V4 { + companion object { + @JvmStatic + fun ed25519Curve25519( + vararg userId: CharSequence, + creationTime: Date = Date() + ): PGPSecretKeyRing { + return buildV4(creationTime = creationTime) + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + userId.forEach { + addUserId(it) + } + addDirectKeySignature(SelfSignatureSubpackets.applyHashed { + setKeyFlags(KeyFlag.CERTIFY_OTHER) + }) + keyPair + } + .addSubkey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + addBindingSignature(SelfSignatureSubpackets.applyHashed { + setKeyFlags(KeyFlag.SIGN_DATA) + }) + } + .addSubkey(KeyType.XDH(XDHSpec._X25519)) { + addBindingSignature(SelfSignatureSubpackets.applyHashed { + setKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE) + }) + } + .build() + } + } + } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyPairGenerator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyPairGenerator.kt new file mode 100644 index 00000000..51d66ece --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/OpenPgpKeyPairGenerator.kt @@ -0,0 +1,90 @@ +package org.pgpainless.key.generation + +import org.bouncycastle.bcpg.PublicSubkeyPacket +import org.bouncycastle.openpgp.PGPKeyPair +import org.bouncycastle.openpgp.PGPPrivateKey +import org.bouncycastle.openpgp.PGPPublicKey +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.provider.ProviderFactory +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.util.* + +/** + * Generator interface for [PGPKeyPair] objects. + */ +internal interface OpenPgpKeyPairGenerator { + + /** + * Generate a [PGPKeyPair] in primary key format. + * + * @param type key type + * @param creationTime creation time of the key + * @return primary key pair + */ + fun generatePrimaryKey(type: KeyType, creationTime: Date): PGPKeyPair + + + /** + * Generate a [PGPKeyPair] in subkey format. + * + * @param type key type + * @param creationTime creation time of the key + * @return subkey pair + */ + fun generateSubkey(type: KeyType, creationTime: Date): PGPKeyPair + + /** + * Implementation of [OpenPgpKeyPairGenerator] which generates OpenPGP v4 keys. + */ + class V4 : OpenPgpKeyPairGenerator { + + /** + * Generate an asymmetric cipher key pair. + * + * @param type algorithm specification + * @return key pair + */ + private fun generateKeyPair(type: KeyType): KeyPair { + return KeyPairGenerator.getInstance(type.name, ProviderFactory.provider) + .also { it.initialize(type.algorithmSpec) } + .generateKeyPair() + } + + /** + * Generate a PGP key pair. + * + * @param type key type + * @param creationTime creation time of the key + * @return pgp key pair + */ + private fun generatePgpKeyPair(type: KeyType, creationTime: Date): PGPKeyPair { + return ImplementationFactory.getInstance() + .getPGPV4KeyPair(type.algorithm, generateKeyPair(type), creationTime) + } + + override fun generatePrimaryKey(type: KeyType, creationTime: Date): PGPKeyPair { + // already in primary key format + return generatePgpKeyPair(type, creationTime) + } + + override fun generateSubkey(type: KeyType, creationTime: Date): PGPKeyPair { + val keyPair = generatePgpKeyPair(type, creationTime) + + // We need to convert the keyPair which is in primary key format into subkey format + val fpCalc = ImplementationFactory.getInstance().keyFingerprintCalculator + val pubkey = keyPair.publicKey + val privkey = keyPair.privateKey + // transform to subkey packet + val subkey = + PublicSubkeyPacket( + pubkey.algorithm, pubkey.creationTime, pubkey.publicKeyPacket.key) + // return as PGP key pair + return PGPKeyPair( + PGPPublicKey(subkey, fpCalc), + PGPPrivateKey(pubkey.keyID, subkey, privkey.privateKeyDataPacket) + ) + } + } +} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/Opinionated.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/Opinionated.kt deleted file mode 100644 index a0d854e4..00000000 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/Opinionated.kt +++ /dev/null @@ -1,243 +0,0 @@ -package org.pgpainless.key.generation - -import org.bouncycastle.openpgp.PGPSecretKeyRing -import org.pgpainless.PGPainless -import org.pgpainless.key.generation.type.KeyType -import org.pgpainless.key.generation.type.eddsa.EdDSACurve -import org.pgpainless.key.generation.type.xdh.XDHSpec -import org.pgpainless.policy.Policy -import org.pgpainless.signature.subpackets.SelfSignatureSubpackets -import java.util.Date - -fun buildV4( - policy: Policy = PGPainless.getPolicy(), - creationTime: Date = Date() -): OpinionatedPrimaryKeyBuilder.V4 { - return OpinionatedPrimaryKeyBuilder.V4(policy, creationTime) -} - -fun buildV6( - policy: Policy = PGPainless.getPolicy(), - creationTime: Date = Date() -): OpinionatedPrimaryKeyBuilder.V6 { - return OpinionatedPrimaryKeyBuilder.V6(policy, creationTime) -} - -fun test() { - // Unopinionated - buildV4() - .unopinionated() - .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), Date()) { - addDirectKeySignature(SelfSignatureSubpackets.nop()) - addUserId("Alice ") - } - .addSubkey(KeyType.EDDSA(EdDSACurve._Ed25519), Date()) { - addBindingSignature(SelfSignatureSubpackets.nop()) - } - .addSubkey(KeyType.XDH(XDHSpec._X25519), Date()) { - addBindingSignature(SelfSignatureSubpackets.nop()) - } - .build() - - // Opinionated - buildV4() - .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), Date()) { - addDirectKeySignature(SelfSignatureSubpackets.nop()) - addUserId("Alice") - } - .addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519), Date()) { - // - } - .build() - - // Unopinionated - buildV6() - .unopinionated() - .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), Date()) - .addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519), Date()) - .build() - - // Opinionated - buildV6() - .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), Date()) - .addSubkey(KeyType.XDH(XDHSpec._X25519), Date()) - .build() -} - -abstract class PrimaryKeyBuilder>( - protected val creationTime: Date -) { - abstract fun setPrimaryKey( - type: KeyType, - creationTime: Date = this.creationTime, - function: ApplyToPrimaryKey.() -> Unit = {}): B -} - -abstract class OpinionatedPrimaryKeyBuilder( - protected val policy: Policy, - creationTime: Date, - protected val unopinionated: UnopinionatedPrimaryKeyBuilder -) : PrimaryKeyBuilder(creationTime) { - - fun unopinionated() = unopinionated - - class V4( - policy: Policy, - creationTime: Date - ) : OpinionatedPrimaryKeyBuilder( - policy, - creationTime, - UnopinionatedPrimaryKeyBuilder.V4(creationTime) - ) { - - override fun setPrimaryKey( - type: KeyType, - creationTime: Date, - function: ApplyToPrimaryKey.() -> Unit - ): Opinionated.V4 { - return Opinionated.V4( - policy, - unopinionated.setPrimaryKey(type, creationTime, function) as Unopinionated.V4) - } - } - - class V6( - policy: Policy, - creationTime: Date - ) : OpinionatedPrimaryKeyBuilder( - policy, - creationTime, - UnopinionatedPrimaryKeyBuilder.V6(creationTime) - ) { - override fun setPrimaryKey( - type: KeyType, - creationTime: Date, - function: ApplyToPrimaryKey.() -> Unit - ): Opinionated.V6 { - return Opinionated.V6( - policy, - unopinionated.setPrimaryKey(type, creationTime, function) as Unopinionated.V6) - } - } -} - -abstract class UnopinionatedPrimaryKeyBuilder( - creationTime: Date -) : PrimaryKeyBuilder( - creationTime -) { - - class V4(creationTime: Date) : UnopinionatedPrimaryKeyBuilder(creationTime) { - override fun setPrimaryKey( - type: KeyType, - creationTime: Date, - function: ApplyToPrimaryKey.() -> Unit - ): Unopinionated.V4 { - return Unopinionated.V4() - } - } - - class V6(creationTime: Date) : UnopinionatedPrimaryKeyBuilder(creationTime) { - override fun setPrimaryKey( - type: KeyType, - creationTime: Date, - function: ApplyToPrimaryKey.() -> Unit - ): Unopinionated.V6 { - return Unopinionated.V6() - } - } -} - -interface KeyBuilder> { - - fun addSubkey(type: KeyType, creationTime: Date, function: ApplyToSubkey.() -> Unit = {}): B - - fun addEncryptionSubkey(type: KeyType, creationTime: Date, function: ApplyToSubkey.() -> Unit = {}): B { - return addSubkey(type, creationTime, function) - } - - fun build(): PGPSecretKeyRing -} - -@JvmDefaultWithoutCompatibility -abstract class Opinionated( - protected val policy: Policy -) : KeyBuilder { - - abstract val unopinionated: Unopinionated - - override fun build(): PGPSecretKeyRing = unopinionated.build() - - class V4( - policy: Policy, - override val unopinionated: Unopinionated.V4 = Unopinionated.V4() - ) : Opinionated(policy) { - - override fun addSubkey( - type: KeyType, - creationTime: Date, - function: ApplyToSubkey.() -> Unit - ): V4 = apply { - unopinionated.addSubkey(type, creationTime, function) - } - - } - - class V6( - policy: Policy, - override val unopinionated: Unopinionated.V6 = Unopinionated.V6() - ) : Opinionated(policy) { - - override fun addSubkey( - type: KeyType, - creationTime: Date, - function: ApplyToSubkey.() -> Unit - ): V6 = apply { - unopinionated.addSubkey(type, creationTime, function) - } - } -} - -@JvmDefaultWithoutCompatibility -abstract class Unopinionated : KeyBuilder { - - class V4 : Unopinionated() { - - override fun addSubkey( - type: KeyType, - creationTime: Date, - function: ApplyToSubkey.() -> Unit - ): V4 = apply { - // Add key - } - - override fun build(): PGPSecretKeyRing { - TODO("Not yet implemented") - } - } - - class V6 : Unopinionated() { - - override fun addSubkey( - type: KeyType, - creationTime: Date, - function: ApplyToSubkey.() -> Unit - ): V6 = apply { - // Add Key - } - - override fun build(): PGPSecretKeyRing { - TODO("Not yet implemented") - } - } -} - -interface ApplyToPrimaryKey { - fun addUserId(userId: CharSequence) - - fun addDirectKeySignature(subpacketsCallback: SelfSignatureSubpackets.Callback) -} - -interface ApplyToSubkey { - fun addBindingSignature(subpacketsCallback: SelfSignatureSubpackets.Callback) -} diff --git a/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/GenerateOpenPgpKeyTest.kt b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/GenerateOpenPgpKeyTest.kt index 3de93932..3f3056bd 100644 --- a/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/GenerateOpenPgpKeyTest.kt +++ b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/GenerateOpenPgpKeyTest.kt @@ -125,21 +125,6 @@ class GenerateOpenPgpKeyTest { "Cannot read resource $resourceName: InputStream is null.") } - fun testAround() { - GenerateOpenPgpKey(Policy.getInstance()) - .buildKeyV4() - .primaryKey(RSA, listOf(KeyFlag.CERTIFY_OTHER)) - - GenerateOpenPgpKey(Policy.getInstance()) - .buildKey() - .unopinionated() - .apply { - primaryKey(RSA, creationTime) - .directKeySignature(callback) - } - - } - interface TestInterface> { fun doSomething(): T } diff --git a/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyGeneratorTest.kt b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyGeneratorTest.kt new file mode 100644 index 00000000..7c043013 --- /dev/null +++ b/pgpainless-core/src/test/kotlin/org/pgpainless/key/generation/OpenPgpKeyGeneratorTest.kt @@ -0,0 +1,102 @@ +package org.pgpainless.key.generation + +import openpgp.plusSeconds +import org.bouncycastle.extensions.toAsciiArmor +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import org.pgpainless.algorithm.CertificationType +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.key.generation.type.KeyType +import org.pgpainless.key.generation.type.eddsa.EdDSACurve +import org.pgpainless.key.generation.type.xdh.XDHSpec +import org.pgpainless.util.DateUtil + +class OpenPgpKeyGeneratorTest { + + @Test + fun `minimal call with opinionated builder adds a default DK sig but no user info`() { + val key = buildV4() + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) + .build() + + assertFalse(key.publicKey.userIDs.hasNext(), + "Key MUST NOT have a UserID") + assertFalse(key.publicKey.userAttributes.hasNext(), + "Key MUST NOT have a UserAttribute") + assertEquals(1, key.publicKey.keySignatures.asSequence().toList().size, + "Opinionated builder adds exactly one DirectKey signature") + + println(key.toAsciiArmor()) + } + + @Test + fun `minimal call with unopinionated builder does not add a default DK sig`() { + val key = buildV4() + .unopinionated() + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) + .build() + + assertFalse(key.publicKey.keySignatures.hasNext()) + + println(key.toAsciiArmor()) + } + + @Test + fun `adding a direct-key signature with the opinionated builder omits the default DK sig`() { + val key = buildV4() + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + addDirectKeySignature() // "overwrites" the default dk sig + } + .build() + + assertEquals(1, key.publicKey.keySignatures.asSequence().toList().size) + + println(key.toAsciiArmor()) + } + + @Test + fun testUnopinionatedV4() { + // Unopinionated + buildV4() + .unopinionated() + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + addDirectKeySignature() + addUserId("Alice ") + } + .addSubkey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + addBindingSignature() + } + .addSubkey(KeyType.XDH(XDHSpec._X25519)) { + addBindingSignature() + } + .build() + } + + @Test + fun testOpinionatedV4() { + // Opinionated + val time = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC") + buildV4(creationTime = time) + .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { + addDirectKeySignature() + addUserId("Alice", + bindingTime = time.plusSeconds(60L)!!, + hashAlgorithm = HashAlgorithm.SHA384, + certificationType = CertificationType.GENERIC) + } + .addSubkey(KeyType.XDH(XDHSpec._X25519)) { + addBindingSignature() + } + .build().let { println(it.toAsciiArmor()) } + } + + @Test + fun testV4Ed25519Curve25519Template() { + Templates.V4.ed25519Curve25519("Alice ", "Alice ") + .let { + println(it.toAsciiArmor()) + } + } + +}