mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 04:17:59 +01:00
WIP: flexible, generic API
This commit is contained in:
parent
6703a514c8
commit
a368a44f11
5 changed files with 761 additions and 258 deletions
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue