Restructure hierarchy of OpenPgpKeyGenerator classes

This commit is contained in:
Paul Schaub 2024-02-19 12:27:27 +01:00
parent 900860c1fa
commit f7d389c8d3
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
4 changed files with 312 additions and 304 deletions

View File

@ -23,7 +23,7 @@ class KeyRingTemplates {
* *
* @param userId userId or null * @param userId userId or null
* @param length length of the RSA keys * @param length length of the RSA keys
* @param passphrase passphrase to encrypt the key with. Can be empty for an unencrytped key. * @param passphrase passphrase to encrypt the key with. Can be empty for an unencrypted key.
* @return key * @return key
*/ */
@JvmOverloads @JvmOverloads
@ -179,7 +179,7 @@ class KeyRingTemplates {
passphrase: Passphrase = Passphrase.emptyPassphrase() passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing = ): PGPSecretKeyRing =
OpenPgpKeyGenerator.buildV4() OpenPgpKeyGenerator.buildV4()
.setCertificationKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) {
if (userId != null) { if (userId != null) {
addUserId(userId) addUserId(userId)
} }

View File

@ -52,21 +52,6 @@ class OpenPgpKeyGenerator internal constructor() {
): OpinionatedDefinePrimaryKeyV4 { ): OpinionatedDefinePrimaryKeyV4 {
return OpinionatedDefinePrimaryKeyV4(policy, creationTime, preferences) return OpinionatedDefinePrimaryKeyV4(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
*/
@JvmStatic
fun buildV6(
policy: Policy = PGPainless.getPolicy(),
creationTime: Date = Date(),
preferences: AlgorithmSuite = AlgorithmSuite.v6AlgorithmSuite
): OpinionatedDefinePrimaryKeyV6 {
return OpinionatedDefinePrimaryKeyV6(policy, creationTime, preferences)
}
} }
} }
@ -75,219 +60,82 @@ class OpenPgpKeyGenerator internal constructor() {
* *
* @param policy algorithm policy * @param policy algorithm policy
* @param creationTime default value for the creation time of the primary key. * @param creationTime default value for the creation time of the primary key.
* @param preferences algorithm preferences
* @param O versioned subkeys builder
*/ */
abstract class DefinePrimaryKey<B : DefineSubkeys<B>> abstract class DefinePrimaryKey<O : DefineSubkeys<O>>
internal constructor( internal constructor(val policy: Policy, val creationTime: Date, val preferences: AlgorithmSuite) {
val policy: Policy,
val creationTime: Date, // If set to true, no default direct-key signature will be added to the key
) { internal var skipDefaultDirectKeySignature = false
internal var keyFlags: List<KeyFlag>? = null
internal abstract fun preferencesSubpackets(): SelfSignatureSubpackets.Callback
internal abstract fun generatePrimaryKey(type: KeyType, creationTime: Date): PGPKeyPair
internal abstract fun applyToPrimaryKey(
primaryKey: PGPKeyPair,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): PGPKeyPair
/** /**
* Define the primary key of the OpenPGP key. * Define the primary key for the OpenPGP key.
* *
* @param type primary key type * @param type primary key type
* @param creationTime creation time of the primary key. Defaults to the [DefinePrimaryKey]'s * @param keyFlags list of key flags that denote the primary keys capabilities
* [creationTime]. * @param creationTime creation time of the primary key
* @param applyToPrimaryKey function that gets applied to the primary key. Is used to add * @param applyToPrimaryKey function block to apply to the primary key
* binding signatures, UserIDs and user-attributes on the primary key. * @return subkey builder
* @return next step key builder
*/ */
abstract fun setPrimaryKey( abstract fun setPrimaryKey(
type: KeyType, type: KeyType,
keyFlags: List<KeyFlag>? = listOf(KeyFlag.CERTIFY_OTHER),
creationTime: Date = this.creationTime, creationTime: Date = this.creationTime,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? = null // default: do nothing applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? = null
): B ): O
internal abstract fun preferencesSubpackets(): SelfSignatureSubpackets.Callback
} }
/** /** Implementation of [DefinePrimaryKey] build for version 4 OpenPGP keys. */
* Opinionated implementation of [DefinePrimaryKey]. Contrary to an unopinionated implementation, an abstract class DefinePrimaryKeyV4<O : DefineSubkeys<O>>(
* 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>
internal constructor(
policy: Policy, policy: Policy,
creationTime: Date, creationTime: Date,
val preferences: AlgorithmSuite, preferences: AlgorithmSuite
protected val unopinionated: UnopinionatedDefinePrimaryKey<U> ) : DefinePrimaryKey<O>(policy, creationTime, preferences) {
) : DefinePrimaryKey<OpinionatedDefineSubkeys>(policy, creationTime) {
/** override fun generatePrimaryKey(type: KeyType, creationTime: Date): PGPKeyPair {
* Turn this builder into an unopinionated one by returning the underlying unopinionated return OpenPgpKeyPairGenerator.V4().generatePrimaryKey(type, creationTime)
* implementation.
*
* @return unopinionated implementation
*/
abstract fun unopinionated(): UnopinionatedDefinePrimaryKey<U>
override fun preferencesSubpackets(): SelfSignatureSubpackets.Callback =
SelfSignatureSubpackets.applyHashed {
setSignatureCreationTime(creationTime)
setPreferredHashAlgorithms(preferences.hashAlgorithms)
setPreferredSymmetricKeyAlgorithms(preferences.symmetricKeyAlgorithms)
setPreferredCompressionAlgorithms(preferences.compressionAlgorithms)
setFeatures(preferences.features)
}
fun setCertificationKey(
type: KeyType,
creationTime: Date = this.creationTime,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? = { keyPair }
): B {
return setPrimaryKey(type, creationTime) {
addDirectKeySignature(
preferencesSubpackets()
.then(
SelfSignatureSubpackets.applyHashed { setKeyFlags(KeyFlag.CERTIFY_OTHER) }))
then(applyToPrimaryKey)
}
as B
} }
}
/** override fun applyToPrimaryKey(
* Implementation of an [OpinionatedDefinePrimaryKey] for OpenPGP v4 keys. primaryKey: PGPKeyPair,
*
* @param policy policy for algorithm compliance and fallbacks
* @param creationTime creation time of the primary key
*/
class OpinionatedDefinePrimaryKeyV4
internal constructor(policy: Policy, creationTime: Date, preferences: AlgorithmSuite) :
OpinionatedDefinePrimaryKey<OpinionatedDefineSubkeysV4, UnopinionatedDefineSubkeysV4>(
policy, creationTime, preferences, UnopinionatedDefinePrimaryKeyV4(policy, creationTime)) {
override fun unopinionated(): UnopinionatedDefinePrimaryKeyV4 =
unopinionated as UnopinionatedDefinePrimaryKeyV4
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)? applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): OpinionatedDefineSubkeysV4 { ): PGPKeyPair {
return if (applyToPrimaryKey == null) {
require(policy.publicKeyAlgorithmPolicy.isAcceptable(type.algorithm, type.bitStrength)) { primaryKey
"Public Key algorithm ${type.algorithm} with ${type.bitStrength} is too weak" + } else {
" for the current public key algorithm policy." ApplyToPrimaryKeyV4(primaryKey, this).applyToPrimaryKey()
} }
val applier =
applyToPrimaryKey
?: {
// Add default direct-key signature containing preferences
addDirectKeySignature(preferencesSubpackets())
}
val unopinionatedSubkeys = unopinionated().setPrimaryKey(type, creationTime, applier)
return OpinionatedDefineSubkeysV4(
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 OpinionatedDefinePrimaryKeyV6
internal constructor(policy: Policy, creationTime: Date, preferences: AlgorithmSuite) :
OpinionatedDefinePrimaryKey<OpinionatedDefineSubkeysV6, UnopinionatedDefineSubkeysV6>(
policy, creationTime, preferences, UnopinionatedDefinePrimaryKeyV6(policy, creationTime)) {
override fun unopinionated(): UnopinionatedDefinePrimaryKeyV6 =
unopinionated as UnopinionatedDefinePrimaryKeyV6
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): OpinionatedDefineSubkeysV6 {
val applier = applyToPrimaryKey ?: { addDirectKeySignature(preferencesSubpackets()) }
return OpinionatedDefineSubkeysV6(
policy,
creationTime,
unopinionated.setPrimaryKey(type, creationTime, applier)
as UnopinionatedDefineSubkeysV6)
}
}
/**
* 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>
internal constructor(
policy: Policy,
creationTime: Date,
) : DefinePrimaryKey<UnopinionatedDefineSubkeys>(policy, creationTime) {
override fun preferencesSubpackets(): SelfSignatureSubpackets.Callback =
SelfSignatureSubpackets.nop()
}
/**
* Implementation of an [UnopinionatedDefinePrimaryKey] for OpenPGP v4 keys.
*
* @param creationTime creation time of the primary key
*/
class UnopinionatedDefinePrimaryKeyV4 internal constructor(policy: Policy, creationTime: Date) :
UnopinionatedDefinePrimaryKey<UnopinionatedDefineSubkeysV4>(policy, creationTime) {
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): UnopinionatedDefineSubkeysV4 {
// generate primary key
var primaryKey = OpenPgpKeyPairGenerator.V4().generatePrimaryKey(type, creationTime)
// add user-ids and direct-key signatures if requested
if (applyToPrimaryKey != null) {
primaryKey = ApplyToPrimaryKeyV4(primaryKey, this).applyToPrimaryKey()
}
// return builder for adding subkeys
return UnopinionatedDefineSubkeysV4(primaryKey, policy, creationTime)
}
}
/**
* Implementation of an [UnopinionatedDefinePrimaryKey] for OpenPGP v6 keys.
*
* @param creationTime creation time of the primary key
*/
class UnopinionatedDefinePrimaryKeyV6 internal constructor(policy: Policy, creationTime: Date) :
UnopinionatedDefinePrimaryKey<UnopinionatedDefineSubkeysV6>(policy, creationTime) {
override fun setPrimaryKey(
type: KeyType,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): UnopinionatedDefineSubkeysV6 {
return UnopinionatedDefineSubkeysV6(policy, creationTime)
} }
} }
/** /**
* Interface for key builder that can be used to add additional subkeys to an OpenPGP key. * Interface for key builder that can be used to add additional subkeys to an OpenPGP key.
* *
* @param primaryKey primary key of the OpenPGP key
* @param policy policy to sanitize algorithms against * @param policy policy to sanitize algorithms against
* @param creationTime creation time of the OpenPGP key * @param creationTime creation time of the OpenPGP key
* @param subkeys list of subkeys that were already added
* @param B type of this builder
*/ */
abstract class DefineSubkeys<B : DefineSubkeys<B>> abstract class DefineSubkeys<B : DefineSubkeys<B>>
internal constructor(val policy: Policy, val creationTime: Date) { internal constructor(
internal var primaryKey: PGPKeyPair,
internal val policy: Policy,
internal val creationTime: Date,
internal val subkeys: MutableList<PGPKeyPair> = mutableListOf()
) {
/** /**
* Add a subkey to the OpenPGP key. * Add a subkey to the OpenPGP key.
@ -297,11 +145,26 @@ internal constructor(val policy: Policy, val creationTime: Date) {
* @param applyToSubkey function to apply to the subkey. Used to add binding signatures. * @param applyToSubkey function to apply to the subkey. Used to add binding signatures.
* @return this * @return this
*/ */
abstract fun addSubkey( fun addSubkey(
type: KeyType, type: KeyType,
creationTime: Date = this.creationTime, creationTime: Date = this.creationTime,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)? = null applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)? = null
): B ): B =
apply {
val subkey = generateSubkey(type, creationTime)
subkeys.add(applyToSubkey(subkey, applyToSubkey))
}
as B
internal abstract fun applyToSubkey(
subkey: PGPKeyPair,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): PGPKeyPair
internal abstract fun generateSubkey(
type: KeyType,
creationTime: Date = this.creationTime
): PGPKeyPair
/** /**
* Finish the key generation and return the OpenPGP [PGPSecretKeyRing]. * Finish the key generation and return the OpenPGP [PGPSecretKeyRing].
@ -323,20 +186,138 @@ internal constructor(val policy: Policy, val creationTime: Date) {
fun build(passphrase: Passphrase) = build(SecretKeyRingProtector.unlockAnyKeyWith(passphrase)) fun build(passphrase: Passphrase) = build(SecretKeyRingProtector.unlockAnyKeyWith(passphrase))
} }
abstract class DefineSubkeysV4<O : DefineSubkeys<O>>(
primaryKey: PGPKeyPair,
policy: Policy,
creationTime: Date,
subkeys: List<PGPKeyPair>
) : DefineSubkeys<O>(primaryKey, policy, creationTime, subkeys.toMutableList()) {
override fun generateSubkey(type: KeyType, creationTime: Date): PGPKeyPair {
return OpenPgpKeyPairGenerator.V4().generateSubkey(type, creationTime)
}
override fun applyToSubkey(
subkey: PGPKeyPair,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): PGPKeyPair {
return if (applyToSubkey == null) {
subkey
} else {
ApplyToSubkeyV4(primaryKey, subkey, this).applyToSubkey()
}
}
override fun build(protector: SecretKeyRingProtector): PGPSecretKeyRing {
return PGPSecretKeyRing(
buildList {
// Primary Key
add(
PGPSecretKey(
primaryKey.privateKey,
primaryKey.publicKey,
ImplementationFactory.getInstance().v4FingerprintCalculator,
true,
protector.getEncryptor(primaryKey.keyID)))
// Subkeys
subkeys.forEach {
add(
PGPSecretKey(
it.privateKey,
it.publicKey,
ImplementationFactory.getInstance().v4FingerprintCalculator,
false,
protector.getEncryptor(it.keyID)))
}
})
}
}
/** /**
* Opinionated builder for adding subkeys to an OpenPGP key. * Implementation of an [OpinionatedDefinePrimaryKey] for OpenPGP v4 keys.
* *
* @param policy policy to sanitize algorithms with * @param policy policy for algorithm compliance and fallbacks
* @param creationTime creation time of the OpenPGP key * @param creationTime creation time of the primary key
*/ */
abstract class OpinionatedDefineSubkeys internal constructor(policy: Policy, creationTime: Date) : class OpinionatedDefinePrimaryKeyV4
DefineSubkeys<OpinionatedDefineSubkeys>(policy, creationTime) { internal constructor(policy: Policy, creationTime: Date, preferences: AlgorithmSuite) :
DefinePrimaryKeyV4<OpinionatedDefineSubkeysV4>(policy, creationTime, preferences) {
// unopinionated builder fun unopinionated() = UnopinionatedDefinePrimaryKeyV4(this)
abstract val unopinionated: UnopinionatedDefineSubkeys
override fun build(protector: SecretKeyRingProtector): PGPSecretKeyRing = override fun preferencesSubpackets(): SelfSignatureSubpackets.Callback =
unopinionated.build(protector) SelfSignatureSubpackets.applyHashed {
setSignatureCreationTime(creationTime)
setPreferredHashAlgorithms(preferences.hashAlgorithms)
setPreferredSymmetricKeyAlgorithms(preferences.symmetricKeyAlgorithms)
setPreferredCompressionAlgorithms(preferences.compressionAlgorithms)
setFeatures(preferences.features)
keyFlags?.let { setKeyFlags(it) }
}
override fun setPrimaryKey(
type: KeyType,
keyFlags: List<KeyFlag>?,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): OpinionatedDefineSubkeysV4 {
// Check algorithm is signing capable
require(type.algorithm.isSigningCapable()) { "Primary Key MUST be capable of signing." }
// Check key strength
require(policy.publicKeyAlgorithmPolicy.isAcceptable(type.algorithm, type.bitStrength)) {
"Public Key algorithm ${type.algorithm} with ${type.bitStrength} is too weak" +
" for the current public key algorithm policy."
}
// Remember flags for DK and UID signatures
this.keyFlags = keyFlags
// Add user-provided signatures
var key = applyToPrimaryKey(generatePrimaryKey(type, creationTime), applyToPrimaryKey)
// If no DK sig has been added by user, add default DK sig
if (!skipDefaultDirectKeySignature) {
key = applyToPrimaryKey(key) { addDirectKeySignature(preferencesSubpackets()) }
}
return OpinionatedDefineSubkeysV4(key, policy, creationTime)
}
}
/**
* Implementation of an [UnopinionatedDefinePrimaryKey] for OpenPGP v4 keys.
*
* @param creationTime creation time of the primary key
*/
class UnopinionatedDefinePrimaryKeyV4
internal constructor(policy: Policy, creationTime: Date, preferences: AlgorithmSuite) :
DefinePrimaryKeyV4<UnopinionatedDefineSubkeysV4>(policy, creationTime, preferences) {
constructor(
opinionated: OpinionatedDefinePrimaryKeyV4
) : this(opinionated.policy, opinionated.creationTime, opinionated.preferences)
override fun preferencesSubpackets(): SelfSignatureSubpackets.Callback =
SelfSignatureSubpackets.nop()
override fun setPrimaryKey(
type: KeyType,
keyFlags: List<KeyFlag>?,
creationTime: Date,
applyToPrimaryKey: (ApplyToPrimaryKey.() -> PGPKeyPair)?
): UnopinionatedDefineSubkeysV4 {
// Remember flags for DK and UID signatures
// this.keyFlags = keyFlags
// Add user-provided signatures
val key = applyToPrimaryKey(generatePrimaryKey(type, creationTime), applyToPrimaryKey)
// return builder for adding subkeys
return UnopinionatedDefineSubkeysV4(key, policy, creationTime)
}
} }
/** /**
@ -348,21 +329,10 @@ abstract class OpinionatedDefineSubkeys internal constructor(policy: Policy, cre
* @param unopinionated unopinionated variant of this builder * @param unopinionated unopinionated variant of this builder
*/ */
class OpinionatedDefineSubkeysV4 class OpinionatedDefineSubkeysV4
internal constructor( internal constructor(primaryKey: PGPKeyPair, policy: Policy, creationTime: Date) :
primaryKey: PGPKeyPair, DefineSubkeysV4<OpinionatedDefineSubkeysV4>(primaryKey, policy, creationTime, listOf()) {
policy: Policy,
creationTime: Date,
override val unopinionated: UnopinionatedDefineSubkeysV4 =
UnopinionatedDefineSubkeysV4(primaryKey, policy, creationTime)
) : OpinionatedDefineSubkeys(policy, creationTime) {
override fun addSubkey( fun unopinionated() = UnopinionatedDefineSubkeysV4(this)
type: KeyType,
creationTime: Date,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): OpinionatedDefineSubkeysV4 = apply {
unopinionated.addSubkey(type, creationTime, applyToSubkey)
}
fun addSigningSubkey( fun addSigningSubkey(
type: KeyType, type: KeyType,
@ -389,83 +359,32 @@ internal constructor(
) = addSubkey(type, creationTime, applyToSubkey) ) = addSubkey(type, creationTime, applyToSubkey)
} }
class OpinionatedDefineSubkeysV6 class UnopinionatedDefineSubkeysV4
internal constructor( internal constructor(
primaryKey: PGPKeyPair,
policy: Policy, policy: Policy,
creationTime: Date, creationTime: Date,
override val unopinionated: UnopinionatedDefineSubkeysV6 = subkeys: List<PGPKeyPair> = mutableListOf()
UnopinionatedDefineSubkeysV6(policy, creationTime) ) : DefineSubkeysV4<UnopinionatedDefineSubkeysV4>(primaryKey, policy, creationTime, subkeys) {
) : OpinionatedDefineSubkeys(policy, creationTime) {
override fun addSubkey( constructor(
type: KeyType, opinionated: OpinionatedDefineSubkeysV4
creationTime: Date, ) : this(
opinionated.primaryKey, opinionated.policy, opinionated.creationTime, opinionated.subkeys)
override fun applyToSubkey(
subkey: PGPKeyPair,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)? applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): OpinionatedDefineSubkeysV6 = apply { ): PGPKeyPair {
unopinionated.addSubkey(type, creationTime, applyToSubkey) return if (applyToSubkey == null) {
} subkey
} } else {
ApplyToSubkeyV4(primaryKey, subkey, this).applyToSubkey()
abstract class UnopinionatedDefineSubkeys internal constructor(policy: Policy, creationTime: Date) : }
DefineSubkeys<UnopinionatedDefineSubkeys>(policy, creationTime)
class UnopinionatedDefineSubkeysV4
internal constructor(val primaryKey: PGPKeyPair, policy: Policy, creationTime: Date) :
UnopinionatedDefineSubkeys(policy, creationTime) {
private val subkeys: MutableList<PGPKeyPair> = mutableListOf()
override fun addSubkey(
type: KeyType,
creationTime: Date,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): UnopinionatedDefineSubkeysV4 = apply {
val subkey = OpenPgpKeyPairGenerator.V4().generateSubkey(type, creationTime)
subkeys.add(
if (applyToSubkey == null) {
subkey
} else {
ApplyToSubkeyV4(primaryKey, subkey, this).applyToSubkey()
})
} }
override fun build(protector: SecretKeyRingProtector): PGPSecretKeyRing { override fun generateSubkey(type: KeyType, creationTime: Date): PGPKeyPair {
val primary = return OpenPgpKeyPairGenerator.V4().generateSubkey(type, creationTime)
PGPSecretKey(
primaryKey.privateKey,
primaryKey.publicKey,
ImplementationFactory.getInstance().v4FingerprintCalculator,
true,
protector.getEncryptor(primaryKey.keyID))
return PGPSecretKeyRing(
buildList {
add(primary)
subkeys.forEach {
add(
PGPSecretKey(
it.privateKey,
it.publicKey,
ImplementationFactory.getInstance().v4FingerprintCalculator,
false,
protector.getEncryptor(it.keyID)))
}
})
}
}
class UnopinionatedDefineSubkeysV6 internal constructor(policy: Policy, creationTime: Date) :
UnopinionatedDefineSubkeys(policy, creationTime) {
override fun addSubkey(
type: KeyType,
creationTime: Date,
applyToSubkey: (ApplyToSubkey.() -> PGPKeyPair)?
): UnopinionatedDefineSubkeysV6 {
TODO("Not yet implemented")
}
override fun build(protector: SecretKeyRingProtector): PGPSecretKeyRing {
TODO("Not yet implemented")
} }
} }
@ -491,7 +410,7 @@ internal constructor(var keyPair: PGPKeyPair, val builder: DefinePrimaryKey<*>)
*/ */
abstract fun addUserId( abstract fun addUserId(
userId: CharSequence, userId: CharSequence,
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop(), subpacketsCallback: SelfSignatureSubpackets.Callback = builder.preferencesSubpackets(),
certificationType: CertificationType = CertificationType.POSITIVE, certificationType: CertificationType = CertificationType.POSITIVE,
hashAlgorithm: HashAlgorithm = hashAlgorithm: HashAlgorithm =
builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm, builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
@ -551,13 +470,27 @@ internal constructor(var keyPair: PGPKeyPair, val builder: DefinePrimaryKey<*>)
* Add a DirectKeySignature to the primary key. Such a signature usually carries information * 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. * that applies to the whole OpenPGP key, such as algorithm preferences etc.
*/ */
abstract fun addDirectKeySignature( fun addDirectKeySignature(
subpacketsCallback: SelfSignatureSubpackets.Callback = builder.preferencesSubpackets(), subpacketsCallback: SelfSignatureSubpackets.Callback = builder.preferencesSubpackets(),
hashAlgorithm: HashAlgorithm = hashAlgorithm: HashAlgorithm =
builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm, builder.policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
bindingTime: Date = builder.creationTime bindingTime: Date = builder.creationTime
): PGPKeyPair {
skipDefaultSignature()
return doAddDirectKeySignature(subpacketsCallback, hashAlgorithm, bindingTime)
}
protected abstract fun doAddDirectKeySignature(
subpacketsCallback: SelfSignatureSubpackets.Callback,
hashAlgorithm: HashAlgorithm,
bindingTime: Date
): PGPKeyPair ): PGPKeyPair
fun skipDefaultSignature(): PGPKeyPair {
builder.skipDefaultDirectKeySignature = true
return keyPair
}
/** /**
* Schedule the execution of another [ApplyToPrimaryKey] function instance right after this one * Schedule the execution of another [ApplyToPrimaryKey] function instance right after this one
* has been executed. * has been executed.
@ -578,14 +511,34 @@ class ApplyToPrimaryKeyV4 internal constructor(keyPair: PGPKeyPair, builder: Def
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
bindingTime: Date bindingTime: Date
): PGPKeyPair { ): PGPKeyPair {
val callback = markAsPrimaryIfNecessary(subpacketsCallback)
val sig = val sig =
buildCertificationFor( buildCertificationFor(
keyPair, userId, certificationType, bindingTime, hashAlgorithm, subpacketsCallback) keyPair, userId, certificationType, bindingTime, hashAlgorithm, callback)
keyPair = keyPair.plusCertification(userId, sig) keyPair = keyPair.plusCertification(userId, sig)
return keyPair return keyPair
} }
private fun markAsPrimaryIfNecessary(
subpacketsCallback: SelfSignatureSubpackets.Callback
): SelfSignatureSubpackets.Callback {
val callback =
// if key has primary User-IDs already, do nothing
if (keyPair.publicKey.userIDs.asSequence().any { uid ->
keyPair.publicKey.getSignaturesForID(uid).asSequence().any { sig ->
sig.hashedSubPackets.isPrimaryUserID
}
}) {
subpacketsCallback
} else {
// else set this user-id as primary
SelfSignatureSubpackets.applyHashed { setPrimaryUserId() }.then(subpacketsCallback)
}
return callback
}
override fun addUserAttribute( override fun addUserAttribute(
userAttribute: PGPUserAttributeSubpacketVector, userAttribute: PGPUserAttributeSubpacketVector,
subpacketsCallback: SelfSignatureSubpackets.Callback, subpacketsCallback: SelfSignatureSubpackets.Callback,
@ -606,7 +559,7 @@ class ApplyToPrimaryKeyV4 internal constructor(keyPair: PGPKeyPair, builder: Def
return keyPair return keyPair
} }
override fun addDirectKeySignature( override fun doAddDirectKeySignature(
subpacketsCallback: SelfSignatureSubpackets.Callback, subpacketsCallback: SelfSignatureSubpackets.Callback,
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
bindingTime: Date bindingTime: Date

View File

@ -45,7 +45,6 @@ import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.TestKeys; import org.pgpainless.key.TestKeys;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.consumer.SignaturePicker; import org.pgpainless.signature.consumer.SignaturePicker;
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;

View File

@ -1,16 +1,24 @@
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation package org.pgpainless.key.generation
import org.bouncycastle.bcpg.sig.PrimaryUserID
import org.bouncycastle.extensions.toAsciiArmor import org.bouncycastle.extensions.toAsciiArmor
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.eddsa.EdDSACurve import org.pgpainless.key.generation.type.eddsa.EdDSACurve
import org.pgpainless.key.generation.type.rsa.RsaLength import org.pgpainless.key.generation.type.rsa.RsaLength
import org.pgpainless.key.generation.type.xdh.XDHSpec import org.pgpainless.key.generation.type.xdh.XDHSpec
import org.pgpainless.policy.Policy import org.pgpainless.policy.Policy
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.util.DateUtil import org.pgpainless.util.DateUtil
class OpenPgpKeyGeneratorTest { class OpenPgpKeyGeneratorTest {
@ -57,6 +65,39 @@ class OpenPgpKeyGeneratorTest {
println(key.toAsciiArmor()) println(key.toAsciiArmor())
} }
@Test
fun `adding two user-ids will mark the first one as primary`() {
val key =
OpenPgpKeyGenerator.buildV4()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId("Primary <primary@example.com>")
addUserId("Non Primary <non-primary@example.com>")
}
.build()
val info = PGPainless.inspectKeyRing(key)
assertEquals("Primary <primary@example.com>", info.primaryUserId)
}
@Test
fun `adding two user-ids but mark the first as non-primary will mark the second one as primary`() {
val key =
OpenPgpKeyGenerator.buildV4()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
addUserId(
"Non Primary <non-primary@example.com>",
SelfSignatureSubpackets.applyHashed {
// Not primary
setPrimaryUserId(PrimaryUserID(false, false))
})
addUserId("Primary <primary@example.com>")
}
.build()
val info = PGPainless.inspectKeyRing(key)
assertEquals("Primary <primary@example.com>", info.primaryUserId)
}
@Test @Test
fun testUnopinionatedV4() { fun testUnopinionatedV4() {
// Unopinionated // Unopinionated
@ -76,7 +117,7 @@ class OpenPgpKeyGeneratorTest {
// Opinionated // Opinionated
val time = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC") val time = DateUtil.parseUTCDate("2024-01-01 00:00:00 UTC")
OpenPgpKeyGenerator.buildV4(creationTime = time) OpenPgpKeyGenerator.buildV4(creationTime = time)
.setCertificationKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { .setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER)) {
addUserId("Alice <alice@pgpainless.org>") addUserId("Alice <alice@pgpainless.org>")
} }
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519)) .addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
@ -109,7 +150,7 @@ class OpenPgpKeyGeneratorTest {
@Test @Test
fun test() { fun test() {
OpenPgpKeyGenerator.buildV4() OpenPgpKeyGenerator.buildV4()
.setCertificationKey(KeyType.RSA(RsaLength._3072)) .setPrimaryKey(KeyType.RSA(RsaLength._3072), keyFlags = listOf(KeyFlag.CERTIFY_OTHER))
.build() .build()
.toAsciiArmor() .toAsciiArmor()
.let { println(it) } .let { println(it) }
@ -138,4 +179,19 @@ class OpenPgpKeyGeneratorTest {
.unopinionated() // unopinionated builder allows for non-compliant configurations .unopinionated() // unopinionated builder allows for non-compliant configurations
.setPrimaryKey(KeyType.RSA(RsaLength._2048)) .setPrimaryKey(KeyType.RSA(RsaLength._2048))
} }
@Test
fun `skip default DirectKey signature will not add one`() {
val key =
OpenPgpKeyGenerator.buildV4()
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) { skipDefaultSignature() }
.build()
assertFalse(key.publicKey.keySignatures.hasNext())
}
@Test
fun testModernKeyGeneration() {
println(KeyRingTemplates().modernKeyRing("null").toAsciiArmor())
}
} }