From 513a5cacfc7567d59a735a50c2af236d3df7892a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 15:34:56 +0200 Subject: [PATCH] Kotlin conversion: KeyRingBuider / KeySpecBuilderInterface --- .../key/generation/KeyRingBuilder.java | 322 ------------------ .../generation/KeyRingBuilderInterface.java | 45 --- .../generation/KeySpecBuilderInterface.java | 26 -- .../key/generation/KeyRingBuilder.kt | 244 +++++++++++++ .../key/generation/KeyRingBuilderInterface.kt | 34 ++ .../key/generation/KeySpecBuilderInterface.kt | 23 ++ 6 files changed, 301 insertions(+), 393 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java deleted file mode 100644 index 208c17b6..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.bcpg.sig.KeyFlags; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyPair; -import org.bouncycastle.openpgp.PGPKeyRingGenerator; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.PGPSignatureGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; -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.HashAlgorithm; -import org.pgpainless.algorithm.KeyFlag; -import org.pgpainless.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.generation.type.KeyType; -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; - -public class KeyRingBuilder implements KeyRingBuilderInterface { - - private static final long YEAR_IN_SECONDS = 1000L * 60 * 60 * 24 * 365; - - private KeySpec primaryKeySpec; - private final List subkeySpecs = new ArrayList<>(); - private final Map userIds = new LinkedHashMap<>(); - private Passphrase passphrase = Passphrase.emptyPassphrase(); - private Date expirationDate = new Date(System.currentTimeMillis() + YEAR_IN_SECONDS * 5); // Expiration in 5 years - - @Override - public KeyRingBuilder setPrimaryKey(@Nonnull KeySpec keySpec) { - verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()); - verifyMasterKeyCanCertify(keySpec); - this.primaryKeySpec = keySpec; - return this; - } - - @Override - public KeyRingBuilder addSubkey(@Nonnull KeySpec keySpec) { - verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy()); - this.subkeySpecs.add(keySpec); - return this; - } - - @Override - public KeyRingBuilder addUserId(@Nonnull String userId) { - this.userIds.put(userId.trim(), null); - return this; - } - - public KeyRingBuilder addUserId( - @Nonnull String userId, - @Nullable SelfSignatureSubpackets.Callback subpacketsCallback) { - this.userIds.put(userId.trim(), subpacketsCallback); - return this; - } - - @Override - public KeyRingBuilder addUserId(@Nonnull byte[] userId) { - return addUserId(Strings.fromUTF8ByteArray(userId)); - } - - @Override - public KeyRingBuilder setExpirationDate(@Nullable Date expirationDate) { - if (expirationDate == null) { - // No expiration - this.expirationDate = null; - return this; - } - - Date now = new Date(); - if (now.after(expirationDate)) { - throw new IllegalArgumentException("Expiration date must be in the future."); - } - this.expirationDate = expirationDate; - return this; - } - - @Override - public KeyRingBuilder setPassphrase(@Nonnull Passphrase passphrase) { - this.passphrase = passphrase; - return this; - } - - private void verifyKeySpecCompliesToPolicy(KeySpec keySpec, Policy policy) { - PublicKeyAlgorithm publicKeyAlgorithm = keySpec.getKeyType().getAlgorithm(); - int bitStrength = keySpec.getKeyType().getBitStrength(); - - if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { - throw new IllegalArgumentException("Public key algorithm policy violation: " + - publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable."); - } - } - - private void verifyMasterKeyCanCertify(KeySpec spec) { - if (!hasCertifyOthersFlag(spec)) { - throw new IllegalArgumentException("Certification Key MUST have KeyFlag CERTIFY_OTHER"); - } - if (!keyIsCertificationCapable(spec)) { - throw new IllegalArgumentException("Key algorithm " + spec.getKeyType().getName() + " is not capable of creating certifications."); - } - } - - private boolean hasCertifyOthersFlag(KeySpec keySpec) { - KeyFlags keyFlags = keySpec.getSubpacketGenerator().getKeyFlagsSubpacket(); - return keyFlags != null && KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.CERTIFY_OTHER); - } - - private boolean keyIsCertificationCapable(KeySpec keySpec) { - return keySpec.getKeyType().canCertify(); - } - - @Override - public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException { - PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator(); - PBESecretKeyEncryptor secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator); - PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor(); - - passphrase.clear(); - - // Generate Primary Key - PGPKeyPair certKey = generateKeyPair(primaryKeySpec); - PGPContentSignerBuilder signer = buildContentSigner(certKey); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signer); - - SignatureSubpackets hashedSubPacketGenerator = primaryKeySpec.getSubpacketGenerator(); - hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.getPublicKey()); - if (expirationDate != null) { - hashedSubPacketGenerator.setKeyExpirationTime(certKey.getPublicKey(), expirationDate); - } - if (!userIds.isEmpty()) { - hashedSubPacketGenerator.setPrimaryUserId(); - } - - PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator(); - SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator); - PGPSignatureSubpacketVector hashedSubPackets = generator.generate(); - PGPKeyRingGenerator ringGenerator; - if (userIds.isEmpty()) { - ringGenerator = new PGPKeyRingGenerator( - certKey, - keyFingerprintCalculator, - hashedSubPackets, - null, - signer, - secretKeyEncryptor); - } else { - String primaryUserId = userIds.entrySet().iterator().next().getKey(); - ringGenerator = new PGPKeyRingGenerator( - SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey, - primaryUserId, keyFingerprintCalculator, - hashedSubPackets, null, signer, secretKeyEncryptor); - } - - addSubKeys(certKey, ringGenerator); - - // Generate secret key ring with only primary user id - PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing(); - - Iterator secretKeys = secretKeyRing.getSecretKeys(); - - // Attempt to add additional user-ids to the primary public key - PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey(); - PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor); - Iterator> userIdIterator = - this.userIds.entrySet().iterator(); - if (userIdIterator.hasNext()) { - userIdIterator.next(); // Skip primary user id - } - while (userIdIterator.hasNext()) { - Map.Entry additionalUserId = userIdIterator.next(); - String userIdString = additionalUserId.getKey(); - SelfSignatureSubpackets.Callback callback = additionalUserId.getValue(); - SelfSignatureSubpackets subpackets = null; - if (callback == null) { - subpackets = hashedSubPacketGenerator; - subpackets.setPrimaryUserId(null); - // additional user-ids are not primary - } else { - subpackets = SignatureSubpackets.createHashedSubpackets(primaryPubKey); - callback.modifyHashedSubpackets(subpackets); - } - signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey); - signatureGenerator.setHashedSubpackets( - SignatureSubpacketsHelper.toVector((SignatureSubpackets) subpackets)); - PGPSignature additionalUserIdSignature = - signatureGenerator.generateCertification(userIdString, primaryPubKey); - primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, - userIdString, additionalUserIdSignature); - } - - // "reassemble" secret key ring with modified primary key - PGPSecretKey primarySecKey = new PGPSecretKey( - privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor); - List secretKeyList = new ArrayList<>(); - secretKeyList.add(primarySecKey); - while (secretKeys.hasNext()) { - secretKeyList.add(secretKeys.next()); - } - secretKeyRing = new PGPSecretKeyRing(secretKeyList); - return secretKeyRing; - } - - private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator) - throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException { - for (KeySpec subKeySpec : subkeySpecs) { - PGPKeyPair subKey = generateKeyPair(subKeySpec); - if (subKeySpec.isInheritedSubPackets()) { - ringGenerator.addSubKey(subKey); - } else { - PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets(); - try { - hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary( - primaryKey, subKey, hashedSubpackets); - } catch (IOException e) { - throw new PGPException("Exception while adding primary key binding signature to signing subkey", e); - } - ringGenerator.addSubKey(subKey, hashedSubpackets, null); - } - } - } - - private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary( - PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets) - throws PGPException, IOException { - int keyFlagMask = hashedSubpackets.getKeyFlags(); - if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && - !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { - return hashedSubpackets; - } - - PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey)); - bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey()); - PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey()); - PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets); - subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig); - return subpacketGenerator.generate(); - } - - private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) { - HashAlgorithm hashAlgorithm = PGPainless.getPolicy() - .getSignatureHashAlgorithmPolicy().defaultHashAlgorithm(); - return ImplementationFactory.getInstance().getPGPContentSignerBuilder( - certKey.getPublicKey().getAlgorithm(), - hashAlgorithm.getAlgorithmId()); - } - - private PBESecretKeyEncryptor buildSecretKeyEncryptor(PGPDigestCalculator keyFingerprintCalculator) { - SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy() - .getSymmetricKeyEncryptionAlgorithmPolicy() - .getDefaultSymmetricKeyAlgorithm(); - if (!passphrase.isValid()) { - throw new IllegalStateException("Passphrase was cleared."); - } - return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted - ImplementationFactory.getInstance().getPBESecretKeyEncryptor( - keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase); - } - - private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException { - if (!passphrase.isValid()) { - throw new IllegalStateException("Passphrase was cleared."); - } - return passphrase.isEmpty() ? null : - ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); - } - - public static PGPKeyPair generateKeyPair(KeySpec spec) - throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException { - KeyType type = spec.getKeyType(); - KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(), - ProviderFactory.getProvider()); - certKeyGenerator.initialize(type.getAlgorithmSpec()); - - // Create raw Key Pair - KeyPair keyPair = certKeyGenerator.generateKeyPair(); - - Date keyCreationDate = spec.getKeyCreationDate() != null ? spec.getKeyCreationDate() : new Date(); - - // Form PGP key pair - PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance() - .getPGPKeyPair(type.getAlgorithm(), keyPair, keyCreationDate); - return pgpKeyPair; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java deleted file mode 100644 index ecff123b..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.util.Date; -import javax.annotation.Nonnull; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.pgpainless.key.util.UserId; -import org.pgpainless.util.Passphrase; - -public interface KeyRingBuilderInterface> { - - B setPrimaryKey(@Nonnull KeySpec keySpec); - - default B setPrimaryKey(@Nonnull KeySpecBuilder builder) { - return setPrimaryKey(builder.build()); - } - - B addSubkey(@Nonnull KeySpec keySpec); - - default B addSubkey(@Nonnull KeySpecBuilder builder) { - return addSubkey(builder.build()); - } - - default B addUserId(UserId userId) { - return addUserId(userId.toString()); - } - - B addUserId(@Nonnull String userId); - - B addUserId(@Nonnull byte[] userId); - - B setExpirationDate(@Nonnull Date expirationDate); - - B setPassphrase(@Nonnull Passphrase passphrase); - - PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException; -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java deleted file mode 100644 index 4a99bc8d..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeySpecBuilderInterface.java +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.generation; - -import javax.annotation.Nonnull; - -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -import org.pgpainless.algorithm.SymmetricKeyAlgorithm; - -import java.util.Date; - -public interface KeySpecBuilderInterface { - - KeySpecBuilder overridePreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... compressionAlgorithms); - - KeySpecBuilder overridePreferredHashAlgorithms(@Nonnull HashAlgorithm... preferredHashAlgorithms); - - KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms); - - KeySpecBuilder setKeyCreationDate(@Nonnull Date creationDate); - - KeySpec build(); -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt new file mode 100644 index 00000000..3620043e --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -0,0 +1,244 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// 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 { + + private var primaryKeySpec: KeySpec? = null + private val subKeySpecs = mutableListOf() + private val userIds = mutableMapOf() + 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(hasCertifyOthersFlag(spec)) { + "Certification Key MUST have the KeyFlag CERTIFY_OTHER." + } + require(keyIsCertificationCapable(spec)) { + "Key algorithm ${spec.keyType.name} is not capable of creation certifications." + } + } + + private fun hasCertifyOthersFlag(keySpec: KeySpec): Boolean { + val flags = keySpec.subpacketGenerator.keyFlagsSubpacket ?: return false + return KeyFlag.hasKeyFlag(flags.flags, KeyFlag.CERTIFY_OTHER) + } + + 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) + } + } + } + +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt new file mode 100644 index 00000000..0ed301fe --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilderInterface.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// 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> { + + 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 +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt new file mode 100644 index 00000000..fcafb9c1 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilderInterface.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// 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 +} \ No newline at end of file