mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-10 18:59:39 +02:00
Kotlin conversion: KeyRingBuilder
This commit is contained in:
parent
43335cbcd3
commit
eaef1fe44a
6 changed files with 293 additions and 384 deletions
|
@ -0,0 +1,236 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
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.key.protection.UnlockSecretKey
|
||||
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
|
||||
import java.io.IOException
|
||||
import java.security.KeyPairGenerator
|
||||
import java.util.*
|
||||
|
||||
const val MILLIS_IN_YEAR = 1000L * 60 * 60 * 24 * 365
|
||||
|
||||
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 = UnlockSecretKey.unlockSecretKey(secretKeyRing.secretKey, 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().signatureHashAlgorithmPolicy.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 {
|
||||
@JvmStatic
|
||||
fun generateKeyPair(spec: KeySpec): PGPKeyPair {
|
||||
spec.keyType.let { type ->
|
||||
// Create raw Key Pair
|
||||
val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.getProvider())
|
||||
.also { it.initialize(type.algorithmSpec) }
|
||||
.generateKeyPair()
|
||||
|
||||
val keyCreationDate = spec.keyCreationDate ?: Date()
|
||||
// Form PGP Key Pair
|
||||
return ImplementationFactory.getInstance()
|
||||
.getPGPKeyPair(type.algorithm, keyPair, keyCreationDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
|
||||
interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
||||
|
||||
fun setPrimaryKey(keySpec: KeySpec): B
|
||||
|
||||
fun setPrimaryKey(builder: KeySpecBuilder): B = setPrimaryKey(builder.build())
|
||||
|
||||
fun addSubkey(keySpec: KeySpec): B
|
||||
|
||||
fun addSubkey(builder: KeySpecBuilder): B = addSubkey(builder.build())
|
||||
|
||||
fun addUserId(userId: CharSequence): B
|
||||
|
||||
fun addUserId(userId: ByteArray): B
|
||||
|
||||
fun setExpirationDate(expirationDate: Date?): B
|
||||
|
||||
fun setPassphrase(passphrase: Passphrase): B
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class)
|
||||
fun build(): PGPSecretKeyRing
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import java.util.*
|
||||
|
||||
interface KeySpecBuilderInterface {
|
||||
|
||||
fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder
|
||||
|
||||
fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder
|
||||
|
||||
fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder
|
||||
|
||||
fun setKeyCreationDate(creationDate: Date): KeySpecBuilder
|
||||
|
||||
fun build(): KeySpec
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue