From 5de1e6a56db860aa4205b4947d97a7de828478df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 5 Mar 2025 15:15:13 +0100 Subject: [PATCH] Work on AlgorithmSuite --- .../main/kotlin/org/pgpainless/PGPainless.kt | 20 ++++- .../pgpainless/algorithm/AlgorithmSuite.kt | 46 +++++++++-- .../key/generation/KeyRingTemplates.kt | 6 +- .../key/generation/KeySpecBuilder.kt | 14 ++-- .../subpackets/BaseSignatureSubpackets.kt | 9 ++- .../subpackets/SelfSignatureSubpackets.kt | 2 + .../subpackets/SignatureSubpackets.kt | 3 +- .../key/generation/GenerateV6KeyTest.java | 78 +++++++++++++++++++ 8 files changed, 158 insertions(+), 20 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index f9e009cf..2b43958c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -14,6 +14,7 @@ import org.bouncycastle.openpgp.api.OpenPGPApi import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPImplementation import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator import org.bouncycastle.openpgp.api.OpenPGPKeyReader import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi import org.pgpainless.algorithm.OpenPGPKeyVersion @@ -43,8 +44,23 @@ class PGPainless( api = BcOpenPGPApi(implementation) } - fun generateKey(version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4): KeyRingTemplates = - KeyRingTemplates(version) + @JvmOverloads + fun generateKey( + version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4, + creationTime: Date = Date() + ): KeyRingTemplates = KeyRingTemplates(version, creationTime) + + @JvmOverloads + fun buildKey( + version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4, + creationTime: Date = Date() + ): OpenPGPKeyGenerator = + OpenPGPKeyGenerator( + implementation, version.numeric, version == OpenPGPKeyVersion.v6, creationTime) + .apply { + val genAlgs = algorithmPolicy.keyGenerationAlgorithmSuite + setDefaultFeatures(genAlgs.features.toSignatureSubpacketsFunction(true)) + } fun readKey(): OpenPGPKeyReader = api.readKeyOrCertificate() diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt index 867bf1b8..2012e2e1 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/AlgorithmSuite.kt @@ -4,15 +4,30 @@ package org.pgpainless.algorithm +import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction + class AlgorithmSuite( - symmetricKeyAlgorithms: List, - hashAlgorithms: List, - compressionAlgorithms: List + symmetricKeyAlgorithms: List?, + hashAlgorithms: List?, + compressionAlgorithms: List?, + aeadAlgorithms: List?, + features: List ) { - val symmetricKeyAlgorithms: Set = symmetricKeyAlgorithms.toSet() - val hashAlgorithms: Set = hashAlgorithms.toSet() - val compressionAlgorithms: Set = compressionAlgorithms.toSet() + val symmetricKeyAlgorithms: Set? = symmetricKeyAlgorithms?.toSet() + val hashAlgorithms: Set? = hashAlgorithms?.toSet() + val compressionAlgorithms: Set? = compressionAlgorithms?.toSet() + val aeadAlgorithms: Set? = aeadAlgorithms?.toSet() + val features: FeatureSet = FeatureSet(features.toSet()) + + class FeatureSet(val features: Set) { + fun toSignatureSubpacketsFunction(critical: Boolean = true): SignatureSubpacketsFunction { + return SignatureSubpacketsFunction { + val b = Feature.toBitmask(*features.toTypedArray()) + it.apply { setFeature(critical, b) } + } + } + } companion object { @@ -39,9 +54,26 @@ class AlgorithmSuite( CompressionAlgorithm.ZIP, CompressionAlgorithm.UNCOMPRESSED) + @JvmStatic + val defaultAEADAlgorithmSuites = + listOf( + AEADCipherMode(AEADAlgorithm.EAX, SymmetricKeyAlgorithm.AES_256), + AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256), + AEADCipherMode(AEADAlgorithm.GCM, SymmetricKeyAlgorithm.AES_256), + AEADCipherMode(AEADAlgorithm.EAX, SymmetricKeyAlgorithm.AES_192), + AEADCipherMode(AEADAlgorithm.EAX, SymmetricKeyAlgorithm.AES_192)) + + @JvmStatic + val defaultFeatures = + listOf(Feature.MODIFICATION_DETECTION, Feature.MODIFICATION_DETECTION_2) + @JvmStatic val defaultAlgorithmSuite = AlgorithmSuite( - defaultSymmetricKeyAlgorithms, defaultHashAlgorithms, defaultCompressionAlgorithms) + defaultSymmetricKeyAlgorithms, + defaultHashAlgorithms, + defaultCompressionAlgorithms, + defaultAEADAlgorithmSuites, + defaultFeatures) } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt index cbb5a9df..7ee032ae 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingTemplates.kt @@ -4,6 +4,7 @@ package org.pgpainless.key.generation +import java.util.* import org.bouncycastle.openpgp.api.OpenPGPKey import org.pgpainless.PGPainless.Companion.buildKeyRing import org.pgpainless.algorithm.KeyFlag @@ -15,7 +16,10 @@ import org.pgpainless.key.generation.type.rsa.RsaLength import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec import org.pgpainless.util.Passphrase -class KeyRingTemplates(private val version: OpenPGPKeyVersion) { +class KeyRingTemplates( + private val version: OpenPGPKeyVersion, + private val creationTime: Date = Date() +) { /** * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt index 0e7f9aae..b934698e 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeySpecBuilder.kt @@ -20,11 +20,12 @@ constructor( private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets() private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite - private var preferredCompressionAlgorithms: Set = + private var preferredCompressionAlgorithms: Set? = algorithmSuite.compressionAlgorithms - private var preferredHashAlgorithms: Set = algorithmSuite.hashAlgorithms - private var preferredSymmetricAlgorithms: Set = + private var preferredHashAlgorithms: Set? = algorithmSuite.hashAlgorithms + private var preferredSymmetricAlgorithms: Set? = algorithmSuite.symmetricKeyAlgorithms + private var preferredAEADAlgorithms: Set? = algorithmSuite.aeadAlgorithms private var keyCreationDate: Date? = null constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags)) @@ -59,9 +60,10 @@ constructor( return hashedSubpackets .apply { setKeyFlags(keyFlags) - setPreferredCompressionAlgorithms(preferredCompressionAlgorithms) - setPreferredHashAlgorithms(preferredHashAlgorithms) - setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms) + preferredCompressionAlgorithms?.let { setPreferredCompressionAlgorithms(it) } + preferredHashAlgorithms?.let { setPreferredHashAlgorithms(it) } + preferredSymmetricAlgorithms?.let { setPreferredSymmetricKeyAlgorithms(it) } + preferredAEADAlgorithms?.let { setPreferredAEADCiphersuites(it) } setFeatures(Feature.MODIFICATION_DETECTION) } .let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt index f3e22faf..2349ed9a 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/BaseSignatureSubpackets.kt @@ -21,14 +21,17 @@ interface BaseSignatureSubpackets { fun setAppropriateIssuerInfo(key: PGPPublicKey): BaseSignatureSubpackets /** - * Depending on the given [version], use the appropriate means of setting issuer information. - * V6 signatures for example MUST NOT contain an [IssuerKeyID] packet. + * Depending on the given [version], use the appropriate means of setting issuer information. V6 + * signatures for example MUST NOT contain an [IssuerKeyID] packet. * * @param key issuer key * @param version signature version * @return this */ - fun setAppropriateIssuerInfo(key: PGPPublicKey, version: OpenPGPKeyVersion): BaseSignatureSubpackets + fun setAppropriateIssuerInfo( + key: PGPPublicKey, + version: OpenPGPKeyVersion + ): BaseSignatureSubpackets /** * Add both an [IssuerKeyID] and [IssuerFingerprint] subpacket pointing to the given key. diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt index 318b7adf..7e7c8236 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SelfSignatureSubpackets.kt @@ -112,6 +112,8 @@ interface SelfSignatureSubpackets : BaseSignatureSubpackets { fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets + fun setPreferredAEADCiphersuites(aeadAlgorithms: Set) + fun addRevocationKey(revocationKey: PGPPublicKey): SelfSignatureSubpackets fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt index d91b8659..93796ca8 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/subpackets/SignatureSubpackets.kt @@ -360,7 +360,8 @@ class SignatureSubpackets : when (version) { OpenPGPKeyVersion.v3 -> setIssuerKeyId(key.keyID) OpenPGPKeyVersion.v4 -> setIssuerFingerprintAndKeyId(key) - OpenPGPKeyVersion.librePgp, OpenPGPKeyVersion.v6 -> setIssuerFingerprint(key) + OpenPGPKeyVersion.librePgp, + OpenPGPKeyVersion.v6 -> setIssuerFingerprint(key) } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateV6KeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateV6KeyTest.java index 8ad1daeb..a524eea6 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateV6KeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateV6KeyTest.java @@ -4,12 +4,24 @@ package org.pgpainless.key.generation; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.SignatureParameters; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.OpenPGPKeyVersion; +import org.pgpainless.algorithm.PublicKeyAlgorithm; + +import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; public class GenerateV6KeyTest { @@ -20,4 +32,70 @@ public class GenerateV6KeyTest { .getPGPSecretKeyRing(); assertEquals(6, secretKey.getPublicKey().getVersion()); } + + @Test + public void buildMinimalEd25519V6Key() throws PGPException { + OpenPGPKey key = PGPainless.getInstance().buildKey(OpenPGPKeyVersion.v6) + .withPrimaryKey(PGPKeyPairGenerator::generateEd25519KeyPair, new SignatureParameters.Callback() { + @Override + public SignatureParameters apply(SignatureParameters parameters) { + return parameters.setHashedSubpacketsFunction(pgpSignatureSubpacketGenerator -> { + // TODO: Remove once https://github.com/bcgit/bc-java/pull/2013 lands + pgpSignatureSubpacketGenerator.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + pgpSignatureSubpacketGenerator.setKeyFlags(KeyFlag.SIGN_DATA.getFlag()); + return pgpSignatureSubpacketGenerator; + }); + } + }) + .build(); + + assertTrue(key.isSecretKey()); + + assertEquals(1, key.getKeys().size()); + OpenPGPCertificate.OpenPGPPrimaryKey primaryKey = key.getPrimaryKey(); + assertEquals(6, primaryKey.getVersion()); + assertTrue(primaryKey.isPrimaryKey()); + assertEquals(PublicKeyAlgorithm.ED25519.getAlgorithmId(), primaryKey.getPGPPublicKey().getAlgorithm()); + assertEquals(primaryKey, key.getSigningKeys().get(0)); + assertTrue(key.getEncryptionKeys().isEmpty()); + + OpenPGPKey.OpenPGPSecretKey primarySecretKey = key.getPrimarySecretKey(); + assertEquals(primarySecretKey, key.getSecretKey(primaryKey)); + } + + @Test + public void buildCompositeCurve25519V6Key() + throws PGPException, IOException { + OpenPGPKey key = PGPainless.getInstance().buildKey(OpenPGPKeyVersion.v6) + .withPrimaryKey(PGPKeyPairGenerator::generateEd25519KeyPair) + .addSigningSubkey(PGPKeyPairGenerator::generateEd25519KeyPair) + .addEncryptionSubkey(PGPKeyPairGenerator::generateX25519KeyPair) + .addUserId("Alice ") + .build(); + + assertTrue(key.isSecretKey()); + assertEquals(3, key.getKeys().size()); + OpenPGPCertificate.OpenPGPPrimaryKey primaryKey = key.getPrimaryKey(); + assertEquals(PublicKeyAlgorithm.ED25519.getAlgorithmId(), primaryKey.getPGPPublicKey().getAlgorithm()); + assertEquals(6, primaryKey.getVersion()); + assertTrue(primaryKey.isPrimaryKey()); + assertEquals(primaryKey, key.getKeys().get(0)); + + OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getKeys().get(1); + assertTrue(key.getSigningKeys().contains(signingKey)); + assertEquals(6, signingKey.getVersion()); + assertEquals(PublicKeyAlgorithm.ED25519.getAlgorithmId(), signingKey.getPGPPublicKey().getAlgorithm()); + assertFalse(signingKey.isPrimaryKey()); + + OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getKeys().get(2); + assertTrue(key.getEncryptionKeys().contains(encryptionKey)); + assertEquals(6, encryptionKey.getVersion()); + assertEquals(PublicKeyAlgorithm.X25519.getAlgorithmId(), encryptionKey.getPGPPublicKey().getAlgorithm()); + assertFalse(encryptionKey.isPrimaryKey()); + + OpenPGPCertificate certificate = key.toCertificate(); + assertFalse(certificate.isSecretKey()); + + System.out.println(certificate.toAsciiArmoredString()); + } }