1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Work on AlgorithmSuite

This commit is contained in:
Paul Schaub 2025-03-05 15:15:13 +01:00
parent 67af718db9
commit 5de1e6a56d
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
8 changed files with 158 additions and 20 deletions

View file

@ -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()

View file

@ -4,15 +4,30 @@
package org.pgpainless.algorithm
import org.bouncycastle.openpgp.api.SignatureSubpacketsFunction
class AlgorithmSuite(
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
hashAlgorithms: List<HashAlgorithm>,
compressionAlgorithms: List<CompressionAlgorithm>
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>?,
hashAlgorithms: List<HashAlgorithm>?,
compressionAlgorithms: List<CompressionAlgorithm>?,
aeadAlgorithms: List<AEADCipherMode>?,
features: List<Feature>
) {
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
val compressionAlgorithms: Set<CompressionAlgorithm> = compressionAlgorithms.toSet()
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>? = symmetricKeyAlgorithms?.toSet()
val hashAlgorithms: Set<HashAlgorithm>? = hashAlgorithms?.toSet()
val compressionAlgorithms: Set<CompressionAlgorithm>? = compressionAlgorithms?.toSet()
val aeadAlgorithms: Set<AEADCipherMode>? = aeadAlgorithms?.toSet()
val features: FeatureSet = FeatureSet(features.toSet())
class FeatureSet(val features: Set<Feature>) {
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)
}
}

View file

@ -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

View file

@ -20,11 +20,12 @@ constructor(
private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets()
private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> =
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm>? =
algorithmSuite.compressionAlgorithms
private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> =
private var preferredHashAlgorithms: Set<HashAlgorithm>? = algorithmSuite.hashAlgorithms
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm>? =
algorithmSuite.symmetricKeyAlgorithms
private var preferredAEADAlgorithms: Set<AEADCipherMode>? = 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) }

View file

@ -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.

View file

@ -112,6 +112,8 @@ interface SelfSignatureSubpackets : BaseSignatureSubpackets {
fun setPreferredHashAlgorithms(algorithms: PreferredAlgorithms?): SelfSignatureSubpackets
fun setPreferredAEADCiphersuites(aeadAlgorithms: Set<AEADCipherMode>)
fun addRevocationKey(revocationKey: PGPPublicKey): SelfSignatureSubpackets
fun addRevocationKey(isCritical: Boolean, revocationKey: PGPPublicKey): SelfSignatureSubpackets

View file

@ -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)
}
}

View file

@ -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 <alice@pgpainless.org>")
.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());
}
}