pgpainless/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt

269 lines
11 KiB
Kotlin

// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.generation
import java.io.IOException
import java.security.KeyPairGenerator
import java.util.*
import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
import org.bouncycastle.openpgp.operator.PGPDigestCalculator
import org.bouncycastle.util.Strings
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.policy.Policy
import org.pgpainless.provider.ProviderFactory
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import org.pgpainless.util.Passphrase
class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
private var primaryKeySpec: KeySpec? = null
private val subKeySpecs = mutableListOf<KeySpec>()
private val userIds = mutableMapOf<String, SelfSignatureSubpackets.Callback?>()
private var passphrase = Passphrase.emptyPassphrase()
private var expirationDate: Date? = Date(System.currentTimeMillis() + (5 * MILLIS_IN_YEAR))
override fun setPrimaryKey(keySpec: KeySpec): KeyRingBuilder = apply {
verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy())
verifyPrimaryKeyCanCertify(keySpec)
this.primaryKeySpec = keySpec
}
override fun addSubkey(keySpec: KeySpec): KeyRingBuilder = apply {
verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy())
subKeySpecs.add(keySpec)
}
override fun addUserId(userId: CharSequence): KeyRingBuilder = apply {
userIds[userId.toString().trim()] = null
}
override fun addUserId(userId: ByteArray): KeyRingBuilder =
addUserId(Strings.fromUTF8ByteArray(userId))
override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
if (expirationDate == null) {
this.expirationDate = null
return@apply
}
this.expirationDate =
expirationDate.let {
require(Date() < expirationDate) { "Expiration date must be in the future." }
expirationDate
}
}
override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply {
this.passphrase = passphrase
}
private fun verifyKeySpecCompliesToPolicy(keySpec: KeySpec, policy: Policy) {
val algorithm = keySpec.keyType.algorithm
val bitStrength = keySpec.keyType.bitStrength
require(policy.publicKeyAlgorithmPolicy.isAcceptable(algorithm, bitStrength)) {
"Public key algorithm policy violation: $algorithm with bit strength $bitStrength is not acceptable."
}
}
private fun verifyPrimaryKeyCanCertify(spec: KeySpec) {
require(keyIsCertificationCapable(spec)) {
"Key algorithm ${spec.keyType.name} is not capable of creation certifications."
}
}
private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify
override fun build(): PGPSecretKeyRing {
val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator
val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
val secretKeyDecryptor = buildSecretKeyDecryptor()
passphrase.clear() // Passphrase was used above, so we can get rid of it
// generate primary key
requireNotNull(primaryKeySpec) { "Primary Key spec required." }
val certKey = generateKeyPair(primaryKeySpec!!)
val signer = buildContentSigner(certKey)
val signatureGenerator = PGPSignatureGenerator(signer)
val hashedSubPacketGenerator = primaryKeySpec!!.subpacketGenerator
hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.publicKey)
expirationDate?.let { hashedSubPacketGenerator.setKeyExpirationTime(certKey.publicKey, it) }
if (userIds.isNotEmpty()) {
hashedSubPacketGenerator.setPrimaryUserId()
}
val generator = PGPSignatureSubpacketGenerator()
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
val hashedSubPackets = generator.generate()
val ringGenerator =
if (userIds.isEmpty()) {
PGPKeyRingGenerator(
certKey,
keyFingerprintCalculator,
hashedSubPackets,
null,
signer,
secretKeyEncryptor)
} else {
userIds.keys.first().let { primaryUserId ->
PGPKeyRingGenerator(
SignatureType.POSITIVE_CERTIFICATION.code,
certKey,
primaryUserId,
keyFingerprintCalculator,
hashedSubPackets,
null,
signer,
secretKeyEncryptor)
}
}
addSubKeys(certKey, ringGenerator)
// Generate secret key ring with only primary userId
val secretKeyRing = ringGenerator.generateSecretKeyRing()
val secretKeys = secretKeyRing.secretKeys
// Attempt to add additional user-ids to the primary public key
var primaryPubKey = secretKeys.next().publicKey
val privateKey = secretKeyRing.secretKey.unlock(secretKeyDecryptor)
val userIdIterator = userIds.entries.iterator()
if (userIdIterator.hasNext()) {
userIdIterator.next() // Skip primary userId
}
while (userIdIterator.hasNext()) {
val additionalUserId = userIdIterator.next()
val userIdString = additionalUserId.key
val callback = additionalUserId.value
val subpackets =
if (callback == null) {
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
} else {
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also {
callback.modifyHashedSubpackets(it)
}
}
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets))
val additionalUserIdSignature =
signatureGenerator.generateCertification(userIdString, primaryPubKey)
primaryPubKey =
PGPPublicKey.addCertification(
primaryPubKey, userIdString, additionalUserIdSignature)
}
// Reassemble secret key ring with modified primary key
val primarySecretKey =
PGPSecretKey(
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
val secretKeyList = mutableListOf(primarySecretKey)
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next())
}
return PGPSecretKeyRing(secretKeyList)
}
private fun addSubKeys(primaryKey: PGPKeyPair, ringGenerator: PGPKeyRingGenerator) {
for (subKeySpec in subKeySpecs) {
val subKey = generateKeyPair(subKeySpec)
if (subKeySpec.isInheritedSubPackets) {
ringGenerator.addSubKey(subKey)
} else {
var hashedSubpackets = subKeySpec.subpackets
try {
hashedSubpackets =
addPrimaryKeyBindingSignatureIfNecessary(
primaryKey, subKey, hashedSubpackets)
} catch (e: IOException) {
throw PGPException(
"Exception while adding primary key binding signature to signing subkey.",
e)
}
ringGenerator.addSubKey(subKey, hashedSubpackets, null)
}
}
}
private fun addPrimaryKeyBindingSignatureIfNecessary(
primaryKey: PGPKeyPair,
subKey: PGPKeyPair,
hashedSubpackets: PGPSignatureSubpacketVector
): PGPSignatureSubpacketVector {
val keyFlagMask = hashedSubpackets.keyFlags
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
return hashedSubpackets
}
val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
val primaryKeyBindingSig =
bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
return subpacketGenerator.generate()
}
private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
val hashAlgorithm =
PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm
return ImplementationFactory.getInstance()
.getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
}
private fun buildSecretKeyEncryptor(
keyFingerprintCalculator: PGPDigestCalculator
): PBESecretKeyEncryptor? {
val keyEncryptionAlgorithm =
PGPainless.getPolicy()
.symmetricKeyEncryptionAlgorithmPolicy
.defaultSymmetricKeyAlgorithm
check(passphrase.isValid) { "Passphrase was cleared." }
return if (passphrase.isEmpty) null
else
ImplementationFactory.getInstance()
.getPBESecretKeyEncryptor(
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
}
private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
check(passphrase.isValid) { "Passphrase was cleared." }
return if (passphrase.isEmpty) null
else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase)
}
companion object {
const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365
@JvmStatic
@JvmOverloads
fun generateKeyPair(
spec: KeySpec,
creationTime: Date = spec.keyCreationDate ?: Date()
): PGPKeyPair {
spec.keyType.let { type ->
// 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()
.getPGPKeyPair(type.algorithm, keyPair, creationTime)
}
}
}
}