WIP: flexible, generic API

This commit is contained in:
Paul Schaub 2024-02-12 14:43:14 +01:00
parent 6703a514c8
commit a368a44f11
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
5 changed files with 761 additions and 258 deletions

View File

@ -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<B : DefineSubkeys<B>>(
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<B : OpinionatedDefineSubkeys, U : UnopinionatedDefineSubkeys>(
policy: Policy,
creationTime: Date,
protected val unopinionated: UnopinionatedDefinePrimaryKey<U>
) : DefinePrimaryKey<OpinionatedDefineSubkeys>(policy, creationTime) {
/**
* Turn this builder into an unopinionated one by returning the underlying unopinionated
* implementation.
*
* @return unopinionated implementation
*/
abstract fun unopinionated(): UnopinionatedDefinePrimaryKey<U>
/**
* 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<OpinionatedDefineSubkeys.V4, UnopinionatedDefineSubkeys.V4>(
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<OpinionatedDefineSubkeys.V6, UnopinionatedDefineSubkeys.V6>(
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<U : UnopinionatedDefineSubkeys>(
policy: Policy,
creationTime: Date,
) : DefinePrimaryKey<UnopinionatedDefineSubkeys>(
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<UnopinionatedDefineSubkeys.V4>(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<UnopinionatedDefineSubkeys.V6>(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<B : DefineSubkeys<B>>(
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<OpinionatedDefineSubkeys>(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<UnopinionatedDefineSubkeys>(policy, creationTime) {
class V4(val primaryKey: PGPKeyPair, policy: Policy, creationTime: Date) : UnopinionatedDefineSubkeys(policy, creationTime) {
val subkeys: MutableList<PGPKeyPair> = 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()
}
}
}
}

View File

@ -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)
)
}
}
}

View File

@ -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 <alice@pgpainless.org>")
}
.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<B : KeyBuilder<B>>(
protected val creationTime: Date
) {
abstract fun setPrimaryKey(
type: KeyType,
creationTime: Date = this.creationTime,
function: ApplyToPrimaryKey.() -> Unit = {}): B
}
abstract class OpinionatedPrimaryKeyBuilder<B : Opinionated, U : Unopinionated>(
protected val policy: Policy,
creationTime: Date,
protected val unopinionated: UnopinionatedPrimaryKeyBuilder<U>
) : PrimaryKeyBuilder<Opinionated>(creationTime) {
fun unopinionated() = unopinionated
class V4(
policy: Policy,
creationTime: Date
) : OpinionatedPrimaryKeyBuilder<Opinionated.V4, Unopinionated.V4>(
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<Opinionated.V6, Unopinionated.V6>(
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<B : Unopinionated>(
creationTime: Date
) : PrimaryKeyBuilder<Unopinionated>(
creationTime
) {
class V4(creationTime: Date) : UnopinionatedPrimaryKeyBuilder<Unopinionated.V4>(creationTime) {
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
function: ApplyToPrimaryKey.() -> Unit
): Unopinionated.V4 {
return Unopinionated.V4()
}
}
class V6(creationTime: Date) : UnopinionatedPrimaryKeyBuilder<Unopinionated.V6>(creationTime) {
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
function: ApplyToPrimaryKey.() -> Unit
): Unopinionated.V6 {
return Unopinionated.V6()
}
}
}
interface KeyBuilder<B : KeyBuilder<B>> {
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<Opinionated> {
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<Unopinionated> {
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)
}

View File

@ -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<T : TestInterface<T>> {
fun doSomething(): T
}

View File

@ -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 <alice@pgpainless.org>")
}
.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@example.org>", "Alice <alice@pgpainless.org>")
.let {
println(it.toAsciiArmor())
}
}
}