2024-01-22 16:52:40 +01:00
|
|
|
// SPDX-FileCopyrightText: 2024 Paul Schaub <vanitasvitae@fsfe.org>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-01-06 01:31:12 +01:00
|
|
|
package org.pgpainless.key.generation
|
|
|
|
|
2024-02-02 16:34:04 +01:00
|
|
|
import java.io.IOException
|
|
|
|
import java.io.InputStream
|
2024-01-11 16:44:21 +01:00
|
|
|
import java.util.*
|
2024-02-02 16:34:04 +01:00
|
|
|
import org.bouncycastle.bcpg.attr.ImageAttribute
|
2024-01-22 16:52:40 +01:00
|
|
|
import org.bouncycastle.openpgp.PGPSecretKey
|
|
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
2024-01-11 16:44:21 +01:00
|
|
|
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector
|
2024-02-02 16:01:47 +01:00
|
|
|
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVectorGenerator
|
2024-01-22 16:52:40 +01:00
|
|
|
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
|
2024-02-02 16:01:47 +01:00
|
|
|
import org.bouncycastle.util.io.Streams
|
2024-01-22 16:52:40 +01:00
|
|
|
import org.pgpainless.algorithm.AlgorithmSuite
|
2024-01-11 16:44:21 +01:00
|
|
|
import org.pgpainless.algorithm.KeyFlag
|
2024-01-06 01:31:12 +01:00
|
|
|
import org.pgpainless.implementation.ImplementationFactory
|
|
|
|
import org.pgpainless.key.generation.type.KeyType
|
2024-01-22 16:52:40 +01:00
|
|
|
import org.pgpainless.key.protection.SecretKeyRingProtector
|
2024-01-11 16:44:21 +01:00
|
|
|
import org.pgpainless.policy.Policy
|
2024-01-08 13:51:16 +01:00
|
|
|
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
2024-01-06 01:31:12 +01:00
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2024-01-27 18:45:55 +01:00
|
|
|
open class GenerateOpenPgpKey(
|
|
|
|
private val policy: Policy,
|
|
|
|
private val referenceTime: Date = Date(),
|
|
|
|
private val preferences: AlgorithmSuite = policy.keyGenerationAlgorithmSuite
|
2024-01-22 16:52:40 +01:00
|
|
|
) {
|
2024-01-06 01:31:12 +01:00
|
|
|
|
2024-01-27 18:45:55 +01:00
|
|
|
abstract class OpenPgpKeyBuilder(
|
|
|
|
protected val policy: Policy,
|
|
|
|
protected val referenceTime: Date,
|
|
|
|
protected val preferences: AlgorithmSuite
|
|
|
|
) {
|
|
|
|
|
2024-02-02 16:34:04 +01:00
|
|
|
/** Make sure, that the chosen [KeyType] is allowed. */
|
2024-01-27 18:45:55 +01:00
|
|
|
open fun sanitizePublicKeyAlgorithms(keyType: KeyType, policy: Policy) {
|
|
|
|
verifyAlgorithmComplianceWithPolicy(keyType, policy)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-02-02 16:34:04 +01:00
|
|
|
* Make sure, that the chosen [KeyType] complies to the given [Policy] by comparing it to
|
|
|
|
* the [Policy.PublicKeyAlgorithmPolicy].
|
2024-01-27 18:45:55 +01:00
|
|
|
*
|
|
|
|
* @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."
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
/**
|
|
|
|
* 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].
|
2024-01-27 18:45:55 +01:00
|
|
|
* @return [V4GenerateOpenPgpKey] which can be further modified, e.g. add subkeys, user-ids etc.
|
2024-01-24 18:47:27 +01:00
|
|
|
*/
|
2024-01-06 01:31:12 +01:00
|
|
|
fun buildV4Key(
|
2024-01-22 16:52:40 +01:00
|
|
|
keyType: KeyType,
|
2024-01-24 18:47:27 +01:00
|
|
|
flags: List<KeyFlag>? = listOf(KeyFlag.CERTIFY_OTHER)
|
2024-02-02 16:34:04 +01:00
|
|
|
): V4GenerateOpenPgpKey =
|
|
|
|
V4GenerateOpenPgpKey(keyType, flags, policy, referenceTime, preferences)
|
2024-01-27 17:23:29 +01:00
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2024-01-27 18:45:55 +01:00
|
|
|
class V4GenerateOpenPgpKey
|
2024-01-24 18:47:27 +01:00
|
|
|
internal constructor(
|
|
|
|
primaryKeyType: KeyType,
|
|
|
|
primaryFlags: List<KeyFlag>?,
|
2024-01-22 16:52:40 +01:00
|
|
|
policy: Policy,
|
|
|
|
referenceTime: Date,
|
2024-01-24 18:47:27 +01:00
|
|
|
preferences: AlgorithmSuite
|
|
|
|
) : OpenPgpKeyBuilder(policy, referenceTime, preferences) {
|
2024-01-06 01:31:12 +01:00
|
|
|
|
2024-01-27 17:23:29 +01:00
|
|
|
init {
|
2024-01-27 18:45:55 +01:00
|
|
|
sanitizePublicKeyAlgorithms(primaryKeyType, policy)
|
2024-01-27 17:23:29 +01:00
|
|
|
}
|
|
|
|
|
2024-01-22 16:52:40 +01:00
|
|
|
private val primaryKey =
|
2024-01-24 18:47:27 +01:00
|
|
|
BaseOpenPgpKeyBuilder.BaseV4PrimaryKeyBuilder(primaryKeyType, referenceTime, policy)
|
2024-01-22 16:52:40 +01:00
|
|
|
private val subkeys = mutableListOf<BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder>()
|
2024-01-08 13:51:16 +01:00
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
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 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
|
|
|
|
*/
|
2024-01-22 16:52:40 +01:00
|
|
|
fun addUserId(
|
2024-01-11 16:44:21 +01:00
|
|
|
userId: CharSequence,
|
2024-01-24 11:27:42 +01:00
|
|
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
|
|
) = apply {
|
2024-01-24 18:47:27 +01:00
|
|
|
primaryKey.userId(
|
|
|
|
userId, subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
2024-01-24 11:27:42 +01:00
|
|
|
}
|
2024-01-11 16:44:21 +01:00
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2024-01-22 16:52:40 +01:00
|
|
|
fun addUserAttribute(
|
|
|
|
attribute: PGPUserAttributeSubpacketVector,
|
2024-01-24 11:27:42 +01:00
|
|
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
|
|
) = apply {
|
|
|
|
primaryKey.userAttribute(
|
2024-01-24 18:47:27 +01:00
|
|
|
attribute, subpacketsCallback = preferencesCallback.then(subpacketsCallback))
|
2024-01-24 11:27:42 +01:00
|
|
|
}
|
2024-01-11 16:44:21 +01:00
|
|
|
|
2024-02-02 16:01:47 +01:00
|
|
|
/**
|
2024-02-02 16:34:04 +01:00
|
|
|
* Add the contents of a JPEG input stream as image attribute to the key.
|
2024-02-02 16:01:47 +01:00
|
|
|
*
|
2024-02-02 16:34:04 +01:00
|
|
|
* @param jpegInputStream input stream containing a JPEG image
|
|
|
|
* @param subpacketsCallback callback to modify the user-attribute binding signature
|
|
|
|
* subpackets.
|
2024-02-02 16:01:47 +01:00
|
|
|
* @return this
|
|
|
|
*/
|
|
|
|
@Throws(IOException::class)
|
|
|
|
fun addJpegImage(
|
2024-02-02 16:34:04 +01:00
|
|
|
jpegInputStream: InputStream,
|
2024-02-02 16:01:47 +01:00
|
|
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
|
|
|
) = apply {
|
2024-02-02 16:34:04 +01:00
|
|
|
PGPUserAttributeSubpacketVectorGenerator()
|
|
|
|
.apply { setImageAttribute(ImageAttribute.JPEG, Streams.readAll(jpegInputStream)) }
|
|
|
|
.generate()
|
|
|
|
.let { addUserAttribute(it, subpacketsCallback) }
|
2024-02-02 16:01:47 +01:00
|
|
|
}
|
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2024-01-22 16:52:40 +01:00
|
|
|
fun addSubkey(
|
|
|
|
keyType: KeyType,
|
|
|
|
creationTime: Date = referenceTime,
|
2024-01-11 16:44:21 +01:00
|
|
|
bindingTime: Date = creationTime,
|
2024-01-24 18:47:27 +01:00
|
|
|
keyFlags: List<KeyFlag>? = null,
|
|
|
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
2024-01-22 16:52:40 +01:00
|
|
|
) =
|
|
|
|
addSubkey(
|
|
|
|
BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder(
|
|
|
|
keyType, creationTime, policy, primaryKey),
|
2024-01-24 18:47:27 +01:00
|
|
|
SelfSignatureSubpackets.applyHashed {
|
|
|
|
setSignatureCreationTime(bindingTime)
|
|
|
|
keyFlags?.let { setKeyFlags(it) }
|
|
|
|
}
|
|
|
|
.then(subpacketsCallback))
|
2024-01-22 16:52:40 +01:00
|
|
|
|
|
|
|
fun addSubkey(
|
|
|
|
subkeyBuilder: BaseOpenPgpKeyBuilder.BaseV4SubkeyBuilder,
|
2024-01-24 18:47:27 +01:00
|
|
|
subpacketsCallback: SelfSignatureSubpackets.Callback = SelfSignatureSubpackets.nop()
|
2024-01-11 16:44:21 +01:00
|
|
|
) = apply {
|
2024-01-27 18:45:55 +01:00
|
|
|
sanitizePublicKeyAlgorithms(subkeyBuilder.type, policy)
|
2024-01-24 18:47:27 +01:00
|
|
|
subkeys.add(subkeyBuilder.bindingSignature(subpacketsCallback = subpacketsCallback))
|
2024-01-11 16:44:21 +01:00
|
|
|
}
|
|
|
|
|
2024-01-22 16:52:40 +01:00
|
|
|
fun addEncryptionSubkey(
|
|
|
|
keyType: KeyType,
|
|
|
|
creationTime: Date = referenceTime,
|
|
|
|
bindingTime: Date = creationTime
|
|
|
|
) =
|
|
|
|
addSubkey(
|
|
|
|
keyType,
|
|
|
|
creationTime,
|
|
|
|
bindingTime,
|
|
|
|
listOf(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
|
|
|
|
|
|
|
|
fun addSigningSubkey(
|
|
|
|
keyType: KeyType,
|
|
|
|
creationTime: Date = referenceTime,
|
|
|
|
bindingTime: Date = creationTime
|
|
|
|
) = addSubkey(keyType, creationTime, bindingTime, listOf(KeyFlag.SIGN_DATA))
|
|
|
|
|
|
|
|
fun build(
|
|
|
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
|
|
|
): PGPSecretKeyRing {
|
2024-01-22 17:17:26 +01:00
|
|
|
|
2024-01-24 18:47:27 +01:00
|
|
|
// add a direct key sig with preferences
|
|
|
|
primaryKey.directKeySignature(subpacketsCallback = preferencesCallback)
|
2024-01-22 17:17:26 +01:00
|
|
|
|
2024-01-22 16:52:40 +01:00
|
|
|
return PGPSecretKeyRing(
|
|
|
|
mutableListOf(
|
2024-01-24 18:47:27 +01:00
|
|
|
toSecretKey(primaryKey, true, protector.getEncryptor(primaryKey.key.keyID)))
|
2024-01-22 16:52:40 +01:00
|
|
|
.plus(
|
|
|
|
subkeys.map {
|
2024-01-24 18:47:27 +01:00
|
|
|
toSecretKey(it, false, protector.getEncryptor(it.key.keyID))
|
2024-01-22 16:52:40 +01:00
|
|
|
}))
|
|
|
|
}
|
2024-01-11 16:44:21 +01:00
|
|
|
|
2024-01-22 16:52:40 +01:00
|
|
|
private fun toSecretKey(
|
|
|
|
key: BaseOpenPgpKeyBuilder.BaseV4KeyBuilder<*>,
|
|
|
|
isPrimaryKey: Boolean,
|
|
|
|
encryptor: PBESecretKeyEncryptor?
|
|
|
|
): PGPSecretKey {
|
|
|
|
return PGPSecretKey(
|
|
|
|
key.key.privateKey,
|
|
|
|
key.key.publicKey,
|
|
|
|
ImplementationFactory.getInstance().v4FingerprintCalculator,
|
|
|
|
isPrimaryKey,
|
|
|
|
encryptor)
|
2024-01-08 13:51:16 +01:00
|
|
|
}
|
|
|
|
}
|
2024-01-06 01:31:12 +01:00
|
|
|
}
|