mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-25 04:17:59 +01:00
Delete intermediate experiments
This commit is contained in:
parent
0c8febd17d
commit
f6629e95dc
4 changed files with 47 additions and 758 deletions
|
@ -1,339 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
|
||||||
import org.bouncycastle.bcpg.attr.ImageAttribute
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
|
||||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector
|
|
||||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
|
|
||||||
import org.bouncycastle.util.io.Streams
|
|
||||||
import org.pgpainless.algorithm.AlgorithmSuite
|
|
||||||
import org.pgpainless.algorithm.KeyFlag
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory
|
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
|
||||||
import org.pgpainless.policy.Policy
|
|
||||||
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenPGP key builder. This implementation supersedes the old [KeyRingBuilder].
|
|
||||||
*
|
|
||||||
* @param policy algorithm policy, which is consulted to determine suitable algorithms
|
|
||||||
* @param referenceTime reference time for key generation
|
|
||||||
* @param preferences set of preferred algorithms and enabled features
|
|
||||||
*/
|
|
||||||
open class GenerateOpenPgpKey(
|
|
||||||
private val policy: Policy,
|
|
||||||
private val referenceTime: Date = Date(),
|
|
||||||
private val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
|
|
||||||
) {
|
|
||||||
|
|
||||||
/** Builder for OpenPGP secret keys. */
|
|
||||||
abstract class OpinionatedPgpKeyBuilder(
|
|
||||||
protected val policy: Policy,
|
|
||||||
protected val referenceTime: Date,
|
|
||||||
protected val preferences: AlgorithmSuite
|
|
||||||
) {
|
|
||||||
|
|
||||||
/** Make sure, that the chosen [KeyType] is allowed. */
|
|
||||||
open fun sanitizePublicKeyAlgorithms(keyType: KeyType, policy: Policy) {
|
|
||||||
verifyAlgorithmComplianceWithPolicy(keyType, policy)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure, that the chosen [KeyType] complies to the given [Policy] by comparing it to
|
|
||||||
* the [Policy.PublicKeyAlgorithmPolicy].
|
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if [keyType] fails to be accepted by [policy]
|
|
||||||
*/
|
|
||||||
private fun verifyAlgorithmComplianceWithPolicy(keyType: KeyType, policy: Policy) {
|
|
||||||
val algorithm = keyType.algorithm
|
|
||||||
val bitStrength = keyType.bitStrength
|
|
||||||
require(policy.publicKeyAlgorithmPolicy.isAcceptable(algorithm, bitStrength)) {
|
|
||||||
"Public key algorithm policy violation: $algorithm with bit strength $bitStrength is not acceptable."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build an OpenPGP v4 key. The result will be a key compliant to RFC4880, RFC6637.
|
|
||||||
*
|
|
||||||
* @param keyType type of the primary key
|
|
||||||
* @param flags key flags for the primary key. Defaults to [KeyFlag.CERTIFY_OTHER].
|
|
||||||
* @return [OpinionatedV4KeyBuilder] which can be further modified, e.g. add subkeys, user-ids
|
|
||||||
* etc.
|
|
||||||
*/
|
|
||||||
fun buildV4Key(
|
|
||||||
keyType: KeyType,
|
|
||||||
flags: List<KeyFlag>? = listOf(KeyFlag.CERTIFY_OTHER)
|
|
||||||
): OpinionatedV4KeyBuilder =
|
|
||||||
OpinionatedV4KeyBuilder(keyType, flags, policy, referenceTime, preferences)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builder for version 4 OpenPGP keys.
|
|
||||||
*
|
|
||||||
* @param primaryKeyType type of the primary key
|
|
||||||
* @param primaryFlags list of key-flags for the primary key. Can be `null`.
|
|
||||||
* @param policy algorithm policy
|
|
||||||
* @param referenceTime reference time for key generation
|
|
||||||
* @param preferences set of algorithm preferences and enabled features for the key
|
|
||||||
*/
|
|
||||||
class OpinionatedV4KeyBuilder
|
|
||||||
internal constructor(
|
|
||||||
primaryKeyType: KeyType,
|
|
||||||
primaryFlags: List<KeyFlag>?,
|
|
||||||
policy: Policy,
|
|
||||||
referenceTime: Date,
|
|
||||||
preferences: AlgorithmSuite
|
|
||||||
) : OpinionatedPgpKeyBuilder(policy, referenceTime, preferences) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
require(primaryKeyType.canCertify) {
|
|
||||||
"KeyType $primaryKeyType MUST be certification capable."
|
|
||||||
}
|
|
||||||
sanitizePublicKeyAlgorithms(primaryKeyType, policy)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val primaryKey =
|
|
||||||
OpenPgpComponentKeyBuilder.V4PrimaryKeyBuilder(primaryKeyType, referenceTime, policy)
|
|
||||||
private val subkeys = mutableListOf<OpenPgpComponentKeyBuilder.V4SubkeyBuilder>()
|
|
||||||
private var addDirectKeySignature = true
|
|
||||||
|
|
||||||
private val preferencesCallback =
|
|
||||||
SelfSignatureSubpackets.applyHashed {
|
|
||||||
setPreferredSymmetricKeyAlgorithms(preferences.symmetricKeyAlgorithms)
|
|
||||||
setPreferredHashAlgorithms(preferences.hashAlgorithms)
|
|
||||||
setPreferredCompressionAlgorithms(preferences.compressionAlgorithms)
|
|
||||||
setFeatures(*preferences.features.toTypedArray())
|
|
||||||
primaryFlags?.let { setKeyFlags(*it.toTypedArray()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a direct-key signature to the primary key. If this method is called, the automatic
|
|
||||||
* addition of a direct-key signature in the final build-step will be omitted.
|
|
||||||
*
|
|
||||||
* @param subpacketsCallback callback to modify the subpackets of the direct-key signature.
|
|
||||||
*/
|
|
||||||
fun directKeySignature(
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
addDirectKeySignature = false
|
|
||||||
primaryKey.directKeySignature(
|
|
||||||
subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this method is called, the automatic addition of a direct-key signature in the final
|
|
||||||
* build-step will be omitted.
|
|
||||||
*/
|
|
||||||
fun noDirectKeySignature() = apply { addDirectKeySignature = false }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a user-id to the key. The subpackets of the binding signature are prepopulated,
|
|
||||||
* setting algorithm preferences and features. However, the subpackets can still be modified
|
|
||||||
* using the provided [subpacketsCallback].
|
|
||||||
*
|
|
||||||
* @param userId user-id to add
|
|
||||||
* @param subpacketsCallback callback to modify the user-id binding signatures subpackets.
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
fun addUserId(
|
|
||||||
userId: CharSequence,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
primaryKey.userId(
|
|
||||||
userId,
|
|
||||||
subpacketsCallback =
|
|
||||||
preferencesCallback.then(markFirstPrimaryUserId()).then(subpacketsCallback))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun markFirstPrimaryUserId() =
|
|
||||||
SelfSignatureSubpackets.applyHashed {
|
|
||||||
if (primaryKeyHasPrimaryUserId()) {
|
|
||||||
setPrimaryUserId(null)
|
|
||||||
} else {
|
|
||||||
setPrimaryUserId()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun primaryKeyHasPrimaryUserId() =
|
|
||||||
primaryKey.pair.publicKey.let { pk ->
|
|
||||||
pk.userIDs.asSequence().any { uid ->
|
|
||||||
pk.getSignaturesForID(uid).asSequence().any {
|
|
||||||
it.hashedSubPackets.isPrimaryUserID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a user-attribute to the key. The subpackets of the binding signature are
|
|
||||||
* prepopulated, setting algorithm preferences and features. However, the subpackets can
|
|
||||||
* still be modified using the provided [subpacketsCallback].
|
|
||||||
*
|
|
||||||
* @param attribute user-attribute to add
|
|
||||||
* @param subpacketsCallback callback to modify the user-attribute binding signatures
|
|
||||||
* subpackets.
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
fun addUserAttribute(
|
|
||||||
attribute: PGPUserAttributeSubpacketVector,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
primaryKey.userAttribute(
|
|
||||||
attribute, subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the contents of a JPEG input stream as image attribute to the key.
|
|
||||||
*
|
|
||||||
* @param jpegInputStream input stream containing a JPEG image
|
|
||||||
* @param subpacketsCallback callback to modify the user-attribute binding signature
|
|
||||||
* subpackets.
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun addJpegImage(
|
|
||||||
jpegInputStream: InputStream,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
PGPUserAttributeSubpacketVectorGenerator()
|
|
||||||
.apply { setImageAttribute(ImageAttribute.JPEG, Streams.readAll(jpegInputStream)) }
|
|
||||||
.generate()
|
|
||||||
.let { addUserAttribute(it, subpacketsCallback) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a subkey to the key. The subpackets of the binding signature will be populated with
|
|
||||||
* issuer information, the passed in [bindingTime] as signature creation time and given
|
|
||||||
* key-flags (if non-null). You can further manipulate the subpackets by passing in an
|
|
||||||
* appropriate [subpacketsCallback].
|
|
||||||
*
|
|
||||||
* @param keyType type of the key
|
|
||||||
* @param creationTime creation time of the key. Defaults to [referenceTime]
|
|
||||||
* @param bindingTime creation time of the binding signature. Defaults to [creationTime]
|
|
||||||
* @param keyFlags list of key-flags for the subkey.
|
|
||||||
* @param subpacketsCallback callback to modify the subpackets of the binding signature
|
|
||||||
*/
|
|
||||||
fun addSubkey(
|
|
||||||
keyType: KeyType,
|
|
||||||
creationTime: Date = referenceTime,
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
keyFlags: List<KeyFlag>? = null,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) =
|
|
||||||
addSubkey(
|
|
||||||
OpenPgpComponentKeyBuilder.V4SubkeyBuilder(
|
|
||||||
keyType, creationTime, policy, primaryKey),
|
|
||||||
SelfSignatureSubpackets.applyHashed {
|
|
||||||
setSignatureCreationTime(bindingTime)
|
|
||||||
keyFlags?.let { setKeyFlags(it) }
|
|
||||||
}
|
|
||||||
.then(subpacketsCallback))
|
|
||||||
|
|
||||||
fun addSubkey(
|
|
||||||
subkeyBuilder: OpenPgpComponentKeyBuilder.V4SubkeyBuilder,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
sanitizePublicKeyAlgorithms(subkeyBuilder.type, policy)
|
|
||||||
subkeys.add(subkeyBuilder.bindingSignature(subpacketsCallback = subpacketsCallback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new subkey to be used for encryption. The binding signature will mark the key as
|
|
||||||
* encryption-capable using both [KeyFlag.ENCRYPT_COMMS] and [KeyFlag.ENCRYPT_STORAGE].
|
|
||||||
*
|
|
||||||
* @param keyType type of the encryption subkey
|
|
||||||
* @param creationTime time of creation of the subkey
|
|
||||||
* @param bindingTime creation time of the binding signature
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
fun addEncryptionSubkey(
|
|
||||||
keyType: KeyType,
|
|
||||||
creationTime: Date = referenceTime,
|
|
||||||
bindingTime: Date = creationTime
|
|
||||||
) = apply {
|
|
||||||
require(keyType.canEncryptCommunication || keyType.canEncryptStorage) {
|
|
||||||
"KeyType $keyType cannot be used for encryption keys."
|
|
||||||
}
|
|
||||||
addSubkey(
|
|
||||||
keyType,
|
|
||||||
creationTime,
|
|
||||||
bindingTime,
|
|
||||||
listOf(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new subkey to be used for creating data signatures. The binding signature will mark
|
|
||||||
* the key as signing-capable using [KeyFlag.SIGN_DATA].
|
|
||||||
*
|
|
||||||
* @param keyType type of the signing subkey
|
|
||||||
* @param creationTime time of creation of the subkey
|
|
||||||
* @param bindingTime creation time of the binding signature
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
fun addSigningSubkey(
|
|
||||||
keyType: KeyType,
|
|
||||||
creationTime: Date = referenceTime,
|
|
||||||
bindingTime: Date = creationTime
|
|
||||||
) = apply {
|
|
||||||
require(keyType.canSign) { "KeyType $keyType cannot be used for signing keys." }
|
|
||||||
addSubkey(keyType, creationTime, bindingTime, listOf(KeyFlag.SIGN_DATA))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the finished OpenPGP key. By default, the key will not be protected using
|
|
||||||
* passphrases. To set a passphrase, you can provide
|
|
||||||
* [SecretKeyRingProtector.unlockAnyKeyWith] with a passphrase of your choice.
|
|
||||||
*
|
|
||||||
* @param protector protector to secure the secret keys using passphrases. Defaults to
|
|
||||||
* [SecretKeyRingProtector.unprotectedKeys].
|
|
||||||
* @return OpenPGP Secret Key
|
|
||||||
*/
|
|
||||||
fun build(
|
|
||||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
|
||||||
): PGPSecretKeyRing {
|
|
||||||
|
|
||||||
// add a direct key sig with preferences
|
|
||||||
if (addDirectKeySignature) {
|
|
||||||
primaryKey.directKeySignature(subpacketsCallback = preferencesCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PGPSecretKeyRing(
|
|
||||||
mutableListOf(
|
|
||||||
toSecretKey(
|
|
||||||
primaryKey, true, protector.getEncryptor(primaryKey.pair.keyID)))
|
|
||||||
.plus(
|
|
||||||
subkeys.map {
|
|
||||||
toSecretKey(it, false, protector.getEncryptor(it.pair.keyID))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a [OpenPgpComponentKeyBuilder.V4ComponentKeyBuilder] to a version 4
|
|
||||||
* [PGPSecretKey].
|
|
||||||
*
|
|
||||||
* @param key builder
|
|
||||||
* @param isPrimaryKey if true, the result will be a primary key, a subkey otherwise.
|
|
||||||
* @param encryptor encryptor to protect the secret key. Can be null for unencrypted keys.
|
|
||||||
*/
|
|
||||||
private fun toSecretKey(
|
|
||||||
key: OpenPgpComponentKeyBuilder.V4ComponentKeyBuilder<*>,
|
|
||||||
isPrimaryKey: Boolean,
|
|
||||||
encryptor: PBESecretKeyEncryptor?
|
|
||||||
): PGPSecretKey {
|
|
||||||
return PGPSecretKey(
|
|
||||||
key.pair.privateKey,
|
|
||||||
key.pair.publicKey,
|
|
||||||
ImplementationFactory.getInstance().v4FingerprintCalculator,
|
|
||||||
isPrimaryKey,
|
|
||||||
encryptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
|
||||||
|
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.util.*
|
|
||||||
import org.pgpainless.bouncycastle.extensions.toPrimaryKeyFormat
|
|
||||||
import org.pgpainless.bouncycastle.extensions.toSubkeyFormat
|
|
||||||
import org.bouncycastle.openpgp.PGPKeyPair
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
|
||||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector
|
|
||||||
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.policy.Policy
|
|
||||||
import org.pgpainless.provider.ProviderFactory
|
|
||||||
import org.pgpainless.signature.builder.DirectKeySelfSignatureBuilder
|
|
||||||
import org.pgpainless.signature.builder.PrimaryKeyBindingSignatureBuilder
|
|
||||||
import org.pgpainless.signature.builder.SelfSignatureBuilder
|
|
||||||
import org.pgpainless.signature.builder.SubkeyBindingSignatureBuilder
|
|
||||||
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
|
||||||
|
|
||||||
class OpenPgpComponentKeyBuilder {
|
|
||||||
|
|
||||||
abstract class V4ComponentKeyBuilder<T : V4ComponentKeyBuilder<T>>(
|
|
||||||
val type: KeyType,
|
|
||||||
val creationTime: Date,
|
|
||||||
val certificateCreationTime: Date = Date(),
|
|
||||||
val policy: Policy
|
|
||||||
) {
|
|
||||||
|
|
||||||
internal var pair = generateKeyPair()
|
|
||||||
|
|
||||||
fun subkey(type: KeyType, creationTime: Date = certificateCreationTime): V4SubkeyBuilder =
|
|
||||||
V4SubkeyBuilder(type, creationTime, policy, primaryKey())
|
|
||||||
|
|
||||||
internal abstract fun primaryKey(): V4PrimaryKeyBuilder
|
|
||||||
|
|
||||||
// Note: The result is a *primary* key pair, so subkeys need adjustment (toPrimaryOrSubkey)
|
|
||||||
private fun generateKeyPair(): PGPKeyPair {
|
|
||||||
// Create raw Key Pair
|
|
||||||
val keyPair =
|
|
||||||
KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
|
|
||||||
.also { it.initialize(type.algorithmSpec) }
|
|
||||||
.generateKeyPair()
|
|
||||||
|
|
||||||
// Form PGP Key Pair
|
|
||||||
return ImplementationFactory.getInstance()
|
|
||||||
.getPGPV4KeyPair(type.algorithm, keyPair, creationTime)
|
|
||||||
.let { toPrimaryOrSubkey(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure, the PGP key packet is a subkey packet for subkeys, and a primary key packet
|
|
||||||
* for primary keys.
|
|
||||||
*/
|
|
||||||
protected abstract fun toPrimaryOrSubkey(keyPair: PGPKeyPair): PGPKeyPair
|
|
||||||
}
|
|
||||||
|
|
||||||
class V4PrimaryKeyBuilder(type: KeyType, creationTime: Date, policy: Policy) :
|
|
||||||
V4ComponentKeyBuilder<V4PrimaryKeyBuilder>(type, creationTime, policy = policy) {
|
|
||||||
|
|
||||||
fun userId(
|
|
||||||
userId: CharSequence,
|
|
||||||
certificationType: CertificationType = CertificationType.POSITIVE,
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
hashAlgorithm: HashAlgorithm =
|
|
||||||
policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
val sig =
|
|
||||||
buildCertificationFor(
|
|
||||||
userId, certificationType, bindingTime, hashAlgorithm, subpacketsCallback)
|
|
||||||
pair =
|
|
||||||
PGPKeyPair(
|
|
||||||
PGPPublicKey.addCertification(pair.publicKey, userId.toString(), sig),
|
|
||||||
pair.privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildCertificationFor(
|
|
||||||
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 userAttribute(
|
|
||||||
userAttribute: PGPUserAttributeSubpacketVector,
|
|
||||||
certificationType: CertificationType = CertificationType.POSITIVE,
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
hashAlgorithm: HashAlgorithm =
|
|
||||||
policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
val sig =
|
|
||||||
buildCertificationFor(
|
|
||||||
userAttribute,
|
|
||||||
certificationType,
|
|
||||||
bindingTime,
|
|
||||||
hashAlgorithm,
|
|
||||||
subpacketsCallback)
|
|
||||||
pair =
|
|
||||||
PGPKeyPair(
|
|
||||||
PGPPublicKey.addCertification(pair.publicKey, userAttribute, sig),
|
|
||||||
pair.privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildCertificationFor(
|
|
||||||
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 directKeySignature(
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
algorithmSuite: AlgorithmSuite = policy.keyGenerationAlgorithmSuite,
|
|
||||||
hashAlgorithm: HashAlgorithm =
|
|
||||||
policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm(),
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
val sig =
|
|
||||||
buildDirectKeySignature(
|
|
||||||
bindingTime, algorithmSuite, hashAlgorithm, subpacketsCallback)
|
|
||||||
pair = PGPKeyPair(PGPPublicKey.addCertification(pair.publicKey, sig), pair.privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildDirectKeySignature(
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toPrimaryOrSubkey(keyPair: PGPKeyPair) = keyPair.toPrimaryKeyFormat()
|
|
||||||
|
|
||||||
override fun primaryKey() = this
|
|
||||||
}
|
|
||||||
|
|
||||||
class V4SubkeyBuilder(
|
|
||||||
type: KeyType,
|
|
||||||
creationTime: Date,
|
|
||||||
policy: Policy,
|
|
||||||
private val primaryKeyBuilder: V4PrimaryKeyBuilder
|
|
||||||
) : V4ComponentKeyBuilder<V4SubkeyBuilder>(type, creationTime, policy = policy) {
|
|
||||||
|
|
||||||
fun bindingSignature(
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
hashAlgorithm: HashAlgorithm =
|
|
||||||
policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
) = apply {
|
|
||||||
val sig = buildBindingSignature(bindingTime, hashAlgorithm, subpacketsCallback)
|
|
||||||
pair = PGPKeyPair(PGPPublicKey.addCertification(pair.publicKey, sig), pair.privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildBindingSignature(
|
|
||||||
bindingTime: Date = creationTime,
|
|
||||||
hashAlgorithm: HashAlgorithm =
|
|
||||||
policy.certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm,
|
|
||||||
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
||||||
): PGPSignature {
|
|
||||||
val builder =
|
|
||||||
SubkeyBindingSignatureBuilder(
|
|
||||||
primaryKeyBuilder.pair.privateKey,
|
|
||||||
primaryKeyBuilder.pair.publicKey,
|
|
||||||
hashAlgorithm)
|
|
||||||
|
|
||||||
builder.hashedSubpackets.setSignatureCreationTime(bindingTime)
|
|
||||||
builder.applyCallback(subpacketsCallback)
|
|
||||||
|
|
||||||
// For signing-keys: Add backsig
|
|
||||||
if (builder.hashedSubpackets.getKeyFlags().orEmpty().contains(KeyFlag.SIGN_DATA) &&
|
|
||||||
builder.hashedSubpackets.getEmbeddedSignaturePackets().isEmpty()) {
|
|
||||||
|
|
||||||
// Create back-sig
|
|
||||||
val backSigBuilder =
|
|
||||||
PrimaryKeyBindingSignatureBuilder(
|
|
||||||
pair.privateKey, pair.publicKey, hashAlgorithm)
|
|
||||||
|
|
||||||
backSigBuilder.hashedSubpackets.setSignatureCreationTime(bindingTime)
|
|
||||||
|
|
||||||
val backSig = backSigBuilder.build(primaryKey().pair.publicKey)
|
|
||||||
builder.hashedSubpackets.addEmbeddedSignature(backSig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build(pair.publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toPrimaryOrSubkey(keyPair: PGPKeyPair) = keyPair.toSubkeyFormat()
|
|
||||||
|
|
||||||
override fun primaryKey() = primaryKeyBuilder.primaryKey()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
|
||||||
|
|
||||||
import java.io.InputStream
|
|
||||||
import org.bouncycastle.util.io.Streams
|
|
||||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import org.opentest4j.TestAbortedException
|
|
||||||
import org.pgpainless.PGPainless
|
|
||||||
import org.pgpainless.algorithm.KeyFlag
|
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
|
||||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
|
||||||
import org.pgpainless.key.generation.type.rsa.RsaLength
|
|
||||||
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
|
||||||
import org.pgpainless.policy.Policy
|
|
||||||
import org.pgpainless.util.DateUtil
|
|
||||||
|
|
||||||
class GenerateOpenPgpKeyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test() {
|
|
||||||
val date = DateUtil.parseUTCDate("2020-04-01 10:00:00 UTC")
|
|
||||||
val key =
|
|
||||||
GenerateOpenPgpKey(Policy(), date)
|
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
|
||||||
.addUserId("Alice")
|
|
||||||
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
|
|
||||||
.addSigningSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
|
|
||||||
.build(SecretKeyRingProtector.unprotectedKeys())
|
|
||||||
println(PGPainless.asciiArmor(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun minimal() {
|
|
||||||
val key =
|
|
||||||
GenerateOpenPgpKey(Policy())
|
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
|
||||||
.build()
|
|
||||||
println(PGPainless.asciiArmor(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun minimalWithUserId() {
|
|
||||||
val key =
|
|
||||||
GenerateOpenPgpKey(Policy())
|
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519), listOf(KeyFlag.CERTIFY_OTHER))
|
|
||||||
.addUserId("Alice <alice@pgpainless.org>")
|
|
||||||
.build()
|
|
||||||
println(PGPainless.asciiArmor(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun primaryKeyMustBeCertificationCapable() {
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
GenerateOpenPgpKey(Policy())
|
|
||||||
// XDH is not signing/certification capable
|
|
||||||
.buildV4Key(KeyType.XDH(XDHSpec._X25519))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun encryptionSubkeyMustBeEncryptionCapable() {
|
|
||||||
val builder = GenerateOpenPgpKey(Policy()).buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
|
||||||
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
builder.addEncryptionSubkey(KeyType.EDDSA(EdDSACurve._Ed25519))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun signingSubkeysMustBeSigningCapable() {
|
|
||||||
val builder = GenerateOpenPgpKey(Policy()).buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
|
||||||
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
builder.addSigningSubkey(KeyType.XDH(XDHSpec._X25519))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testKeyGenerationWithUnacceptablePKAlgorithmFails() {
|
|
||||||
// Policy only allows RSA 4096 algorithms
|
|
||||||
val policy =
|
|
||||||
Policy(
|
|
||||||
publicKeyAlgorithmPolicy =
|
|
||||||
Policy.PublicKeyAlgorithmPolicy(mapOf(PublicKeyAlgorithm.RSA_GENERAL to 4096)),
|
|
||||||
)
|
|
||||||
val builder = GenerateOpenPgpKey(policy)
|
|
||||||
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
builder.buildV4Key(KeyType.RSA(RsaLength._3072)) // too weak
|
|
||||||
}
|
|
||||||
|
|
||||||
val v4Builder = builder.buildV4Key(KeyType.RSA(RsaLength._4096)) // ok
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
v4Builder.addSigningSubkey(KeyType.RSA(RsaLength._2048)) // too weak
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testKeyGenerationWithJPEGAttribute() {
|
|
||||||
val key =
|
|
||||||
GenerateOpenPgpKey(Policy())
|
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
|
||||||
.addJpegImage(requireResource("suzanne.jpg"))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
assertTrue(key.publicKey.userAttributes.hasNext())
|
|
||||||
assertArrayEquals(
|
|
||||||
Streams.readAll(requireResource("suzanne.jpg")),
|
|
||||||
key.publicKey.userAttributes.next().imageAttribute.imageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requireResource(resourceName: String): InputStream {
|
|
||||||
return javaClass.classLoader.getResourceAsStream(resourceName)
|
|
||||||
?: throw TestAbortedException(
|
|
||||||
"Cannot read resource $resourceName: InputStream is null.")
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TestInterface<T : TestInterface<T>> {
|
|
||||||
fun doSomething(): T
|
|
||||||
}
|
|
||||||
|
|
||||||
class LowerTestClass : TestInterface<LowerTestClass> {
|
|
||||||
override fun doSomething(): LowerTestClass {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestClass : TestInterface<TestClass> {
|
|
||||||
|
|
||||||
override fun doSomething(): TestClass {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun lower(): LowerTestClass {
|
|
||||||
return LowerTestClass()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
|
@ -24,13 +24,14 @@ class MalformedKeyGenerationTest {
|
||||||
fun malformedPrimaryUserIdSubpacket() {
|
fun malformedPrimaryUserIdSubpacket() {
|
||||||
val userId = "Alice <alice@pgpainless.org>"
|
val userId = "Alice <alice@pgpainless.org>"
|
||||||
val key =
|
val key =
|
||||||
GenerateOpenPgpKey(Policy())
|
OpenPgpKeyGenerator.buildV4(Policy())
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
|
||||||
.addUserId(
|
addUserId(
|
||||||
userId,
|
userId,
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setPrimaryUserId(PrimaryUserID(false, false, byteArrayOf(0x02)))
|
setPrimaryUserId(PrimaryUserID(false, false, byteArrayOf(0x02)))
|
||||||
})
|
})
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
println(PGPainless.asciiArmor(key))
|
println(PGPainless.asciiArmor(key))
|
||||||
|
@ -42,13 +43,14 @@ class MalformedKeyGenerationTest {
|
||||||
@Test
|
@Test
|
||||||
fun malformedExportableSubpacket() {
|
fun malformedExportableSubpacket() {
|
||||||
val key =
|
val key =
|
||||||
GenerateOpenPgpKey(Policy())
|
OpenPgpKeyGenerator.buildV4(Policy())
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
|
||||||
.addUserId(
|
addUserId(
|
||||||
"Alice <alice@pgpainless.org>",
|
"Alice <alice@pgpainless.org>",
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setExportable(Exportable(false, false, byteArrayOf(0x03)))
|
setExportable(Exportable(false, false, byteArrayOf(0x03)))
|
||||||
})
|
})
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
println(PGPainless.asciiArmor(key))
|
println(PGPainless.asciiArmor(key))
|
||||||
|
@ -60,13 +62,14 @@ class MalformedKeyGenerationTest {
|
||||||
@Test
|
@Test
|
||||||
fun malformedRevocableSubpacket() {
|
fun malformedRevocableSubpacket() {
|
||||||
val key =
|
val key =
|
||||||
GenerateOpenPgpKey(Policy())
|
OpenPgpKeyGenerator.buildV4(Policy())
|
||||||
.buildV4Key(KeyType.EDDSA(EdDSACurve._Ed25519))
|
.setPrimaryKey(KeyType.EDDSA(EdDSACurve._Ed25519)) {
|
||||||
.addUserId(
|
addUserId(
|
||||||
"Alice <alice@pgpainless.org>",
|
"Alice <alice@pgpainless.org>",
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setRevocable(Revocable(false, false, byteArrayOf(0x04)))
|
setRevocable(Revocable(false, false, byteArrayOf(0x04)))
|
||||||
})
|
})
|
||||||
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
println(PGPainless.asciiArmor(key))
|
println(PGPainless.asciiArmor(key))
|
||||||
|
@ -79,27 +82,28 @@ class MalformedKeyGenerationTest {
|
||||||
fun primaryUserIdOnDirectKeySig() {
|
fun primaryUserIdOnDirectKeySig() {
|
||||||
val policy = Policy()
|
val policy = Policy()
|
||||||
val key =
|
val key =
|
||||||
GenerateOpenPgpKey(policy)
|
OpenPgpKeyGenerator.buildV4(policy)
|
||||||
.buildV4Key(
|
.setPrimaryKey(
|
||||||
KeyType.EDDSA(EdDSACurve._Ed25519),
|
KeyType.EDDSA(EdDSACurve._Ed25519),
|
||||||
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
|
listOf(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) {
|
||||||
.directKeySignature(
|
addDirectKeySignature(
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setPrimaryUserId()
|
setPrimaryUserId()
|
||||||
setPreferredHashAlgorithms(HashAlgorithm.SHA224)
|
setPreferredHashAlgorithms(HashAlgorithm.SHA224)
|
||||||
})
|
})
|
||||||
.addUserId(
|
addUserId(
|
||||||
"Alice <alice@pgpainless.org>",
|
"Alice <alice@pgpainless.org>",
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setPrimaryUserId(null)
|
setPrimaryUserId(null)
|
||||||
setPreferredHashAlgorithms(HashAlgorithm.SHA256)
|
setPreferredHashAlgorithms(HashAlgorithm.SHA256)
|
||||||
})
|
})
|
||||||
.addUserId(
|
addUserId(
|
||||||
"Bob <bob@pgpainless.org>",
|
"Bob <bob@pgpainless.org>",
|
||||||
SelfSignatureSubpackets.applyHashed {
|
SelfSignatureSubpackets.applyHashed {
|
||||||
setPrimaryUserId()
|
setPrimaryUserId()
|
||||||
setPreferredHashAlgorithms(HashAlgorithm.SHA384)
|
setPreferredHashAlgorithms(HashAlgorithm.SHA384)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
|
.addEncryptionSubkey(KeyType.XDH(XDHSpec._X25519))
|
||||||
.build()
|
.build()
|
||||||
println(key.toAsciiArmor())
|
println(key.toAsciiArmor())
|
||||||
|
|
Loading…
Reference in a new issue