From 3e6ba0a465e163f5a522baf923add7f85657d64d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 10:45:00 +0200 Subject: [PATCH 01/53] Rename masterKey -> primaryKey --- .../openpgp/PGPKeyRingGenerator.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 9f8f9d0249..1ffa486d0e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -13,7 +13,7 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculator; /** - * Generator for a PGP master and subkey ring. This class will generate + * Generator for a PGP primary and subkey ring. This class will generate * both the secret and public key rings */ public class PGPKeyRingGenerator @@ -21,8 +21,8 @@ public class PGPKeyRingGenerator List keys = new ArrayList(); private PBESecretKeyEncryptor keyEncryptor; - private PGPDigestCalculator checksumCalculator; - private PGPKeyPair masterKey; + private PGPDigestCalculator checksumCalculator; + private PGPKeyPair primaryKey; private PGPSignatureSubpacketVector hashedPcks; private PGPSignatureSubpacketVector unhashedPcks; private PGPContentSignerBuilder keySignerBuilder; @@ -31,18 +31,18 @@ public class PGPKeyRingGenerator * Create a new key ring generator. * * @param certificationLevel - * @param masterKey + * @param primaryKey * @param id id to associate with the key. * @param checksumCalculator key checksum calculator * @param hashedPcks * @param unhashedPcks - * @param keySignerBuilder builder for key certifications - will be initialised with master secret key. + * @param keySignerBuilder builder for key certifications - will be initialised with primary secret key. * @param keyEncryptor encryptor for secret subkeys. * @throws PGPException */ public PGPKeyRingGenerator( int certificationLevel, - PGPKeyPair masterKey, + PGPKeyPair primaryKey, String id, PGPDigestCalculator checksumCalculator, PGPSignatureSubpacketVector hashedPcks, @@ -51,19 +51,19 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.masterKey = masterKey; + this.primaryKey = primaryKey; this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; this.keySignerBuilder = keySignerBuilder; this.hashedPcks = hashedPcks; this.unhashedPcks = unhashedPcks; - keys.add(new PGPSecretKey(certificationLevel, masterKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor)); + keys.add(new PGPSecretKey(certificationLevel, primaryKey, id, checksumCalculator, hashedPcks, unhashedPcks, keySignerBuilder, keyEncryptor)); } /** * Create a new key ring generator without a user-id, but instead with a primary key carrying a direct-key signature. - * @param masterKey primary key + * @param primaryKey primary key * @param checksumCalculator checksum calculator * @param hashedPcks hashed signature subpackets * @param unhashedPcks unhashed signature subpackets @@ -72,7 +72,7 @@ public PGPKeyRingGenerator( * @throws PGPException */ public PGPKeyRingGenerator( - PGPKeyPair masterKey, + PGPKeyPair primaryKey, PGPDigestCalculator checksumCalculator, PGPSignatureSubpacketVector hashedPcks, PGPSignatureSubpacketVector unhashedPcks, @@ -80,7 +80,7 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.masterKey = masterKey; + this.primaryKey = primaryKey; this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; this.keySignerBuilder = keySignerBuilder; @@ -99,15 +99,15 @@ public PGPKeyRingGenerator( } // Keyring without user-id needs direct key sig - sigGen.init(PGPSignature.DIRECT_KEY, masterKey.getPrivateKey()); + sigGen.init(PGPSignature.DIRECT_KEY, primaryKey.getPrivateKey()); sigGen.setHashedSubpackets(hashedPcks); sigGen.setUnhashedSubpackets(unhashedPcks); - PGPSecretKey secretKey = new PGPSecretKey(masterKey.getPrivateKey(), masterKey.getPublicKey(), checksumCalculator, true, keyEncryptor); + PGPSecretKey secretKey = new PGPSecretKey(primaryKey.getPrivateKey(), primaryKey.getPublicKey(), checksumCalculator, true, keyEncryptor); PGPPublicKey publicKey = secretKey.getPublicKey(); try { - PGPSignature certification = sigGen.generateCertification(masterKey.getPublicKey()); + PGPSignature certification = sigGen.generateCertification(primaryKey.getPublicKey()); publicKey = PGPPublicKey.addCertification(publicKey, certification); } @@ -123,13 +123,13 @@ public PGPKeyRingGenerator( /** * Create a new key ring generator based on an original secret key ring. The default hashed/unhashed sub-packets - * for subkey signatures will be inherited from the first signature on the master key (other than CREATION-TIME + * for subkey signatures will be inherited from the first signature on the primary key (other than CREATION-TIME * which will be ignored). * * @param originalSecretRing the secret key ring we want to add a subkeyto, - * @param secretKeyDecryptor a decryptor for the signing master key. + * @param secretKeyDecryptor a decryptor for the signing primary key. * @param checksumCalculator key checksum calculator - * @param keySignerBuilder builder for key certifications - will be initialised with master secret key. + * @param keySignerBuilder builder for key certifications - will be initialised with primary secret key. * @param keyEncryptor encryptor for secret subkeys. * @throws PGPException */ @@ -141,7 +141,7 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.masterKey = new PGPKeyPair(originalSecretRing.getPublicKey(), + this.primaryKey = new PGPKeyPair(originalSecretRing.getPublicKey(), originalSecretRing.getSecretKey().extractPrivateKey(secretKeyDecryptor)); this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; @@ -167,7 +167,7 @@ public PGPKeyRingGenerator( /** * Add a sub key to the key ring to be generated with default certification and inheriting - * the hashed/unhashed packets of the master key. + * the hashed/unhashed packets of the primary key. * * @param keyPair the key pair to add. * @throws PGPException @@ -181,7 +181,7 @@ public void addSubKey( /** * Add a sub key to the key ring to be generated with default certification and inheriting - * the hashed/unhashed packets of the master key. If bindingSignerBldr is not null it will be used to add a Primary Key Binding + * the hashed/unhashed packets of the primary key. If bindingSignerBldr is not null it will be used to add a Primary Key Binding * signature (type 0x19) into the hashedPcks for the key (required for signing subkeys). * * @param keyPair the key pair to add. @@ -239,7 +239,7 @@ public void addSubKey( // PGPSignatureGenerator sGen = new PGPSignatureGenerator(keySignerBuilder); - sGen.init(PGPSignature.SUBKEY_BINDING, masterKey.getPrivateKey()); + sGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.getPrivateKey()); if (bindingSignerBldr != null) { @@ -251,7 +251,7 @@ public void addSubKey( PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(hashedPcks); spGen.addEmbeddedSignature(false, - pGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey())); + pGen.generateCertification(primaryKey.getPublicKey(), keyPair.getPublicKey())); sGen.setHashedSubpackets(spGen.generate()); } else @@ -263,7 +263,7 @@ public void addSubKey( List subSigs = new ArrayList(); - subSigs.add(sGen.generateCertification(masterKey.getPublicKey(), keyPair.getPublicKey())); + subSigs.add(sGen.generateCertification(primaryKey.getPublicKey(), keyPair.getPublicKey())); // replace the public key packet structure with a public subkey one. PGPPublicKey pubSubKey = new PGPPublicKey(keyPair.getPublicKey(), null, subSigs); From 4a024c91e13b9a187d1251303a39a80779e607b9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 10:54:28 +0200 Subject: [PATCH 02/53] Javadoc --- .../org/bouncycastle/openpgp/PGPKeyRingGenerator.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 1ffa486d0e..31b797dc37 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -30,12 +30,12 @@ public class PGPKeyRingGenerator /** * Create a new key ring generator. * - * @param certificationLevel - * @param primaryKey + * @param certificationLevel certification level for user-ids + * @param primaryKey primary key * @param id id to associate with the key. * @param checksumCalculator key checksum calculator - * @param hashedPcks - * @param unhashedPcks + * @param hashedPcks hashed signature subpackets + * @param unhashedPcks unhashed signature subpackets * @param keySignerBuilder builder for key certifications - will be initialised with primary secret key. * @param keyEncryptor encryptor for secret subkeys. * @throws PGPException From 22af7ed85d02c270d2044162940fef4d63cf6dbf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 10:54:58 +0200 Subject: [PATCH 03/53] Generics --- .../openpgp/PGPKeyRingGenerator.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 31b797dc37..bc003b3ed5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -18,7 +18,7 @@ */ public class PGPKeyRingGenerator { - List keys = new ArrayList(); + List keys = new ArrayList(); private PBESecretKeyEncryptor keyEncryptor; private PGPDigestCalculator checksumCalculator; @@ -148,7 +148,7 @@ public PGPKeyRingGenerator( this.keySignerBuilder = keySignerBuilder; PGPSignature certSig = (PGPSignature)originalSecretRing.getPublicKey().getSignatures().next(); - List hashedVec = new ArrayList(); + List hashedVec = new ArrayList(); PGPSignatureSubpacketVector existing = certSig.getHashedSubPackets(); for (int i = 0; i != existing.size(); i++) { @@ -261,7 +261,7 @@ public void addSubKey( sGen.setUnhashedSubpackets(unhashedPcks); - List subSigs = new ArrayList(); + List subSigs = new ArrayList(); subSigs.add(sGen.generateCertification(primaryKey.getPublicKey(), keyPair.getPublicKey())); @@ -299,14 +299,14 @@ public PGPSecretKeyRing generateSecretKeyRing() */ public PGPPublicKeyRing generatePublicKeyRing() { - Iterator it = keys.iterator(); - List pubKeys = new ArrayList(); - - pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); - + Iterator it = keys.iterator(); + List pubKeys = new ArrayList(); + + pubKeys.add((it.next()).getPublicKey()); + while (it.hasNext()) { - pubKeys.add(((PGPSecretKey)it.next()).getPublicKey()); + pubKeys.add((it.next()).getPublicKey()); } return new PGPPublicKeyRing(pubKeys); From 369af90ccccc7f30b860196c7720d8f3b2ffb1c8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 10:55:14 +0200 Subject: [PATCH 04/53] Use proper constructor for PGPSignatureGenerator --- .../java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index bc003b3ed5..5a792d9e8e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -91,7 +91,7 @@ public PGPKeyRingGenerator( try { - sigGen = new PGPSignatureGenerator(keySignerBuilder); + sigGen = new PGPSignatureGenerator(keySignerBuilder, primaryKey.getPublicKey()); } catch (Exception e) { @@ -237,14 +237,14 @@ public void addSubKey( // // generate the certification // - PGPSignatureGenerator sGen = new PGPSignatureGenerator(keySignerBuilder); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(keySignerBuilder, primaryKey.getPublicKey()); sGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.getPrivateKey()); if (bindingSignerBldr != null) { // add primary key binding - PGPSignatureGenerator pGen = new PGPSignatureGenerator(bindingSignerBldr); + PGPSignatureGenerator pGen = new PGPSignatureGenerator(bindingSignerBldr, keyPair.getPublicKey()); pGen.init(PGPSignature.PRIMARYKEY_BINDING, keyPair.getPrivateKey()); From 88dc4c03f29c3655c04f4534ac8e5bedd83b9453 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 11:00:31 +0200 Subject: [PATCH 05/53] Final members --- .../openpgp/PGPKeyRingGenerator.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 5a792d9e8e..65685aca3c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -18,14 +18,14 @@ */ public class PGPKeyRingGenerator { - List keys = new ArrayList(); - - private PBESecretKeyEncryptor keyEncryptor; - private PGPDigestCalculator checksumCalculator; - private PGPKeyPair primaryKey; - private PGPSignatureSubpacketVector hashedPcks; - private PGPSignatureSubpacketVector unhashedPcks; - private PGPContentSignerBuilder keySignerBuilder; + private final List keys = new ArrayList(); + + private final PBESecretKeyEncryptor keyEncryptor; + private final PGPDigestCalculator checksumCalculator; + private final PGPKeyPair primaryKey; + private final PGPSignatureSubpacketVector hashedPcks; + private final PGPSignatureSubpacketVector unhashedPcks; + private final PGPContentSignerBuilder keySignerBuilder; /** * Create a new key ring generator. From b41abfbe88935de7d5425ff65cb097019b864fde Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 11:01:08 +0200 Subject: [PATCH 06/53] Document PGPException --- .../bouncycastle/openpgp/PGPKeyRingGenerator.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 65685aca3c..91ecb7f851 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -38,7 +38,7 @@ public class PGPKeyRingGenerator * @param unhashedPcks unhashed signature subpackets * @param keySignerBuilder builder for key certifications - will be initialised with primary secret key. * @param keyEncryptor encryptor for secret subkeys. - * @throws PGPException + * @throws PGPException error during signature generation */ public PGPKeyRingGenerator( int certificationLevel, @@ -69,7 +69,7 @@ public PGPKeyRingGenerator( * @param unhashedPcks unhashed signature subpackets * @param keySignerBuilder signer builder * @param keyEncryptor key encryptor - * @throws PGPException + * @throws PGPException error during signature generation */ public PGPKeyRingGenerator( PGPKeyPair primaryKey, @@ -131,7 +131,7 @@ public PGPKeyRingGenerator( * @param checksumCalculator key checksum calculator * @param keySignerBuilder builder for key certifications - will be initialised with primary secret key. * @param keyEncryptor encryptor for secret subkeys. - * @throws PGPException + * @throws PGPException error during signature generation */ public PGPKeyRingGenerator( PGPSecretKeyRing originalSecretRing, @@ -170,7 +170,7 @@ public PGPKeyRingGenerator( * the hashed/unhashed packets of the primary key. * * @param keyPair the key pair to add. - * @throws PGPException + * @throws PGPException error during signature generation */ public void addSubKey( PGPKeyPair keyPair) @@ -186,7 +186,7 @@ public void addSubKey( * * @param keyPair the key pair to add. * @param bindingSignerBldr provide a signing builder to create the Primary Key signature. - * @throws PGPException + * @throws PGPException error during signature generation */ public void addSubKey( PGPKeyPair keyPair, @@ -203,7 +203,7 @@ public void addSubKey( * @param keyPair public/private key pair. * @param hashedPcks hashed packet values to be included in certification. * @param unhashedPcks unhashed packets values to be included in certification. - * @throws PGPException + * @throws PGPException error during signature generation */ public void addSubKey( PGPKeyPair keyPair, @@ -223,7 +223,7 @@ public void addSubKey( * @param hashedPcks hashed packet values to be included in certification. * @param unhashedPcks unhashed packets values to be included in certification. * @param bindingSignerBldr provide a signing builder to create the Primary Key signature. - * @throws PGPException + * @throws PGPException error during signature generation */ public void addSubKey( PGPKeyPair keyPair, From 35b435cf1d06072e2d0b2b35291d7a5458b81ff6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 11:01:28 +0200 Subject: [PATCH 07/53] Remove unnecessary toArray() optimization --- .../main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 91ecb7f851..022560ca18 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -159,7 +159,7 @@ public PGPKeyRingGenerator( hashedVec.add(existing.packets[i]); } this.hashedPcks = new PGPSignatureSubpacketVector( - (SignatureSubpacket[])hashedVec.toArray(new SignatureSubpacket[hashedVec.size()])); + hashedVec.toArray(new SignatureSubpacket[0])); this.unhashedPcks = certSig.getUnhashedSubPackets(); keys.addAll(originalSecretRing.keys); From c0a8ee6f868bad0b21fc77bd6f54afba9ba7ed3d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 11:34:27 +0200 Subject: [PATCH 08/53] Add test for PGPKeyRingGenerator --- .../openpgp/test/PGPKeyRingGeneratorTest.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingGeneratorTest.java diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingGeneratorTest.java new file mode 100644 index 0000000000..e6e02ed5a6 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyRingGeneratorTest.java @@ -0,0 +1,113 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator; +import org.bouncycastle.crypto.generators.X25519KeyPairGenerator; +import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters; +import org.bouncycastle.crypto.params.X25519KeyGenerationParameters; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPKeyRingGenerator; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; + +public class PGPKeyRingGeneratorTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "PGPKeyRingGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + generateMinimalV6Key(); + } + + private void generateMinimalV6Key() + throws PGPException, IOException + { + Date creationTime = currentTimeRounded(); + Ed25519KeyPairGenerator edGen = new Ed25519KeyPairGenerator(); + edGen.init(new Ed25519KeyGenerationParameters(CryptoServicesRegistrar.getSecureRandom())); + AsymmetricCipherKeyPair edKp = edGen.generateKeyPair(); + PGPKeyPair primaryKp = new BcPGPKeyPair(PublicKeyPacket.VERSION_6, PublicKeyAlgorithmTags.Ed25519, edKp, creationTime); + + PGPSignatureSubpacketGenerator hashed = new PGPSignatureSubpacketGenerator(); + hashed.setIssuerFingerprint(true, primaryKp.getPublicKey()); + hashed.setSignatureCreationTime(true, creationTime); + hashed.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); + hashed.setFeature(true, (byte) (Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); + hashed.setPreferredHashAlgorithms(false, new int[] { + HashAlgorithmTags.SHA3_512, HashAlgorithmTags.SHA3_256, + HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256 + }); + hashed.setPreferredSymmetricAlgorithms(false, new int[] { + SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 + }); + hashed.setPreferredAEADCiphersuites(false, new PreferredAEADCiphersuites.Combination[] { + new PreferredAEADCiphersuites.Combination(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB), + new PreferredAEADCiphersuites.Combination(SymmetricKeyAlgorithmTags.AES_192, AEADAlgorithmTags.OCB), + new PreferredAEADCiphersuites.Combination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB) + }); + + PGPKeyRingGenerator gen = new PGPKeyRingGenerator( + primaryKp, + new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1), + hashed.generate(), + null, + new BcPGPContentSignerBuilder(primaryKp.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA3_512), + null); + + X25519KeyPairGenerator xGen = new X25519KeyPairGenerator(); + xGen.init(new X25519KeyGenerationParameters(CryptoServicesRegistrar.getSecureRandom())); + AsymmetricCipherKeyPair xKp = xGen.generateKeyPair(); + PGPKeyPair subKp = new BcPGPKeyPair(PublicKeyPacket.VERSION_6, PublicKeyAlgorithmTags.X25519, xKp, creationTime); + + hashed = new PGPSignatureSubpacketGenerator(); + hashed.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); + hashed.setSignatureCreationTime(true, creationTime); + hashed.setIssuerFingerprint(true, primaryKp.getPublicKey()); + + gen.addSubKey(subKp, hashed.generate(), null, null); + + PGPPublicKeyRing certificate = gen.generatePublicKeyRing(); + PGPSecretKeyRing secretKey = gen.generateSecretKeyRing(); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKey.encode(pOut); + pOut.close(); + aOut.close(); + System.out.println(bOut); + } + + public static void main(String[] args) + { + runTest(new PGPKeyRingGeneratorTest()); + } +} From 8bbae0571539c44e8b9e034b7b38039d89a68394 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 14:48:53 +0200 Subject: [PATCH 09/53] Document legacy curve OIDs --- .../asn1/cryptlib/CryptlibObjectIdentifiers.java | 5 +++++ .../org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/util/src/main/java/org/bouncycastle/asn1/cryptlib/CryptlibObjectIdentifiers.java b/util/src/main/java/org/bouncycastle/asn1/cryptlib/CryptlibObjectIdentifiers.java index 88dd03f5c1..febe07e19b 100644 --- a/util/src/main/java/org/bouncycastle/asn1/cryptlib/CryptlibObjectIdentifiers.java +++ b/util/src/main/java/org/bouncycastle/asn1/cryptlib/CryptlibObjectIdentifiers.java @@ -8,5 +8,10 @@ public class CryptlibObjectIdentifiers public static final ASN1ObjectIdentifier ecc = cryptlib.branch("1").branch("5"); + /** + * Curve25519Legacy for use with ECDH. + * @see + * RFC9580 - ECC Curves for OpenPGP + */ public static final ASN1ObjectIdentifier curvey25519 = ecc.branch("1"); } diff --git a/util/src/main/java/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java b/util/src/main/java/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java index 0f82da0b00..e80cab700b 100644 --- a/util/src/main/java/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java +++ b/util/src/main/java/org/bouncycastle/asn1/gnu/GNUObjectIdentifiers.java @@ -107,5 +107,11 @@ public interface GNUObjectIdentifiers */ ASN1ObjectIdentifier ellipticCurve = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.15"); + /** + * Ed25519Legacy for use with EdDSALegacy. + * + * @see + * RFC9580 - ECC Curves for OpenPGP + */ ASN1ObjectIdentifier Ed25519 = ellipticCurve.branch("1"); } From 1c429db1b87a4835fa81ae932bd5e234b7f8dc19 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 14:49:57 +0200 Subject: [PATCH 10/53] Sanitize key algorithm/version matchups --- .../openpgp/PGPKeyRingGenerator.java | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java index 022560ca18..2a259746b3 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyRingGenerator.java @@ -4,6 +4,10 @@ import java.util.Iterator; import java.util.List; +import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers; +import org.bouncycastle.bcpg.ECPublicBCPGKey; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicSubkeyPacket; import org.bouncycastle.bcpg.SignatureSubpacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; @@ -51,7 +55,7 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.primaryKey = primaryKey; + this.primaryKey = sanitizeKeyPair(primaryKey); this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; this.keySignerBuilder = keySignerBuilder; @@ -80,7 +84,7 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.primaryKey = primaryKey; + this.primaryKey = sanitizeKeyPair(primaryKey); this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; this.keySignerBuilder = keySignerBuilder; @@ -141,8 +145,8 @@ public PGPKeyRingGenerator( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - this.primaryKey = new PGPKeyPair(originalSecretRing.getPublicKey(), - originalSecretRing.getSecretKey().extractPrivateKey(secretKeyDecryptor)); + this.primaryKey = sanitizeKeyPair(new PGPKeyPair(originalSecretRing.getPublicKey(), + originalSecretRing.getSecretKey().extractPrivateKey(secretKeyDecryptor))); this.keyEncryptor = keyEncryptor; this.checksumCalculator = checksumCalculator; this.keySignerBuilder = keySignerBuilder; @@ -165,6 +169,63 @@ public PGPKeyRingGenerator( keys.addAll(originalSecretRing.keys); } + private PGPKeyPair sanitizeKeyPair(PGPKeyPair keyPair) + throws PGPException + { + PGPPublicKey pubKey = keyPair.getPublicKey(); + if (pubKey.getVersion() == PublicKeyPacket.VERSION_6) + { + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT || + pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ELGAMAL_GENERAL) + { + // https://www.rfc-editor.org/rfc/rfc9580.html#name-elgamal + throw new PGPException("An implementation MUST NOT generate v6 ElGamal keys"); + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL) + { + // https://www.rfc-editor.org/rfc/rfc9580.html#name-rsa + if (pubKey.getBitStrength() < 3072) + { + throw new PGPException("An implementation MUST NOT generate v6 RSA keys of a size less than 3072 bits."); + } + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.RSA_ENCRYPT || + pubKey.getAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN) + { + throw new PGPException("An implementation MUST NOT generate v6 RSA keys of type RSA_ENCRYPT/RSA_SIGN"); + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.DSA) + { + // https://www.rfc-editor.org/rfc/rfc9580.html#name-dsa + throw new PGPException("An implementation MUST NOT generate v6 DSA keys."); + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.EDDSA_LEGACY) + { + throw new PGPException("An implementation MUST NOT generate v6 EDDSA_LEGACY keys."); + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.ECDH) + { + ECPublicBCPGKey ecKey = (ECPublicBCPGKey) pubKey.publicPk.getKey(); + if (ecKey.getCurveOID().equals(CryptlibObjectIdentifiers.curvey25519)) + { + throw new PGPException("An implementation MUST NOT generate v6 ECDH keys over Curve25519Legacy."); + } + } + + if (pubKey.getAlgorithm() == PublicKeyAlgorithmTags.DIFFIE_HELLMAN) + { + throw new PGPException("An implementation MUST NOT generate v6 Diffie-Hellman keys."); + } + } + + return keyPair; + } + /** * Add a sub key to the key ring to be generated with default certification and inheriting * the hashed/unhashed packets of the primary key. @@ -176,7 +237,7 @@ public void addSubKey( PGPKeyPair keyPair) throws PGPException { - addSubKey(keyPair, hashedPcks, unhashedPcks); + addSubKey(sanitizeKeyPair(keyPair), hashedPcks, unhashedPcks); } /** @@ -193,7 +254,7 @@ public void addSubKey( PGPContentSignerBuilder bindingSignerBldr) throws PGPException { - addSubKey(keyPair, hashedPcks, unhashedPcks, bindingSignerBldr); + addSubKey(sanitizeKeyPair(keyPair), hashedPcks, unhashedPcks, bindingSignerBldr); } /** @@ -211,7 +272,7 @@ public void addSubKey( PGPSignatureSubpacketVector unhashedPcks) throws PGPException { - addSubKey(keyPair, hashedPcks, unhashedPcks, null); + addSubKey(sanitizeKeyPair(keyPair), hashedPcks, unhashedPcks, null); } /** @@ -232,6 +293,7 @@ public void addSubKey( PGPContentSignerBuilder bindingSignerBldr) throws PGPException { + sanitizeKeyPair(keyPair); try { // From 6374ed977bb3d0a831710e9c9dac540fa617fe88 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 2 Oct 2024 14:50:53 +0200 Subject: [PATCH 11/53] Add PGPKeyPairGenerator class + tests --- .../openpgp/PGPV6KeyRingGenerator.java | 31 ++ .../openpgp/operator/PGPKeyPairGenerator.java | 50 +++ .../operator/PGPKeyPairGeneratorProvider.java | 8 + .../bc/BcPGPKeyPairGeneratorProvider.java | 132 ++++++++ .../JcaPGPKeyPairGeneratorProvider.java | 216 +++++++++++++ .../openpgp/test/PGPKeyPairGeneratorTest.java | 303 ++++++++++++++++++ .../openpgp/test/RegressionTest.java | 3 +- 7 files changed, 742 insertions(+), 1 deletion(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGeneratorProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java new file mode 100644 index 0000000000..fce6a106a9 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java @@ -0,0 +1,31 @@ +package 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; + +public class PGPV6KeyRingGenerator +{ + public PGPV6KeyRingGenerator( + PGPKeyPair primaryKey, + PGPSignatureSubpacketVector hashedPcks, + PGPSignatureSubpacketVector unhashedPcks, + PGPContentSignerBuilder keySignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + + } + + public PGPV6KeyRingGenerator( + PGPSecretKeyRing originalSecretRing, + PBESecretKeyDecryptor secretKeyDecryptor, + PGPDigestCalculator checksumCalculator, + PGPContentSignerBuilder keySignerBuilder, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java new file mode 100644 index 0000000000..f0b02e3a98 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java @@ -0,0 +1,50 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Date; + +public abstract class PGPKeyPairGenerator +{ + + protected final Date creationTime; + protected final int version; + protected SecureRandom random; + + public PGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) + { + this.creationTime = new Date((creationTime.getTime() / 1000) * 1000); + this.version = version; + this.random = random; + } + + public PGPKeyPair generateRsaKeyPair(int bitStrength) + throws PGPException + { + return generateRsaKeyPair(BigInteger.valueOf(0x10001), bitStrength); + } + + public abstract PGPKeyPair generateRsaKeyPair(BigInteger exponent, int bitStrength) + throws PGPException; + + public abstract PGPKeyPair generateEd25519KeyPair() + throws PGPException; + + public abstract PGPKeyPair generateEd448KeyPair() + throws PGPException; + + public abstract PGPKeyPair generateX25519KeyPair() + throws PGPException; + + public abstract PGPKeyPair generateX448KeyPair() + throws PGPException; + + public abstract PGPKeyPair generateLegacyEd25519KeyPair() + throws PGPException; + + public abstract PGPKeyPair generateLegacyX25519KeyPair() + throws PGPException; +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGeneratorProvider.java new file mode 100644 index 0000000000..9b403f9334 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGeneratorProvider.java @@ -0,0 +1,8 @@ +package org.bouncycastle.openpgp.operator; + +import java.util.Date; + +public abstract class PGPKeyPairGeneratorProvider +{ + public abstract PGPKeyPairGenerator get(int version, Date creationTime); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java new file mode 100644 index 0000000000..fba86093b1 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java @@ -0,0 +1,132 @@ +package org.bouncycastle.openpgp.operator.bc; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator; +import org.bouncycastle.crypto.generators.Ed448KeyPairGenerator; +import org.bouncycastle.crypto.generators.RSAKeyPairGenerator; +import org.bouncycastle.crypto.generators.X25519KeyPairGenerator; +import org.bouncycastle.crypto.generators.X448KeyPairGenerator; +import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters; +import org.bouncycastle.crypto.params.Ed448KeyGenerationParameters; +import org.bouncycastle.crypto.params.RSAKeyGenerationParameters; +import org.bouncycastle.crypto.params.X25519KeyGenerationParameters; +import org.bouncycastle.crypto.params.X448KeyGenerationParameters; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Date; + +public class BcPGPKeyPairGeneratorProvider + extends PGPKeyPairGeneratorProvider +{ + private SecureRandom random = CryptoServicesRegistrar.getSecureRandom(); + + @Override + public PGPKeyPairGenerator get(int version, Date creationTime) + { + return new BcPGPKeyPairGenerator(version, creationTime, random); + } + + public BcPGPKeyPairGeneratorProvider setSecureRandom(SecureRandom random) + { + this.random = random; + return this; + } + + private static class BcPGPKeyPairGenerator + extends PGPKeyPairGenerator + { + + public BcPGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) + { + super(version, creationTime, random); + } + + @Override + public PGPKeyPair generateRsaKeyPair(BigInteger exponent, int bitStrength) + throws PGPException + { + RSAKeyPairGenerator gen = new RSAKeyPairGenerator(); + gen.init(new RSAKeyGenerationParameters(exponent, random, bitStrength, 100)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.RSA_GENERAL, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateEd25519KeyPair() + throws PGPException + { + Ed25519KeyPairGenerator gen = new Ed25519KeyPairGenerator(); + gen.init(new Ed25519KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.Ed25519, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateEd448KeyPair() + throws PGPException + { + Ed448KeyPairGenerator gen = new Ed448KeyPairGenerator(); + gen.init(new Ed448KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.Ed448, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateX25519KeyPair() + throws PGPException + { + X25519KeyPairGenerator gen = new X25519KeyPairGenerator(); + gen.init(new X25519KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.X25519, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateX448KeyPair() + throws PGPException + { + X448KeyPairGenerator gen = new X448KeyPairGenerator(); + gen.init(new X448KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.X448, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateLegacyEd25519KeyPair() + throws PGPException + { + if (version == PublicKeyPacket.VERSION_6) + { + throw new PGPException("An implementation MUST NOT generate a v6 LegacyEd25519 key pair."); + } + + Ed25519KeyPairGenerator gen = new Ed25519KeyPairGenerator(); + gen.init(new Ed25519KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.EDDSA_LEGACY, keyPair, creationTime); + } + + @Override + public PGPKeyPair generateLegacyX25519KeyPair() + throws PGPException + { + if (version == PublicKeyPacket.VERSION_6) + { + throw new PGPException("An implementation MUST NOT generate a v6 LegacyX25519 key pair."); + } + + X25519KeyPairGenerator gen = new X25519KeyPairGenerator(); + gen.init(new X25519KeyGenerationParameters(random)); + AsymmetricCipherKeyPair keyPair = gen.generateKeyPair(); + return new BcPGPKeyPair(version, PublicKeyAlgorithmTags.ECDH, keyPair, creationTime); + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java new file mode 100644 index 0000000000..cd728f879b --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java @@ -0,0 +1,216 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.jcajce.spec.EdDSAParameterSpec; +import org.bouncycastle.jcajce.spec.XDHParameterSpec; +import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; +import org.bouncycastle.jcajce.util.NamedJcaJceHelper; +import org.bouncycastle.jcajce.util.ProviderJcaJceHelper; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.Date; + +public class JcaPGPKeyPairGeneratorProvider + extends PGPKeyPairGeneratorProvider +{ + + private OperatorHelper helper; + private SecureRandom secureRandom = CryptoServicesRegistrar.getSecureRandom(); + + public JcaPGPKeyPairGeneratorProvider() + { + this.helper = new OperatorHelper(new DefaultJcaJceHelper()); + } + + + /** + * Set the provider object to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param provider provider object for cryptographic primitives. + * @return the current builder. + */ + public JcaPGPKeyPairGeneratorProvider setProvider(Provider provider) + { + this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider)); + return this; + } + + /** + * Set the provider name to use for creating cryptographic primitives in the resulting factory the builder produces. + * + * @param providerName the name of the provider to reference for cryptographic primitives. + * @return the current builder. + */ + public JcaPGPKeyPairGeneratorProvider setProvider(String providerName) + { + this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName)); + return this; + } + + public JcaPGPKeyPairGeneratorProvider setSecureRandom(SecureRandom random) + { + this.secureRandom = random; + return this; + } + + + @Override + public PGPKeyPairGenerator get(int version, Date creationTime) + { + return new JcaPGPKeyPairGenerator(version, creationTime, helper, secureRandom); + } + + private static class JcaPGPKeyPairGenerator + extends PGPKeyPairGenerator + { + + private final OperatorHelper helper; + + public JcaPGPKeyPairGenerator(int version, Date creationTime, OperatorHelper helper, SecureRandom random) + { + super(version, creationTime, random); + this.helper = helper; + } + + @Override + public PGPKeyPair generateRsaKeyPair(BigInteger exponent, int bitStrength) + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("RSA"); + gen.initialize(new RSAKeyGenParameterSpec(bitStrength, exponent)); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.RSA_GENERAL, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate RSA key pair", e); + } + } + + @Override + public PGPKeyPair generateEd25519KeyPair() + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("EDDSA"); + gen.initialize(new EdDSAParameterSpec("Ed25519")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.Ed25519, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate Ed25519 key pair", e); + } + } + + @Override + public PGPKeyPair generateEd448KeyPair() + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("EDDSA"); + gen.initialize(new EdDSAParameterSpec("Ed448")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.Ed448, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate Ed448 key pair", e); + } + } + + @Override + public PGPKeyPair generateX25519KeyPair() + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("XDH"); + gen.initialize(new XDHParameterSpec("X25519")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.X25519, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate X25519 key pair", e); + } + } + + @Override + public PGPKeyPair generateX448KeyPair() + throws PGPException + { + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("XDH"); + gen.initialize(new XDHParameterSpec("X448")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.X448, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate X448 key pair", e); + } + } + + @Override + public PGPKeyPair generateLegacyEd25519KeyPair() + throws PGPException + { + if (version == PublicKeyPacket.VERSION_6) + { + throw new PGPException("An implementation MUST NOT generate a v6 LegacyEd25519 key pair."); + } + + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("EDDSA"); + gen.initialize(new EdDSAParameterSpec("Ed25519")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.EDDSA_LEGACY, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate LegacyEd25519 key pair."); + } + } + + @Override + public PGPKeyPair generateLegacyX25519KeyPair() + throws PGPException + { + if (version == PublicKeyPacket.VERSION_6) + { + throw new PGPException("An implementation MUST NOT generate a v6 LegacyX25519 key pair."); + } + + try + { + KeyPairGenerator gen = helper.createKeyPairGenerator("XDH"); + gen.initialize(new XDHParameterSpec("X25519")); + KeyPair keyPair = gen.generateKeyPair(); + return new JcaPGPKeyPair(version, PublicKeyAlgorithmTags.ECDH, keyPair, creationTime); + } + catch (GeneralSecurityException e) + { + throw new PGPException("Cannot generate LegacyX25519 key pair.", e); + } + } + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java new file mode 100644 index 0000000000..daf0fc5d89 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPKeyPairGeneratorTest.java @@ -0,0 +1,303 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPairGeneratorProvider; + +import java.util.Date; + +public class PGPKeyPairGeneratorTest + extends AbstractPgpKeyPairTest +{ + + @Override + public String getName() + { + return "PGPKeyPairGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + performWith((int version, Date creationTime) -> + new BcPGPKeyPairGeneratorProvider() + .get(version, creationTime)); + performWith((int version, Date creationTime) -> + new JcaPGPKeyPairGeneratorProvider() + .setProvider(new BouncyCastleProvider()) + .get(version, creationTime)); + } + + private void performWith(Factory factory) + throws PGPException + { + testGenerateV4RsaKey(factory); + testGenerateV6RsaKey(factory); + + testGenerateV6Ed448Key(factory); + testGenerateV4Ed448Key(factory); + + testGenerateV6Ed25519Key(factory); + testGenerateV4Ed25519Key(factory); + + testGenerateV6X448Key(factory); + testGenerateV4X448Key(factory); + + testGenerateV6X25519Key(factory); + testGenerateV4X25519Key(factory); + + // Legacy formats + testGenerateV6LegacyEd25519KeyFails(factory); + testGenerateV4LegacyEd215519Key(factory); + + testGenerateV6LegacyX25519KeyFails(factory); + testGenerateV4LegacyX215519Key(factory); + } + + private void testGenerateV4RsaKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateRsaKeyPair(3072); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.RSA_GENERAL); + isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getBitStrength(), 3072); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6RsaKey(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + PGPKeyPair kp = gen.generateRsaKeyPair(4096); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.RSA_GENERAL); + isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getBitStrength(), 4096); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6Ed25519Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + PGPKeyPair kp = gen.generateEd25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.Ed25519); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), Ed25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4Ed25519Key(Factory factory) + throws PGPException + { + + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateEd25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.Ed25519); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), Ed25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6Ed448Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + PGPKeyPair kp = gen.generateEd448KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.Ed448); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), Ed448PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4Ed448Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateEd448KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.Ed448); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), Ed448PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6X25519Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + PGPKeyPair kp = gen.generateX25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.X25519); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), X25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4X25519Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateX25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.X25519); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), X25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6X448Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + PGPKeyPair kp = gen.generateX448KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_6); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.X448); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), X448PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV4X448Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateX448KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.X448); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), X448PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + + private void testGenerateV4LegacyEd215519Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateLegacyEd25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.EDDSA_LEGACY); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), Ed25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + private void testGenerateV6LegacyEd25519KeyFails(Factory factory) + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + isNotNull( + "Expected exception when attempting to generate v6 LegacyEd25519 key with (" + gen.getClass().getSimpleName() + ")", + testException( + "An implementation MUST NOT generate a v6 LegacyEd25519 key pair.", + "PGPException", + gen::generateLegacyEd25519KeyPair)); + } + + private void testGenerateV6LegacyX25519KeyFails(Factory factory) + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_6, creationTime); + isNotNull( + "Expected exception when attempting to generate v6 LegacyX25519 key with (" + gen.getClass().getSimpleName() + ")", + testException( + "An implementation MUST NOT generate a v6 LegacyX25519 key pair.", + "PGPException", + gen::generateLegacyX25519KeyPair)); + } + + private void testGenerateV4LegacyX215519Key(Factory factory) + throws PGPException + { + Date creationTime = currentTimeRounded(); + PGPKeyPairGenerator gen = factory.create(PublicKeyPacket.VERSION_4, creationTime); + PGPKeyPair kp = gen.generateLegacyX25519KeyPair(); + + isEquals("Key version mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getVersion(), PublicKeyPacket.VERSION_4); + isEquals("Key algorithm mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getAlgorithm(), PublicKeyAlgorithmTags.ECDH); + // isEquals("Key bit-strength mismatch (" + gen.getClass().getSimpleName() + ")", + // kp.getPublicKey().getBitStrength(), X25519PublicBCPGKey.LENGTH * 8); + isEquals("Key creation time mismatch (" + gen.getClass().getSimpleName() + ")", + kp.getPublicKey().getCreationTime(), creationTime); + } + + public static void main(String[] args) + { + runTest(new PGPKeyPairGeneratorTest()); + } + + @FunctionalInterface + private interface Factory + { + PGPKeyPairGenerator create(int version, Date creationTime); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index a603b5f55b..f150bc7e1f 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -85,7 +85,8 @@ public class RegressionTest new PGPv5KeyTest(), new PGPv5MessageDecryptionTest(), - new PGPv6SignatureTest() + new PGPv6SignatureTest(), + new PGPKeyPairGeneratorTest() }; public static void main(String[] args) From 2dcb6e71c3cdf006e9a87f58e64a487caa9e2447 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Oct 2024 11:51:47 +0200 Subject: [PATCH 12/53] Generics for PGPSignatureSubpacketGenerator list --- .../bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index dbf1e513cc..342b80ce4a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -38,7 +38,7 @@ */ public class PGPSignatureSubpacketGenerator { - List packets = new ArrayList(); + List packets = new ArrayList(); /** * Base constructor, creates an empty generator. From 5b4f6550816be6fe650c1677febfb698aaea04f7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Oct 2024 11:52:05 +0200 Subject: [PATCH 13/53] Add removePacketsOfType() method --- .../openpgp/PGPSignatureSubpacketGenerator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index 342b80ce4a..4c453f7b46 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -549,6 +549,23 @@ public boolean removePacket(SignatureSubpacket packet) return packets.remove(packet); } + /** + * Remove all {@link SignatureSubpacket} objects of the given subpacketType from the underlying subpacket vector. + * @param subpacketType type to remove + * @return true if any packet was removed, false otherwise + */ + public boolean removePacketsOfType(int subpacketType) + { + boolean remove = false; + for (int i = packets.size() - 1; i >= 0; i--) { + if (packets.get(i).getType() == subpacketType) { + packets.remove(i); + remove = true; + } + } + return remove; + } + /** * Return true if a particular subpacket type exists. * From 84af3bfe4ba4f769f27749495ecad4424f93b988 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Oct 2024 11:52:35 +0200 Subject: [PATCH 14/53] Add PublicKeyUtils --- .../org/bouncycastle/bcpg/PublicKeyUtils.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java diff --git a/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java new file mode 100644 index 0000000000..3ac93f3fa0 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/bcpg/PublicKeyUtils.java @@ -0,0 +1,54 @@ +package org.bouncycastle.bcpg; + +/** + * Utility methods related to OpenPGP public key algorithms. + */ +public class PublicKeyUtils +{ + + /** + * Return true, if the public key algorithm that corresponds to the given ID is capable of signing. + * @param publicKeyAlgorithm public key algorithm id + * @return true if algorithm can sign + */ + public static boolean isSigningAlgorithm(int publicKeyAlgorithm) + { + switch (publicKeyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_SIGN: + case PublicKeyAlgorithmTags.DSA: + case PublicKeyAlgorithmTags.ECDSA: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + case PublicKeyAlgorithmTags.EDDSA_LEGACY: + case PublicKeyAlgorithmTags.Ed25519: + case PublicKeyAlgorithmTags.Ed448: + return true; + default: + return false; + } + } + + /** + * Return true, if the public key algorithm that corresponds to the given ID is capable of encryption. + * @param publicKeyAlgorithm public key algorithm id + * @return true if algorithm can encrypt + */ + public static boolean isEncryptionAlgorithm(int publicKeyAlgorithm) + { + switch (publicKeyAlgorithm) + { + case PublicKeyAlgorithmTags.RSA_GENERAL: + case PublicKeyAlgorithmTags.RSA_ENCRYPT: + case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: + case PublicKeyAlgorithmTags.ECDH: + case PublicKeyAlgorithmTags.ELGAMAL_GENERAL: + case PublicKeyAlgorithmTags.DIFFIE_HELLMAN: + case PublicKeyAlgorithmTags.X25519: + case PublicKeyAlgorithmTags.X448: + return true; + default: + return false; + } + } +} From b943759982e9c240b0fd15bd30fd31a589091c70 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Oct 2024 11:53:18 +0200 Subject: [PATCH 15/53] Implement high-level OpenPGPV6KeyGenerator class --- .../openpgp/PGPV6KeyRingGenerator.java | 31 - .../openpgp/api/KeyPairGeneratorCallback.java | 21 + .../openpgp/api/OpenPGPV6KeyGenerator.java | 586 ++++++++++++++++++ .../api/SignatureSubpacketsFunction.java | 18 + .../PGPContentSignerBuilderProvider.java | 15 + .../bc/BcPGPContentSignerBuilderProvider.java | 21 + .../JcaPGPContentSignerBuilderProvider.java | 61 ++ .../test/PGPV6KeyRingGeneratorTest.java | 71 +++ 8 files changed, 793 insertions(+), 31 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilderProvider.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java deleted file mode 100644 index fce6a106a9..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPV6KeyRingGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -package 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; - -public class PGPV6KeyRingGenerator -{ - public PGPV6KeyRingGenerator( - PGPKeyPair primaryKey, - PGPSignatureSubpacketVector hashedPcks, - PGPSignatureSubpacketVector unhashedPcks, - PGPContentSignerBuilder keySignerBuilder, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { - - } - - public PGPV6KeyRingGenerator( - PGPSecretKeyRing originalSecretRing, - PBESecretKeyDecryptor secretKeyDecryptor, - PGPDigestCalculator checksumCalculator, - PGPContentSignerBuilder keySignerBuilder, - PBESecretKeyEncryptor keyEncryptor) - throws PGPException - { - - } -} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java new file mode 100644 index 0000000000..274122cebe --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java @@ -0,0 +1,21 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; + +/** + * Callback to generate a {@link PGPKeyPair} from a {@link PGPKeyPairGenerator} instance. + */ +@FunctionalInterface +public interface KeyPairGeneratorCallback { + /** + * Generate a {@link PGPKeyPair} by calling a factory method on a given generator instance. + * + * @param generator PGPKeyPairGenerator + * @return generated key pair + * @throws PGPException + */ + PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException; +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java new file mode 100644 index 0000000000..bc58bb137d --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -0,0 +1,586 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.PublicKeyUtils; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.bcpg.sig.Features; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +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.PGPUserAttributeSubpacketVector; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * High-level generator class for OpenPGP v6 keys. + */ +public class OpenPGPV6KeyGenerator +{ + private static final long SECONDS_PER_MINUTE = 60; + private static final long SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; + private static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; + private static final long SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY; + + public static SignatureSubpacketsFunction DEFAULT_AEAD_ALGORITHM_PREFERENCES = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + subpackets.setPreferredAEADCiphersuites(PreferredAEADCiphersuites.builder(false) + .addCombination(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB) + .addCombination(SymmetricKeyAlgorithmTags.AES_192, AEADAlgorithmTags.OCB) + .addCombination(SymmetricKeyAlgorithmTags.AES_128, AEADAlgorithmTags.OCB)); + return subpackets; + }; + + public static SignatureSubpacketsFunction DEFAULT_SYMMETRIC_KEY_PREFERENCES = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); + subpackets.setPreferredSymmetricAlgorithms(false, new int[] { + SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 + }); + return subpackets; + }; + + public static SignatureSubpacketsFunction DEFAULT_HASH_ALGORITHM_PREFERENCES = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_HASH_ALGS); + subpackets.setPreferredHashAlgorithms(false, new int[] { + HashAlgorithmTags.SHA3_512, HashAlgorithmTags.SHA3_256, + HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256 + }); + return subpackets; + }; + + public static SignatureSubpacketsFunction DEFAULT_FEATURES = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); + subpackets.setFeature(false, (byte) (Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); + return subpackets; + }; + + private final Implementation impl; + private final Configuration conf; + + /** + * Generate a new OpenPGP key generator for v6 keys. + * + * @param kpGenProvider key pair generator provider + * @param contentSignerBuilderProvider content signer builder provider + * @param digestCalculatorProvider digest calculator provider + * @param creationTime key creation time + */ + public OpenPGPV6KeyGenerator( + PGPKeyPairGeneratorProvider kpGenProvider, + PGPContentSignerBuilderProvider contentSignerBuilderProvider, + PGPDigestCalculatorProvider digestCalculatorProvider, + Date creationTime) + { + this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider); + this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); + } + + /** + * Specify the OpenPGP key's primary key. + * @param keyGenCallback callback to select a primary key type + * @param directKeySubpackets callback to modify the direct-key signature's hashed subpackets + * @param encryptor key encryptor + * @return builder + * @throws PGPException + */ + public WithPrimaryKey withPrimaryKey( + KeyPairGeneratorCallback keyGenCallback, + SignatureSubpacketsFunction directKeySubpackets, + PBESecretKeyEncryptor encryptor) + throws PGPException + { + PGPKeyPair pkPair = keyGenCallback.generateFrom( + impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + if (!PublicKeyUtils.isSigningAlgorithm(pkPair.getPublicKey().getAlgorithm())) + { + throw new PGPException("Primary key MUST use signing-capable algorithm."); + } + + return primaryKeyWithDirectKeySig( + pkPair, + subpackets -> { + subpackets.setIssuerFingerprint(true, pkPair.getPublicKey()); + subpackets.setSignatureCreationTime(true, conf.creationTime); + subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); + subpackets = DEFAULT_HASH_ALGORITHM_PREFERENCES.apply(subpackets); + subpackets = DEFAULT_SYMMETRIC_KEY_PREFERENCES.apply(subpackets); + subpackets = DEFAULT_AEAD_ALGORITHM_PREFERENCES.apply(subpackets); + subpackets.setPreferredCompressionAlgorithms(true, new int[] { + // prefer no compression + }); + subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); + return subpackets; + }, + directKeySubpackets, + encryptor); + } + + /** + * Specify the primary key and attach a direct-key signature. + * The direct-key signature's subpackets will first be modified using the baseSubpackets callback, followed + * by the customSubpackets callback. + * If both baseSubpackets and customSubpackets are null, no direct-key signature will be attached. + * + * @param primaryKeyPair primary key pair + * @param baseSubpackets base signature subpackets callback + * @param customSubpackets user-provided signature subpackets callback + * @param encryptor key encryptor + * @return builder + * @throws PGPException + */ + private WithPrimaryKey primaryKeyWithDirectKeySig( + PGPKeyPair primaryKeyPair, + SignatureSubpacketsFunction baseSubpackets, + SignatureSubpacketsFunction customSubpackets, + PBESecretKeyEncryptor encryptor) + throws PGPException + { + if (baseSubpackets != null || customSubpackets != null) + { + // DK sig + PGPSignatureGenerator dkSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(primaryKeyPair.getPublicKey()), + primaryKeyPair.getPublicKey()); + dkSigGen.init(PGPSignature.DIRECT_KEY, primaryKeyPair.getPrivateKey()); + + PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); + // application-dictated subpackets + if (baseSubpackets != null) + { + subpackets = baseSubpackets.apply(subpackets); + } + + // Allow the user to modify the direct-key signature subpackets + if (customSubpackets != null) + { + subpackets = customSubpackets.apply(subpackets); + } + + dkSigGen.setHashedSubpackets(subpackets.generate()); + + PGPSignature dkSig = dkSigGen.generateCertification(primaryKeyPair.getPublicKey()); + primaryKeyPair = new PGPKeyPair( + PGPPublicKey.addCertification(primaryKeyPair.getPublicKey(), dkSig), + primaryKeyPair.getPrivateKey()); + } + + Key primaryKey = new Key(primaryKeyPair, encryptor); + + return new WithPrimaryKey(impl, conf, primaryKey); + } + + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences. + * @param keyEncryptor encryptor for the secret key material + * @return sign-only OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing signOnlyKey(PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + return signOnlyKey(keyEncryptor, null); + } + + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences whose subpackets can be + * modified by providing a {@link SignatureSubpacketsFunction}. + * @param keyEncryptor encryptor for the secret key material + * @param userSubpackets callback to modify the hashed signature subpacket area of the direct-key signature. + * @return sign-only OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing signOnlyKey( + PBESecretKeyEncryptor keyEncryptor, + SignatureSubpacketsFunction userSubpackets) + throws PGPException + { + return primaryKeyWithDirectKeySig( + impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime).generateEd25519KeyPair(), + baseSubpackets -> { + // remove unrelated subpackets not needed for sign-only keys + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); + + // replace key flags to add SIGN_DATA + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + baseSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); + return baseSubpackets; + }, + userSubpackets, // apply user-provided subpacket changes + keyEncryptor) + .build(); + } + + /** + * Intermediate builder class. + * Constructs an OpenPGP key from a specified primary key. + */ + public static class WithPrimaryKey + { + + private final Implementation impl; + private final Configuration conf; + private Key primaryKey; + private List subkeys = new ArrayList(); + + /** + * Builder. + * @param implementation cryptographic implementation + * @param configuration key configuration + * @param primaryKey specified primary key + */ + private WithPrimaryKey(Implementation implementation, Configuration configuration, Key primaryKey) + { + this.impl = implementation; + this.conf = configuration; + this.primaryKey = primaryKey; + } + + /** + * Attach a User-ID with a positive certification to the key. + * @param userId user-id + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addUserId(String userId) + throws PGPException + { + return addUserId(userId, null); + } + + /** + * Attach a User-ID with a positive certification to the key. + * The subpackets of the user-id certification can be modified using the userIdSubpackets callback. + * + * @param userId user-id + * @param userIdSubpackets callback to modify the certification subpackets + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addUserId( + String userId, + SignatureSubpacketsFunction userIdSubpackets) + throws PGPException + { + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + primaryKey.pair.getPublicKey()); + uidSigGen.init(PGPSignature.POSITIVE_CERTIFICATION, primaryKey.pair.getPrivateKey()); + + PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); + subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); + subpackets.setSignatureCreationTime(true, conf.creationTime); + + if (userIdSubpackets != null) + { + subpackets = userIdSubpackets.apply(subpackets); + } + uidSigGen.setHashedSubpackets(subpackets.generate()); + + PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.pair.getPublicKey()); + PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userId, uidSig); + primaryKey = new Key(new PGPKeyPair(pubKey, primaryKey.pair.getPrivateKey()), primaryKey.encryptor); + + return this; + } + + /** + * Attach a UserAttribute with a positive certification to the key. + * @param userAttribute user attribute + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addUserAttribute(PGPUserAttributeSubpacketVector userAttribute) + throws PGPException + { + return addUserAttribute(userAttribute, null); + } + + /** + * Attach a UserAttribute with a positive certification to the key. + * The subpackets of the user-attribute certification can be modified using the userAttributeSubpackets callback. + * @param userAttribute user attribute + * @param userAttributeSubpackets callback to modify the certification subpackets + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addUserAttribute( + PGPUserAttributeSubpacketVector userAttribute, + SignatureSubpacketsFunction userAttributeSubpackets) + throws PGPException + { + PGPSignatureGenerator uAttrSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + primaryKey.pair.getPublicKey()); + uAttrSigGen.init(PGPSignature.POSITIVE_CERTIFICATION, primaryKey.pair.getPrivateKey()); + + PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); + subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); + subpackets.setSignatureCreationTime(true, conf.creationTime); + + if (userAttributeSubpackets != null) + { + subpackets = userAttributeSubpackets.apply(subpackets); + } + uAttrSigGen.setHashedSubpackets(subpackets.generate()); + + PGPSignature uAttrSig = uAttrSigGen.generateCertification(userAttribute, primaryKey.pair.getPublicKey()); + PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userAttribute, uAttrSig); + primaryKey = new Key(new PGPKeyPair(pubKey, primaryKey.pair.getPrivateKey()), primaryKey.encryptor); + + return this; + } + + /** + * Add an encryption-capable X25519 subkey to the OpenPGP key. + * @param encryptor encryptor to encrypt the subkey. + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey(PBESecretKeyEncryptor encryptor) + throws PGPException + { + return addEncryptionSubkey(PGPKeyPairGenerator::generateX25519KeyPair, encryptor); + } + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * @param generatorCallback callback to specify the encryption key type. + * @param encryptor encryptor to encrypt the encryption subkey + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey( + KeyPairGeneratorCallback generatorCallback, + PBESecretKeyEncryptor encryptor) + throws PGPException + { + PGPKeyPairGenerator generator = impl.kpGenProvider.get( + primaryKey.pair.getPublicKey().getVersion(), + conf.creationTime + ); + PGPKeyPair subkey = generatorCallback.generateFrom(generator); + + return addEncryptionSubkey(subkey, null, encryptor); + } + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * @param encryptionSubkey encryption subkey + * @param bindingSubpacketsCallback callback to modify the subkey binding signature subpackets + * @param encryptor encryptor to encrypt the encryption subkey + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey( + PGPKeyPair encryptionSubkey, + SignatureSubpacketsFunction bindingSubpacketsCallback, + PBESecretKeyEncryptor encryptor) + throws PGPException + { + if (!PublicKeyUtils.isEncryptionAlgorithm(encryptionSubkey.getPublicKey().getAlgorithm())) + { + throw new PGPException("Encryption key MUST use encryption-capable algorithm."); + } + // generate binding signature + PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); + subpackets.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); + subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); + subpackets.setSignatureCreationTime(true, conf.creationTime); + + // allow subpacket customization + if (bindingSubpacketsCallback != null) + { + subpackets = bindingSubpacketsCallback.apply(subpackets); + } + + PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + primaryKey.pair.getPublicKey()); + bindingSigGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.pair.getPrivateKey()); + bindingSigGen.setHashedSubpackets(subpackets.generate()); + + PGPSignature bindingSig = bindingSigGen.generateCertification(primaryKey.pair.getPublicKey(), encryptionSubkey.getPublicKey()); + PGPPublicKey publicSubkey = PGPPublicKey.addCertification(encryptionSubkey.getPublicKey(), bindingSig); + Key subkey = new Key(new PGPKeyPair(publicSubkey, encryptionSubkey.getPrivateKey()), encryptor); + subkeys.add(subkey); + return this; + } + + public WithPrimaryKey addSigningSubkey(PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + return addSigningSubkey(PGPKeyPairGenerator::generateEd25519KeyPair, keyEncryptor); + } + + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + return addSigningSubkey( + generatorCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)), + null, + null, + keyEncryptor); + } + + public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, + SignatureSubpacketsFunction bindingSignatureCallback, + SignatureSubpacketsFunction backSignatureCallback, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + if (!PublicKeyUtils.isSigningAlgorithm(signingKey.getPublicKey().getAlgorithm())) + { + throw new PGPException("Signing key MUST use signing-capable algorithm."); + } + + PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); + backSigSubpackets.setIssuerFingerprint(true, signingKey.getPublicKey()); + backSigSubpackets.setSignatureCreationTime(true, conf.creationTime); + if (backSignatureCallback != null) { + backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); + } + + PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); + bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); + bindingSigSubpackets.setSignatureCreationTime(true, conf.creationTime); + bindingSigSubpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); + + PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(signingKey.getPublicKey()), + signingKey.getPublicKey()); + backSigGen.init(PGPSignature.PRIMARYKEY_BINDING, signingKey.getPrivateKey()); + backSigGen.setHashedSubpackets(backSigSubpackets.generate()); + PGPSignature backSig = backSigGen.generateCertification( + primaryKey.pair.getPublicKey(), signingKey.getPublicKey()); + + try + { + bindingSigSubpackets.addEmbeddedSignature(false, backSig); + } + catch (IOException e) + { + throw new PGPException("Cannot embed back-signature.", e); + } + + if (bindingSignatureCallback != null) + { + bindingSigSubpackets = bindingSignatureCallback.apply(bindingSigSubpackets); + } + + PGPSignatureGenerator bindingSigGen = new PGPSignatureGenerator( + impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), + primaryKey.pair.getPublicKey()); + bindingSigGen.init(PGPSignature.SUBKEY_BINDING, primaryKey.pair.getPrivateKey()); + bindingSigGen.setHashedSubpackets(bindingSigSubpackets.generate()); + + PGPSignature bindingSig = bindingSigGen.generateCertification( + primaryKey.pair.getPublicKey(), signingKey.getPublicKey()); + + PGPPublicKey signingPubKey = PGPPublicKey.addCertification(signingKey.getPublicKey(), bindingSig); + signingKey = new PGPKeyPair(signingPubKey, signingKey.getPrivateKey()); + subkeys.add(new Key(signingKey, keyEncryptor)); + + return this; + } + + /** + * Build the {@link PGPSecretKeyRing OpenPGP key}. + * @return OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing build() + throws PGPException + { + PGPSecretKey primarySecretKey = new PGPSecretKey( + primaryKey.pair.getPrivateKey(), + primaryKey.pair.getPublicKey(), + impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + true, + primaryKey.encryptor); + List keys = new ArrayList<>(); + keys.add(primarySecretKey); + + for (Key key : subkeys) { + PGPSecretKey subkey = new PGPSecretKey( + key.pair.getPrivateKey(), + key.pair.getPublicKey(), + impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + false, + key.encryptor); + keys.add(subkey); + } + return new PGPSecretKeyRing(keys); + } + } + + /** + * Bundle implementation-specific provider classes. + */ + private static class Implementation + { + final PGPKeyPairGeneratorProvider kpGenProvider; + final PGPContentSignerBuilderProvider contentSignerBuilderProvider; + final PGPDigestCalculatorProvider digestCalculatorProvider; + + public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, + PGPContentSignerBuilderProvider contentSignerBuilderProvider, + PGPDigestCalculatorProvider digestCalculatorProvider) + { + this.kpGenProvider = keyPairGeneratorProvider; + this.contentSignerBuilderProvider = contentSignerBuilderProvider; + this.digestCalculatorProvider = digestCalculatorProvider; + } + } + + /** + * Bundle configuration-specific data. + */ + private static class Configuration + { + final Date creationTime; + + public Configuration(Date creationTime) + { + this.creationTime = creationTime; + } + } + + /** + * Pair of a {@link PGPKeyPair} and (nullable) {@link PBESecretKeyEncryptor}. + */ + private static class Key + { + private PGPKeyPair pair; + private PBESecretKeyEncryptor encryptor; + + public Key(PGPKeyPair key, PBESecretKeyEncryptor encryptor) + { + this.pair = key; + this.encryptor = encryptor; + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java new file mode 100644 index 0000000000..f9447b9234 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java @@ -0,0 +1,18 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; + +/** + * Callback to modify the contents of a {@link PGPSignatureSubpacketGenerator}. + */ +@FunctionalInterface +public interface SignatureSubpacketsFunction { + /** + * Apply some changes to the given {@link PGPSignatureSubpacketGenerator} and return the result. + * It is also possible to replace the whole {@link PGPSignatureSubpacketGenerator} by returning another instance. + * + * @param subpackets original subpackets + * @return modified subpackets + */ + PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java new file mode 100644 index 0000000000..7a00036bf3 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java @@ -0,0 +1,15 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.openpgp.PGPPublicKey; + +public abstract class PGPContentSignerBuilderProvider +{ + protected final int hashAlgorithmId; + + public PGPContentSignerBuilderProvider(int hashAlgorithmId) + { + this.hashAlgorithmId = hashAlgorithmId; + } + + public abstract PGPContentSignerBuilder get(PGPPublicKey signingKey); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilderProvider.java new file mode 100644 index 0000000000..6c0cdc21f6 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPContentSignerBuilderProvider.java @@ -0,0 +1,21 @@ +package org.bouncycastle.openpgp.operator.bc; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; + +public class BcPGPContentSignerBuilderProvider + extends PGPContentSignerBuilderProvider +{ + + public BcPGPContentSignerBuilderProvider(int hashAlgorithmId) + { + super(hashAlgorithmId); + } + + @Override + public PGPContentSignerBuilder get(PGPPublicKey signingKey) + { + return new BcPGPContentSignerBuilder(signingKey.getAlgorithm(), hashAlgorithmId); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilderProvider.java new file mode 100644 index 0000000000..1f90adda1f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPContentSignerBuilderProvider.java @@ -0,0 +1,61 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; + +import java.security.Provider; +import java.security.SecureRandom; + +public class JcaPGPContentSignerBuilderProvider + extends PGPContentSignerBuilderProvider +{ + private Provider digestProvider; + private Provider securityProvider; + private SecureRandom secureRandom; + + public JcaPGPContentSignerBuilderProvider(int hashAlgorithmId) + { + super(hashAlgorithmId); + } + + public JcaPGPContentSignerBuilderProvider setDigestProvider(Provider provider) + { + this.digestProvider = provider; + return this; + } + + public JcaPGPContentSignerBuilderProvider setSecurityProvider(Provider provider) + { + this.securityProvider = provider; + return this; + } + + public JcaPGPContentSignerBuilderProvider setSecureRandom(SecureRandom random) + { + this.secureRandom = random; + return this; + } + + @Override + public PGPContentSignerBuilder get(PGPPublicKey signingKey) + { + JcaPGPContentSignerBuilder builder = new JcaPGPContentSignerBuilder( + signingKey.getAlgorithm(), hashAlgorithmId); + if (digestProvider != null) + { + builder.setDigestProvider(digestProvider); + } + + if (securityProvider != null) + { + builder.setProvider(securityProvider); + } + + if (secureRandom != null) + { + builder.setSecureRandom(secureRandom); + } + return builder; + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java new file mode 100644 index 0000000000..b4e5b3213d --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java @@ -0,0 +1,71 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PacketFormat; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; + +public class PGPV6KeyRingGeneratorTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "PGPV6KeyRingGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + testGenerateMinimalKey(); + } + + private void testGenerateMinimalKey() + throws PGPException, IOException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( + new BcPGPKeyPairGeneratorProvider(), + new BcPGPContentSignerBuilderProvider(HashAlgorithmTags.SHA3_512), + new BcPGPDigestCalculatorProvider(), + creationTime + ); + PGPSecretKeyRing secretKeys = gen.withPrimaryKey( + PGPKeyPairGenerator::generateEd25519KeyPair, + subpackets -> + { + subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); + return subpackets; + }, + null) + .addUserId("Alice ") + .addEncryptionSubkey(null) + .addSigningSubkey(null) + .build(); + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKeys.encode(pOut); + pOut.close(); + aOut.close(); + System.out.println(bOut); + } + + public static void main(String[] args) + { + runTest(new PGPV6KeyRingGeneratorTest()); + } +} From 18beb4c26c61e37d05280793b6573f15f0e3cb6c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 11:38:26 +0200 Subject: [PATCH 16/53] Organize subpackets --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index bc58bb137d..5d9bc3afd8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -1,6 +1,7 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; @@ -65,12 +66,36 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + public static SignatureSubpacketsFunction DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); + subpackets.setPreferredCompressionAlgorithms(false, new int[] { + CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, + CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2 + }); + return subpackets; + }; + public static SignatureSubpacketsFunction DEFAULT_FEATURES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); subpackets.setFeature(false, (byte) (Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); return subpackets; }; + public static SignatureSubpacketsFunction SIGNING_SUBKEY_SUBPACKETS = subpackets -> { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); + return subpackets; + }; + + public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = subpackets -> { + subpackets = DEFAULT_FEATURES.apply(subpackets); + subpackets = DEFAULT_HASH_ALGORITHM_PREFERENCES.apply(subpackets); + subpackets = DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES.apply(subpackets); + subpackets = DEFAULT_SYMMETRIC_KEY_PREFERENCES.apply(subpackets); + subpackets = DEFAULT_AEAD_ALGORITHM_PREFERENCES.apply(subpackets); + return subpackets; + }; + private final Implementation impl; private final Configuration conf; @@ -119,12 +144,7 @@ public WithPrimaryKey withPrimaryKey( subpackets.setIssuerFingerprint(true, pkPair.getPublicKey()); subpackets.setSignatureCreationTime(true, conf.creationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); - subpackets = DEFAULT_HASH_ALGORITHM_PREFERENCES.apply(subpackets); - subpackets = DEFAULT_SYMMETRIC_KEY_PREFERENCES.apply(subpackets); - subpackets = DEFAULT_AEAD_ALGORITHM_PREFERENCES.apply(subpackets); - subpackets.setPreferredCompressionAlgorithms(true, new int[] { - // prefer no compression - }); + subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); return subpackets; }, From 9974aa01549df2603d7ca4cb926d5d867072cb84 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:23:55 +0200 Subject: [PATCH 17/53] Add overrides for setters with default subpacket criticality --- .../PGPSignatureSubpacketGenerator.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index 4c453f7b46..0bb2a13392 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -78,6 +78,18 @@ public void setRevocable(boolean isCritical, boolean isRevocable) packets.add(new Revocable(isCritical, isRevocable)); } + /** + * Specify, whether the signature should be marked as exportable. + * If this subpacket is missing, the signature is treated as being exportable. + * The subpacket is marked as critical, as is required (for non-exportable signatures) by the spec. + * + * @param isExportable true if the signature should be exportable, false otherwise. + */ + public void setExportable(boolean isExportable) + { + setExportable(true, isExportable); + } + /** * Specify, whether or not the signature should be marked as exportable. * If this subpacket is missing, the signature is treated as being exportable. @@ -119,6 +131,18 @@ public void setTrust(boolean isCritical, int depth, int trustAmount) packets.add(new TrustSignature(isCritical, depth, trustAmount)); } + /** + * Set the number of seconds a key is valid for after the time of its creation. A + * value of zero means the key never expires. + * The subpacket will be marked as critical, as is recommended by the spec. + * + * @param seconds seconds from key creation to expiration + */ + public void setKeyExpirationTime(long seconds) + { + setKeyExpirationTime(true, seconds); + } + /** * Set the number of seconds a key is valid for after the time of its creation. A * value of zero means the key never expires. @@ -131,6 +155,18 @@ public void setKeyExpirationTime(boolean isCritical, long seconds) packets.add(new KeyExpirationTime(isCritical, seconds)); } + /** + * Set the number of seconds a signature is valid for after the time of its creation. + * A value of zero means the signature never expires. + * The subpacket will be marked as critical, as is recommended by the spec. + *. + * @param seconds seconds from signature creation to expiration + */ + public void setSignatureExpirationTime(long seconds) + { + setSignatureExpirationTime(true, seconds); + } + /** * Set the number of seconds a signature is valid for after the time of its creation. * A value of zero means the signature never expires. @@ -143,6 +179,19 @@ public void setSignatureExpirationTime(boolean isCritical, long seconds) packets.add(new SignatureExpirationTime(isCritical, seconds)); } + /** + * Set the creation time for the signature. + * The subpacket will be marked as critical, as is recommended by the spec. + *

+ * Note: this overrides the generation of a creation time when the signature is + * generated. + * @param date date + */ + public void setSignatureCreationTime(Date date) + { + setSignatureCreationTime(true, date); + } + /** * Set the creation time for the signature. *

@@ -274,6 +323,18 @@ public void addPolicyURI(boolean isCritical, String policyUri) packets.add(new PolicyURI(isCritical, policyUri)); } + /** + * Set this keys key flags. + * See {@link PGPKeyFlags}. + * The subpacket will be marked as critical, as is recommended by the spec. + * + * @param flags flags + */ + public void setKeyFlags(int flags) + { + setKeyFlags(true, flags); + } + /** * Set this keys key flags. * See {@link PGPKeyFlags}. @@ -515,6 +576,19 @@ public void setIntendedRecipientFingerprint(boolean isCritical, PGPPublicKey pub addIntendedRecipientFingerprint(isCritical, publicKey); } + /** + * Adds a intended recipient fingerprint for an encrypted payload the signature is associated with. + * The subpacket will be marked as critical, as is recommended by the spec. + * + * @param publicKey the public key the encrypted payload was encrypted against. + */ + public void addIntendedRecipientFingerprint(PGPPublicKey publicKey) + { + // RFC9580 states, that the packet SHOULD be critical if generated in a v6 signature, + // but it doesn't harm to default to critical for any signature version + addIntendedRecipientFingerprint(true, publicKey); + } + /** * Adds a intended recipient fingerprint for an encrypted payload the signature is associated with. * @@ -627,6 +701,17 @@ private boolean contains(int type) return false; } + /** + * Adds a regular expression. + * The subpacket is marked as critical, as is recommended by the spec. + * + * @param regularExpression the regular expression + */ + public void addRegularExpression(String regularExpression) + { + addRegularExpression(true, regularExpression); + } + /** * Adds a regular expression. * From a66753c724399950bc41e3abf0d96fc1317e1bdb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:24:26 +0200 Subject: [PATCH 18/53] Add PGPSecretKeyRing.toCertificate() --- .../bouncycastle/openpgp/PGPSecretKeyRing.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java index 98c1d82bf1..99827ef907 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java @@ -19,6 +19,7 @@ import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PacketTags; +import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.PublicSubkeyPacket; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SecretSubkeyPacket; @@ -492,6 +493,22 @@ public int size() return keys.size(); } + /** + * Return the OpenPGP certificate (Transferable Public Key) of this key. + * + * @return certificate + */ + public PGPPublicKeyRing toCertificate() + { + List pubKeys = new ArrayList<>(); + Iterator it = getPublicKeys(); + while (it.hasNext()) + { + pubKeys.add(it.next()); + } + return new PGPPublicKeyRing(pubKeys); + } + public byte[] getEncoded() throws IOException { From 49cefc91ba09f4e69955b6c725e9923c681e130c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:26:04 +0200 Subject: [PATCH 19/53] Remove methods for adding deprecated UserAttributes --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 78 +++++++------------ 1 file changed, 27 insertions(+), 51 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 5d9bc3afd8..3c40ff042d 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -18,7 +18,6 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; -import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; @@ -142,7 +141,7 @@ public WithPrimaryKey withPrimaryKey( pkPair, subpackets -> { subpackets.setIssuerFingerprint(true, pkPair.getPublicKey()); - subpackets.setSignatureCreationTime(true, conf.creationTime); + subpackets.setSignatureCreationTime(conf.creationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); @@ -304,70 +303,47 @@ public WithPrimaryKey addUserId( SignatureSubpacketsFunction userIdSubpackets) throws PGPException { - PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), - primaryKey.pair.getPublicKey()); - uidSigGen.init(PGPSignature.POSITIVE_CERTIFICATION, primaryKey.pair.getPrivateKey()); - - PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(true, conf.creationTime); - - if (userIdSubpackets != null) - { - subpackets = userIdSubpackets.apply(subpackets); - } - uidSigGen.setHashedSubpackets(subpackets.generate()); - - PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.pair.getPublicKey()); - PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userId, uidSig); - primaryKey = new Key(new PGPKeyPair(pubKey, primaryKey.pair.getPrivateKey()), primaryKey.encryptor); - - return this; + return addUserId(userId, PGPSignature.POSITIVE_CERTIFICATION, userIdSubpackets); } /** - * Attach a UserAttribute with a positive certification to the key. - * @param userAttribute user attribute + * Attach a User-ID with a positive certification to the key. + * The subpackets of the user-id certification can be modified using the userIdSubpackets callback. + * + * @param userId user-id + * @param certificationType signature type + * @param userIdSubpackets callback to modify the certification subpackets * @return builder * @throws PGPException */ - public WithPrimaryKey addUserAttribute(PGPUserAttributeSubpacketVector userAttribute) + public WithPrimaryKey addUserId( + String userId, + int certificationType, + SignatureSubpacketsFunction userIdSubpackets) throws PGPException { - return addUserAttribute(userAttribute, null); - } + if (!PGPSignature.isCertification(certificationType)) + { + throw new IllegalArgumentException("Signature type MUST be a certification type (0x10 - 0x13)"); + } - /** - * Attach a UserAttribute with a positive certification to the key. - * The subpackets of the user-attribute certification can be modified using the userAttributeSubpackets callback. - * @param userAttribute user attribute - * @param userAttributeSubpackets callback to modify the certification subpackets - * @return builder - * @throws PGPException - */ - public WithPrimaryKey addUserAttribute( - PGPUserAttributeSubpacketVector userAttribute, - SignatureSubpacketsFunction userAttributeSubpackets) - throws PGPException - { - PGPSignatureGenerator uAttrSigGen = new PGPSignatureGenerator( + PGPSignatureGenerator uidSigGen = new PGPSignatureGenerator( impl.contentSignerBuilderProvider.get(primaryKey.pair.getPublicKey()), primaryKey.pair.getPublicKey()); - uAttrSigGen.init(PGPSignature.POSITIVE_CERTIFICATION, primaryKey.pair.getPrivateKey()); + uidSigGen.init(certificationType, primaryKey.pair.getPrivateKey()); PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(true, conf.creationTime); + subpackets.setSignatureCreationTime(conf.creationTime); - if (userAttributeSubpackets != null) + if (userIdSubpackets != null) { - subpackets = userAttributeSubpackets.apply(subpackets); + subpackets = userIdSubpackets.apply(subpackets); } - uAttrSigGen.setHashedSubpackets(subpackets.generate()); + uidSigGen.setHashedSubpackets(subpackets.generate()); - PGPSignature uAttrSig = uAttrSigGen.generateCertification(userAttribute, primaryKey.pair.getPublicKey()); - PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userAttribute, uAttrSig); + PGPSignature uidSig = uidSigGen.generateCertification(userId, primaryKey.pair.getPublicKey()); + PGPPublicKey pubKey = PGPPublicKey.addCertification(primaryKey.pair.getPublicKey(), userId, uidSig); primaryKey = new Key(new PGPKeyPair(pubKey, primaryKey.pair.getPrivateKey()), primaryKey.encryptor); return this; @@ -428,7 +404,7 @@ public WithPrimaryKey addEncryptionSubkey( PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(true, conf.creationTime); + subpackets.setSignatureCreationTime(conf.creationTime); // allow subpacket customization if (bindingSubpacketsCallback != null) @@ -479,14 +455,14 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); backSigSubpackets.setIssuerFingerprint(true, signingKey.getPublicKey()); - backSigSubpackets.setSignatureCreationTime(true, conf.creationTime); + backSigSubpackets.setSignatureCreationTime(conf.creationTime); if (backSignatureCallback != null) { backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); } PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - bindingSigSubpackets.setSignatureCreationTime(true, conf.creationTime); + bindingSigSubpackets.setSignatureCreationTime(conf.creationTime); bindingSigSubpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( From 0de7f2136013394fd7e1f23ccd070de7b1afc94c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:26:41 +0200 Subject: [PATCH 20/53] Introduce BC implementation of OpenPGPV6KeyGenerator --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 48 +++++++++++++++++ .../test/BcOpenPGPV6KeyGeneratorTest.java} | 53 ++++++++++--------- 2 files changed, 77 insertions(+), 24 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java rename pg/src/test/java/org/bouncycastle/openpgp/{test/PGPV6KeyRingGeneratorTest.java => api/test/BcOpenPGPV6KeyGeneratorTest.java} (50%) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java new file mode 100644 index 0000000000..3ac4107d67 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -0,0 +1,48 @@ +package org.bouncycastle.openpgp.api; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; + +import java.util.Date; + +/** + * Bouncy Castle implementation of {@link OpenPGPV6KeyGenerator}. + */ +public class BcOpenPGPV6KeyGenerator + extends OpenPGPV6KeyGenerator +{ + + public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; + + public BcOpenPGPV6KeyGenerator() + { + this(DEFAULT_SIGNATURE_HASH_ALGORITHM); + } + + public BcOpenPGPV6KeyGenerator(Date creationTime) + { + this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); + } + + public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) + { + this(signatureHashAlgorithm, new Date()); + } + + /** + * Generate a new OpenPGP key generator for v6 keys. + * + * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key + * @param creationTime creation time of the key and signatures + */ + public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) + { + super( + new BcPGPKeyPairGeneratorProvider(), + new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), + new BcPGPDigestCalculatorProvider(), + creationTime); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java similarity index 50% rename from pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java rename to pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index b4e5b3213d..16fb8ac9a0 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPV6KeyRingGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -1,28 +1,25 @@ -package org.bouncycastle.openpgp.test; +package org.bouncycastle.openpgp.api.test; -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.api.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; -import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; +import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.Date; +import java.util.Iterator; -public class PGPV6KeyRingGeneratorTest +public class BcOpenPGPV6KeyGeneratorTest extends AbstractPgpKeyPairTest { @Override public String getName() { - return "PGPV6KeyRingGeneratorTest"; + return "OpenPGPV6KeyGeneratorTest"; } @Override @@ -33,13 +30,11 @@ public void performTest() } private void testGenerateMinimalKey() - throws PGPException, IOException + throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator gen = new OpenPGPV6KeyGenerator( - new BcPGPKeyPairGeneratorProvider(), - new BcPGPContentSignerBuilderProvider(HashAlgorithmTags.SHA3_512), - new BcPGPDigestCalculatorProvider(), + OpenPGPV6KeyGenerator gen = new BcOpenPGPV6KeyGenerator( + HashAlgorithmTags.SHA3_512, creationTime ); PGPSecretKeyRing secretKeys = gen.withPrimaryKey( @@ -55,17 +50,27 @@ private void testGenerateMinimalKey() .addSigningSubkey(null) .build(); - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); - BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); - secretKeys.encode(pOut); - pOut.close(); - aOut.close(); - System.out.println(bOut); + // Test creation time + for (PGPPublicKey key : secretKeys.toCertificate()) + { + isEquals(creationTime, key.getCreationTime()); + for (Iterator it = key.getSignatures(); it.hasNext(); ) { + PGPSignature sig = it.next(); + isEquals(creationTime, sig.getCreationTime()); + } + } + + PGPPublicKey primaryKey = secretKeys.getPublicKey(); + // Test UIDs + Iterator uids = primaryKey.getUserIDs(); + isEquals("Alice ", uids.next()); + isFalse(uids.hasNext()); + + } public static void main(String[] args) { - runTest(new PGPV6KeyRingGeneratorTest()); + runTest(new BcOpenPGPV6KeyGeneratorTest()); } } From 8473d7b16035daf4807cf29c16fc6cf3a3b66768 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:39:54 +0200 Subject: [PATCH 21/53] Add BcOpenPGPV6KeyGeneratorTest to RegressionTests --- .../java/org/bouncycastle/openpgp/test/RegressionTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index f150bc7e1f..2bf6629e13 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,6 +3,7 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; +import org.bouncycastle.openpgp.api.test.BcOpenPGPV6KeyGeneratorTest; import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; @@ -86,7 +87,8 @@ public class RegressionTest new PGPv5KeyTest(), new PGPv5MessageDecryptionTest(), new PGPv6SignatureTest(), - new PGPKeyPairGeneratorTest() + new PGPKeyPairGeneratorTest(), + new BcOpenPGPV6KeyGeneratorTest() }; public static void main(String[] args) From b4c91c41a12f36a04b456439b07cc2d7575cf2d3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Oct 2024 12:40:03 +0200 Subject: [PATCH 22/53] Javadoc, Checkstyle --- .../PGPSignatureSubpacketGenerator.java | 6 ++-- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 20 ++++++++--- .../openpgp/api/KeyPairGeneratorCallback.java | 3 +- .../openpgp/api/OpenPGPV6KeyGenerator.java | 35 +++++++++++++------ .../api/SignatureSubpacketsFunction.java | 3 +- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index 0bb2a13392..a0637326ba 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -631,8 +631,10 @@ public boolean removePacket(SignatureSubpacket packet) public boolean removePacketsOfType(int subpacketType) { boolean remove = false; - for (int i = packets.size() - 1; i >= 0; i--) { - if (packets.get(i).getType() == subpacketType) { + for (int i = packets.size() - 1; i >= 0; i--) + { + if (packets.get(i).getType() == subpacketType) + { packets.remove(i); remove = true; } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java index 3ac4107d67..f0b9e7e611 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -1,6 +1,5 @@ package org.bouncycastle.openpgp.api; -import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; @@ -14,25 +13,38 @@ public class BcOpenPGPV6KeyGenerator extends OpenPGPV6KeyGenerator { - public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; - + /** + * Create a new key generator for OpenPGP v6 keys. + */ public BcOpenPGPV6KeyGenerator() { this(DEFAULT_SIGNATURE_HASH_ALGORITHM); } + /** + * Create a new key generator for OpenPGP v6 keys. + * The key creation time will be set to {@code creationTime} + * + * @param creationTime creation time of the generated OpenPGP key + */ public BcOpenPGPV6KeyGenerator(Date creationTime) { this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); } + /** + * Create a new key generator for OpenPGP v6 keys. + * Signatures on the key will be generated using the specified {@code signatureHashAlgorithm}. + * + * @param signatureHashAlgorithm ID of the hash algorithm to be used for signature generation + */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) { this(signatureHashAlgorithm, new Date()); } /** - * Generate a new OpenPGP key generator for v6 keys. + * Create a new OpenPGP key generator for v6 keys. * * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java index 274122cebe..07d47f85b8 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/KeyPairGeneratorCallback.java @@ -8,7 +8,8 @@ * Callback to generate a {@link PGPKeyPair} from a {@link PGPKeyPairGenerator} instance. */ @FunctionalInterface -public interface KeyPairGeneratorCallback { +public interface KeyPairGeneratorCallback +{ /** * Generate a {@link PGPKeyPair} by calling a factory method on a given generator instance. * diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 3c40ff042d..4ea7e59bf4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -34,12 +34,15 @@ */ public class OpenPGPV6KeyGenerator { + public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; + private static final long SECONDS_PER_MINUTE = 60; private static final long SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; private static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; private static final long SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY; - public static SignatureSubpacketsFunction DEFAULT_AEAD_ALGORITHM_PREFERENCES = subpackets -> { + public static SignatureSubpacketsFunction DEFAULT_AEAD_ALGORITHM_PREFERENCES = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); subpackets.setPreferredAEADCiphersuites(PreferredAEADCiphersuites.builder(false) .addCombination(SymmetricKeyAlgorithmTags.AES_256, AEADAlgorithmTags.OCB) @@ -48,7 +51,8 @@ public class OpenPGPV6KeyGenerator return subpackets; }; - public static SignatureSubpacketsFunction DEFAULT_SYMMETRIC_KEY_PREFERENCES = subpackets -> { + public static SignatureSubpacketsFunction DEFAULT_SYMMETRIC_KEY_PREFERENCES = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); subpackets.setPreferredSymmetricAlgorithms(false, new int[] { SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128 @@ -56,7 +60,8 @@ public class OpenPGPV6KeyGenerator return subpackets; }; - public static SignatureSubpacketsFunction DEFAULT_HASH_ALGORITHM_PREFERENCES = subpackets -> { + public static SignatureSubpacketsFunction DEFAULT_HASH_ALGORITHM_PREFERENCES = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_HASH_ALGS); subpackets.setPreferredHashAlgorithms(false, new int[] { HashAlgorithmTags.SHA3_512, HashAlgorithmTags.SHA3_256, @@ -65,7 +70,8 @@ public class OpenPGPV6KeyGenerator return subpackets; }; - public static SignatureSubpacketsFunction DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES = subpackets -> { + public static SignatureSubpacketsFunction DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); subpackets.setPreferredCompressionAlgorithms(false, new int[] { CompressionAlgorithmTags.UNCOMPRESSED, CompressionAlgorithmTags.ZIP, @@ -74,19 +80,22 @@ public class OpenPGPV6KeyGenerator return subpackets; }; - public static SignatureSubpacketsFunction DEFAULT_FEATURES = subpackets -> { + public static SignatureSubpacketsFunction DEFAULT_FEATURES = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); subpackets.setFeature(false, (byte) (Features.FEATURE_MODIFICATION_DETECTION | Features.FEATURE_SEIPD_V2)); return subpackets; }; - public static SignatureSubpacketsFunction SIGNING_SUBKEY_SUBPACKETS = subpackets -> { + public static SignatureSubpacketsFunction SIGNING_SUBKEY_SUBPACKETS = subpackets -> + { subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); subpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); return subpackets; }; - public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = subpackets -> { + public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = subpackets -> + { subpackets = DEFAULT_FEATURES.apply(subpackets); subpackets = DEFAULT_HASH_ALGORITHM_PREFERENCES.apply(subpackets); subpackets = DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES.apply(subpackets); @@ -139,7 +148,8 @@ public WithPrimaryKey withPrimaryKey( return primaryKeyWithDirectKeySig( pkPair, - subpackets -> { + subpackets -> + { subpackets.setIssuerFingerprint(true, pkPair.getPublicKey()); subpackets.setSignatureCreationTime(conf.creationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); @@ -236,7 +246,8 @@ public PGPSecretKeyRing signOnlyKey( { return primaryKeyWithDirectKeySig( impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime).generateEd25519KeyPair(), - baseSubpackets -> { + baseSubpackets -> + { // remove unrelated subpackets not needed for sign-only keys baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); @@ -456,7 +467,8 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); backSigSubpackets.setIssuerFingerprint(true, signingKey.getPublicKey()); backSigSubpackets.setSignatureCreationTime(conf.creationTime); - if (backSignatureCallback != null) { + if (backSignatureCallback != null) + { backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); } @@ -520,7 +532,8 @@ public PGPSecretKeyRing build() List keys = new ArrayList<>(); keys.add(primarySecretKey); - for (Key key : subkeys) { + for (Key key : subkeys) + { PGPSecretKey subkey = new PGPSecretKey( key.pair.getPrivateKey(), key.pair.getPublicKey(), diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java index f9447b9234..2b2e0fd94b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java @@ -6,7 +6,8 @@ * Callback to modify the contents of a {@link PGPSignatureSubpacketGenerator}. */ @FunctionalInterface -public interface SignatureSubpacketsFunction { +public interface SignatureSubpacketsFunction +{ /** * Apply some changes to the given {@link PGPSignatureSubpacketGenerator} and return the result. * It is also possible to replace the whole {@link PGPSignatureSubpacketGenerator} by returning another instance. From 8b2ededd68088e9d5f90447343aae7d549fcbaf8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Oct 2024 23:15:31 +0200 Subject: [PATCH 23/53] AEAD secret key encryption --- .../openpgp/api/BcOpenPGPV6KeyGenerator.java | 6 ++ .../openpgp/api/OpenPGPV6KeyGenerator.java | 77 ++++++++++++++----- .../AEADSecretKeyEncryptorBuilder.java | 8 ++ ...AEADSecretKeyEncryptorBuilderProvider.java | 12 +++ .../PBESecretKeyEncryptorFactory.java | 8 ++ .../bc/BcAEADSecretKeyEncryptorBuilder.java | 2 + .../bc/BcAEADSecretKeyEncryptorFactory.java | 29 +++++++ .../bc/BcCFBSecretKeyEncryptorFactory.java | 41 ++++++++++ .../JcaAEADSecretKeyEncryptorBuilder.java | 2 + .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 18 ++++- 10 files changed, 178 insertions(+), 25 deletions(-) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java index f0b9e7e611..91b53106a0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.api; +import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; @@ -55,6 +56,11 @@ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) new BcPGPKeyPairGeneratorProvider(), new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), new BcPGPDigestCalculatorProvider(), + //* + new BcAEADSecretKeyEncryptorFactory(), + /*/ + new BcCFBSecretKeyEncryptorFactory(), + //*/ creationTime); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 4ea7e59bf4..f66c03cddf 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -19,6 +19,7 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; @@ -113,44 +114,64 @@ public class OpenPGPV6KeyGenerator * @param kpGenProvider key pair generator provider * @param contentSignerBuilderProvider content signer builder provider * @param digestCalculatorProvider digest calculator provider + * @param keyEncryptionBuilderProvider secret key encryption builder provider (AEAD) * @param creationTime key creation time */ public OpenPGPV6KeyGenerator( PGPKeyPairGeneratorProvider kpGenProvider, PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, + PBESecretKeyEncryptorFactory keyEncryptionBuilderProvider, Date creationTime) { - this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider); + this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider); this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); } - /** - * Specify the OpenPGP key's primary key. - * @param keyGenCallback callback to select a primary key type - * @param directKeySubpackets callback to modify the direct-key signature's hashed subpackets - * @param encryptor key encryptor - * @return builder - * @throws PGPException - */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback, SignatureSubpacketsFunction directKeySubpackets, - PBESecretKeyEncryptor encryptor) + char[] passphrase) throws PGPException { PGPKeyPair pkPair = keyGenCallback.generateFrom( impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); - if (!PublicKeyUtils.isSigningAlgorithm(pkPair.getPublicKey().getAlgorithm())) + return withPrimaryKey(pkPair, directKeySubpackets, passphrase); + } + + public WithPrimaryKey withPrimaryKey( + PGPKeyPair keyPair, + SignatureSubpacketsFunction directKeySubpackets, + char[] passphrase) + throws PGPException + { + if (passphrase == null) + { + return withPrimaryKey(keyPair, directKeySubpackets, (PBESecretKeyEncryptor) null); + } + return withPrimaryKey( + keyPair, + directKeySubpackets, + impl.keyEncryptorBuilderProvider.build(passphrase, keyPair.getPublicKey().getPublicKeyPacket()) + ); + } + + public WithPrimaryKey withPrimaryKey( + PGPKeyPair keyPair, + SignatureSubpacketsFunction directKeySubpackets, + PBESecretKeyEncryptor keyEncryptor) + throws PGPException + { + if (!PublicKeyUtils.isSigningAlgorithm(keyPair.getPublicKey().getAlgorithm())) { throw new PGPException("Primary key MUST use signing-capable algorithm."); } return primaryKeyWithDirectKeySig( - pkPair, + keyPair, subpackets -> { - subpackets.setIssuerFingerprint(true, pkPair.getPublicKey()); + subpackets.setIssuerFingerprint(true, keyPair.getPublicKey()); subpackets.setSignatureCreationTime(conf.creationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); @@ -158,7 +179,7 @@ public WithPrimaryKey withPrimaryKey( return subpackets; }, directKeySubpackets, - encryptor); + keyEncryptor); } /** @@ -219,14 +240,14 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( * Generate a sign-only OpenPGP key. * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. * It carries a single direct-key signature with signing-related preferences. - * @param keyEncryptor encryptor for the secret key material * @return sign-only OpenPGP key + * @param passphrase passphrase to encrypt the primary key * @throws PGPException */ - public PGPSecretKeyRing signOnlyKey(PBESecretKeyEncryptor keyEncryptor) + public PGPSecretKeyRing signOnlyKey(char[] passphrase) throws PGPException { - return signOnlyKey(keyEncryptor, null); + return signOnlyKey(passphrase, null); } /** @@ -234,18 +255,29 @@ public PGPSecretKeyRing signOnlyKey(PBESecretKeyEncryptor keyEncryptor) * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. * It carries a single direct-key signature with signing-related preferences whose subpackets can be * modified by providing a {@link SignatureSubpacketsFunction}. - * @param keyEncryptor encryptor for the secret key material + * @param passphrase passphrase * @param userSubpackets callback to modify the hashed signature subpacket area of the direct-key signature. * @return sign-only OpenPGP key * @throws PGPException */ public PGPSecretKeyRing signOnlyKey( + char[] passphrase, + SignatureSubpacketsFunction userSubpackets) + throws PGPException + { + PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime).generateEd25519KeyPair(); + PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider + .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); + return signOnlyKey(primaryKey, keyEncryptor, userSubpackets); + } + + public PGPSecretKeyRing signOnlyKey( + PGPKeyPair primaryKey, PBESecretKeyEncryptor keyEncryptor, SignatureSubpacketsFunction userSubpackets) throws PGPException { - return primaryKeyWithDirectKeySig( - impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime).generateEd25519KeyPair(), + return primaryKeyWithDirectKeySig(primaryKey, baseSubpackets -> { // remove unrelated subpackets not needed for sign-only keys @@ -554,14 +586,17 @@ private static class Implementation final PGPKeyPairGeneratorProvider kpGenProvider; final PGPContentSignerBuilderProvider contentSignerBuilderProvider; final PGPDigestCalculatorProvider digestCalculatorProvider; + final PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider; public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, PGPContentSignerBuilderProvider contentSignerBuilderProvider, - PGPDigestCalculatorProvider digestCalculatorProvider) + PGPDigestCalculatorProvider digestCalculatorProvider, + PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider) { this.kpGenProvider = keyPairGeneratorProvider; this.contentSignerBuilderProvider = contentSignerBuilderProvider; this.digestCalculatorProvider = digestCalculatorProvider; + this.keyEncryptorBuilderProvider = keyEncryptorBuilderProvider; } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java new file mode 100644 index 0000000000..2d5d5e1108 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java @@ -0,0 +1,8 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.bcpg.PublicKeyPacket; + +public interface AEADSecretKeyEncryptorBuilder +{ + PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKey); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java new file mode 100644 index 0000000000..5f6056b391 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java @@ -0,0 +1,12 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.bcpg.S2K; + +public abstract class AEADSecretKeyEncryptorBuilderProvider +{ + + public abstract AEADSecretKeyEncryptorBuilder get( + int aeadAlgorithm, + int symmetricAlgorithm, + S2K.Argon2Params argon2Params); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java new file mode 100644 index 0000000000..6317e41a9d --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java @@ -0,0 +1,8 @@ +package org.bouncycastle.openpgp.operator; + +import org.bouncycastle.bcpg.PublicKeyPacket; + +public abstract class PBESecretKeyEncryptorFactory +{ + public abstract PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket); +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorBuilder.java index c1ffcf0001..3b961fbdd4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorBuilder.java @@ -16,10 +16,12 @@ import org.bouncycastle.crypto.params.HKDFParameters; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.AEADSecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.util.Arrays; public class BcAEADSecretKeyEncryptorBuilder + implements AEADSecretKeyEncryptorBuilder { private int aeadAlgorithm; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java new file mode 100644 index 0000000000..5b3df6c88f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java @@ -0,0 +1,29 @@ +package org.bouncycastle.openpgp.operator.bc; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; + +/** + * Return a factory for {@link PBESecretKeyEncryptor} instances which protect the secret key material by deriving + * a key-encryption-key using {@link org.bouncycastle.bcpg.S2K#ARGON_2} S2K and apply + * that key using {@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_AEAD}. + *

+ * This particular factory uses OCB + AES256 for secret key protection and requires 2GiB of RAM + * for the Argon2 key derivation (see {@link S2K.Argon2Params#universallyRecommendedParameters()}). + */ +public class BcAEADSecretKeyEncryptorFactory + extends PBESecretKeyEncryptorFactory +{ + @Override + public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) { + return new BcAEADSecretKeyEncryptorBuilder( + AEADAlgorithmTags.OCB, + SymmetricKeyAlgorithmTags.AES_256, + S2K.Argon2Params.universallyRecommendedParameters()) + .build(passphrase, pubKeyPacket); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java new file mode 100644 index 0000000000..6979fedaa8 --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java @@ -0,0 +1,41 @@ +package org.bouncycastle.openpgp.operator.bc; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; + +/** + * Return a factory for {@link PBESecretKeyEncryptor} instances which protect the secret key material by deriving + * a key-encryption-key using {@link org.bouncycastle.bcpg.S2K#SALTED_AND_ITERATED} S2K and apply + * that key using {@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_SHA1} (CFB mode). + *

+ * This particular factory derives a key-encryption-key via salted+iterated S2K derivation using SHA256 + * and uses AES256 for secret key protection. + */ +public class BcCFBSecretKeyEncryptorFactory + extends PBESecretKeyEncryptorFactory +{ + @Override + public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) + { + PGPDigestCalculator checksumCalc; + try + { + checksumCalc = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA256); + } + catch (PGPException e) + { + throw new RuntimeException(e); // Does not happen in practice + } + + return new BcPBESecretKeyEncryptorBuilder( + SymmetricKeyAlgorithmTags.AES_256, + checksumCalc, + 0xff) // MAX iteration count + .build(passphrase); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorBuilder.java index 5fb1aca085..eee860acd9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorBuilder.java @@ -20,10 +20,12 @@ import org.bouncycastle.jcajce.util.ProviderJcaJceHelper; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.AEADSecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.util.Arrays; public class JcaAEADSecretKeyEncryptorBuilder + implements AEADSecretKeyEncryptorBuilder { private int aeadAlgorithm; private int symmetricAlgorithm; diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java index 16fb8ac9a0..6b5ddf55a8 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java @@ -1,6 +1,9 @@ package org.bouncycastle.openpgp.api.test; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -10,6 +13,8 @@ import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.Date; import java.util.Iterator; @@ -30,8 +35,7 @@ public void performTest() } private void testGenerateMinimalKey() - throws PGPException - { + throws PGPException, IOException { Date creationTime = currentTimeRounded(); OpenPGPV6KeyGenerator gen = new BcOpenPGPV6KeyGenerator( HashAlgorithmTags.SHA3_512, @@ -44,7 +48,7 @@ private void testGenerateMinimalKey() subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); return subpackets; }, - null) + "hello".toCharArray()) .addUserId("Alice ") .addEncryptionSubkey(null) .addSigningSubkey(null) @@ -66,7 +70,13 @@ private void testGenerateMinimalKey() isEquals("Alice ", uids.next()); isFalse(uids.hasNext()); - + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); + BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); + secretKeys.encode(pOut); + pOut.close(); + aOut.close(); + System.out.println(bOut); } public static void main(String[] args) From 88f81228aa9357cc87b96244f58e6f64daeb5994 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 15:34:29 +0200 Subject: [PATCH 24/53] Document PGPSecretKey.getS2KUsage() --- .../main/java/org/bouncycastle/openpgp/PGPSecretKey.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java index cd49368144..febf9c4204 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java @@ -499,6 +499,13 @@ public byte[] getFingerprint() /** * Return the S2K usage associated with this key. + * This value indicates, how the secret key material is protected: + *

    + *
  • {@link SecretKeyPacket#USAGE_NONE}: Unprotected
  • + *
  • {@link SecretKeyPacket#USAGE_CHECKSUM}: Password-protected using malleable CFB (deprecated)
  • + *
  • {@link SecretKeyPacket#USAGE_SHA1}: Password-protected using CFB
  • + *
  • {@link SecretKeyPacket#USAGE_AEAD}: Password-protected using AEAD (recommended)
  • + *
* * @return the key's S2K usage */ From ea34c8c6c70369cf8cb18c9951171b12705e973d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 15:37:53 +0200 Subject: [PATCH 25/53] Further progress with the new KeyGenerator API --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 237 ++++++++++++------ .../api/{ => bc}/BcOpenPGPV6KeyGenerator.java | 31 ++- .../api/jcajce/JcaOpenPGPV6KeyGenerator.java | 71 ++++++ .../openpgp/operator/PGPKeyPairGenerator.java | 39 +++ .../bc/BcAEADSecretKeyEncryptorFactory.java | 4 + .../bc/BcCFBSecretKeyEncryptorFactory.java | 5 + .../JcaAEADSecretKeyEncryptorFactory.java | 35 +++ .../JcaCFBSecretKeyEncryptorFactory.java | 52 ++++ .../api/test/BcOpenPGPV6KeyGeneratorTest.java | 86 ------- .../api/test/OpenPGPV6KeyGeneratorTest.java | 226 +++++++++++++++++ .../openpgp/test/RegressionTest.java | 4 +- 11 files changed, 609 insertions(+), 181 deletions(-) rename pg/src/main/java/org/bouncycastle/openpgp/api/{ => bc}/BcOpenPGPV6KeyGenerator.java (69%) create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java delete mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index f66c03cddf..c46f234ec4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -24,6 +24,7 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; +import org.bouncycastle.util.Arrays; import java.io.IOException; import java.util.ArrayList; @@ -128,32 +129,114 @@ public OpenPGPV6KeyGenerator( this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); } + public PGPSecretKeyRing classicKey(String userId, char[] passphrase) + throws PGPException + { + return withPrimaryKey() + .addUserId(userId) + .addSigningSubkey() + .addEncryptionSubkey() + .build(passphrase); + } + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences. + * @return sign-only OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing signOnlyKey() + throws PGPException + { + return signOnlyKey(null); + } + + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences whose subpackets can be + * modified by providing a {@link SignatureSubpacketsFunction}. + * @param passphrase passphrase to protect the key with + * @return sign-only OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing signOnlyKey( + char[] passphrase) + throws PGPException + { + return signOnlyKey(passphrase, null); + } + + public PGPSecretKeyRing signOnlyKey( + char[] passphrase, + SignatureSubpacketsFunction userSubpackets) + throws PGPException + { + PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime) + .generatePrimaryKey(); + PBESecretKeyEncryptor encryptor = impl.keyEncryptorBuilderProvider + .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); + return signOnlyKey(primaryKey, encryptor, userSubpackets); + } + + public PGPSecretKeyRing signOnlyKey( + PGPKeyPair primaryKey, + PBESecretKeyEncryptor keyEncryptor, + SignatureSubpacketsFunction userSubpackets) + throws PGPException + { + return primaryKeyWithDirectKeySig(primaryKey, + baseSubpackets -> + { + // remove unrelated subpackets not needed for sign-only keys + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); + + // replace key flags to add SIGN_DATA + baseSubpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + baseSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); + return baseSubpackets; + }, + userSubpackets, // apply user-provided subpacket changes + keyEncryptor) + .build(); + } + + public WithPrimaryKey withPrimaryKey() + throws PGPException + { + return withPrimaryKey(null); + } + + public WithPrimaryKey withPrimaryKey( + SignatureSubpacketsFunction directKeySubpackets) + throws PGPException + { + return withPrimaryKey( + PGPKeyPairGenerator::generatePrimaryKey, + directKeySubpackets); + } + public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction directKeySubpackets, - char[] passphrase) + SignatureSubpacketsFunction directKeySubpackets) throws PGPException { PGPKeyPair pkPair = keyGenCallback.generateFrom( impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); - return withPrimaryKey(pkPair, directKeySubpackets, passphrase); + return withPrimaryKey(pkPair, directKeySubpackets); } public WithPrimaryKey withPrimaryKey( PGPKeyPair keyPair, - SignatureSubpacketsFunction directKeySubpackets, - char[] passphrase) + SignatureSubpacketsFunction directKeySubpackets) throws PGPException { - if (passphrase == null) - { - return withPrimaryKey(keyPair, directKeySubpackets, (PBESecretKeyEncryptor) null); - } return withPrimaryKey( keyPair, directKeySubpackets, - impl.keyEncryptorBuilderProvider.build(passphrase, keyPair.getPublicKey().getPublicKeyPacket()) - ); + null); } public WithPrimaryKey withPrimaryKey( @@ -236,65 +319,6 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( return new WithPrimaryKey(impl, conf, primaryKey); } - /** - * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences. - * @return sign-only OpenPGP key - * @param passphrase passphrase to encrypt the primary key - * @throws PGPException - */ - public PGPSecretKeyRing signOnlyKey(char[] passphrase) - throws PGPException - { - return signOnlyKey(passphrase, null); - } - - /** - * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences whose subpackets can be - * modified by providing a {@link SignatureSubpacketsFunction}. - * @param passphrase passphrase - * @param userSubpackets callback to modify the hashed signature subpacket area of the direct-key signature. - * @return sign-only OpenPGP key - * @throws PGPException - */ - public PGPSecretKeyRing signOnlyKey( - char[] passphrase, - SignatureSubpacketsFunction userSubpackets) - throws PGPException - { - PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime).generateEd25519KeyPair(); - PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider - .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); - return signOnlyKey(primaryKey, keyEncryptor, userSubpackets); - } - - public PGPSecretKeyRing signOnlyKey( - PGPKeyPair primaryKey, - PBESecretKeyEncryptor keyEncryptor, - SignatureSubpacketsFunction userSubpackets) - throws PGPException - { - return primaryKeyWithDirectKeySig(primaryKey, - baseSubpackets -> - { - // remove unrelated subpackets not needed for sign-only keys - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); - - // replace key flags to add SIGN_DATA - baseSubpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); - baseSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); - return baseSubpackets; - }, - userSubpackets, // apply user-provided subpacket changes - keyEncryptor) - .build(); - } - /** * Intermediate builder class. * Constructs an OpenPGP key from a specified primary key. @@ -394,26 +418,25 @@ public WithPrimaryKey addUserId( /** * Add an encryption-capable X25519 subkey to the OpenPGP key. - * @param encryptor encryptor to encrypt the subkey. * @return builder * @throws PGPException */ - public WithPrimaryKey addEncryptionSubkey(PBESecretKeyEncryptor encryptor) + public WithPrimaryKey addEncryptionSubkey() throws PGPException { - return addEncryptionSubkey(PGPKeyPairGenerator::generateX25519KeyPair, encryptor); + + return addEncryptionSubkey(PGPKeyPairGenerator::generateEncryptionSubkey, null); } /** * Add an encryption-capable subkey to the OpenPGP key. * @param generatorCallback callback to specify the encryption key type. - * @param encryptor encryptor to encrypt the encryption subkey * @return builder * @throws PGPException */ public WithPrimaryKey addEncryptionSubkey( KeyPairGeneratorCallback generatorCallback, - PBESecretKeyEncryptor encryptor) + SignatureSubpacketsFunction bindingSubpacketsCallback) throws PGPException { PGPKeyPairGenerator generator = impl.kpGenProvider.get( @@ -422,7 +445,7 @@ public WithPrimaryKey addEncryptionSubkey( ); PGPKeyPair subkey = generatorCallback.generateFrom(generator); - return addEncryptionSubkey(subkey, null, encryptor); + return addEncryptionSubkey(subkey, bindingSubpacketsCallback, null); } /** @@ -468,21 +491,21 @@ public WithPrimaryKey addEncryptionSubkey( return this; } - public WithPrimaryKey addSigningSubkey(PBESecretKeyEncryptor keyEncryptor) + public WithPrimaryKey addSigningSubkey() throws PGPException { - return addSigningSubkey(PGPKeyPairGenerator::generateEd25519KeyPair, keyEncryptor); + return addSigningSubkey(PGPKeyPairGenerator::generateSigningSubkey); } - public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback, - PBESecretKeyEncryptor keyEncryptor) + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback) throws PGPException { + PGPKeyPair signingSubkey = generatorCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); return addSigningSubkey( - generatorCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)), + signingSubkey, null, null, - keyEncryptor); + null); } public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, @@ -548,7 +571,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, } /** - * Build the {@link PGPSecretKeyRing OpenPGP key}. + * Build the {@link PGPSecretKeyRing OpenPGP key}, allowing individual passphrases for the subkeys. * @return OpenPGP key * @throws PGPException */ @@ -574,6 +597,49 @@ public PGPSecretKeyRing build() key.encryptor); keys.add(subkey); } + + return new PGPSecretKeyRing(keys); + } + + /** + * Build the {@link PGPSecretKeyRing OpenPGP key} using a single passphrase used to protect all subkeys. + * The passphrase will override whichever key protectors were specified in previous builder steps. + * @return OpenPGP key + * @throws PGPException + */ + public PGPSecretKeyRing build(char[] passphrase) + throws PGPException + { + PBESecretKeyEncryptor primaryKeyEncryptor = impl.keyEncryptorBuilderProvider + .build(passphrase, primaryKey.pair.getPublicKey().getPublicKeyPacket()); + PGPSecretKey primarySecretKey = new PGPSecretKey( + primaryKey.pair.getPrivateKey(), + primaryKey.pair.getPublicKey(), + impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + true, + primaryKeyEncryptor); + List keys = new ArrayList<>(); + keys.add(primarySecretKey); + + for (Key key : subkeys) + { + PBESecretKeyEncryptor subkeyEncryptor = impl.keyEncryptorBuilderProvider + .build(passphrase, key.pair.getPublicKey().getPublicKeyPacket()); + PGPSecretKey subkey = new PGPSecretKey( + key.pair.getPrivateKey(), + key.pair.getPublicKey(), + impl.digestCalculatorProvider.get(HashAlgorithmTags.SHA1), + false, + subkeyEncryptor); + keys.add(subkey); + } + + if (passphrase != null) + { + Arrays.fill(passphrase, (char) 0); + passphrase = null; + } + return new PGPSecretKeyRing(keys); } } @@ -619,6 +685,11 @@ public Configuration(Date creationTime) private static class Key { private PGPKeyPair pair; + + // We introduce Optional here, because that way we can reflect 3 states: + // * The user explicitly wants to encrypt a (sub) key using a dedicated password -> Optional.some(encryptor) + // * The user explicitly wants to keep the key unencrypted -> Optional.empty() + // * The user wants to use one single password for all subkeys -> null private PBESecretKeyEncryptor encryptor; public Key(PGPKeyPair key, PBESecretKeyEncryptor encryptor) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java similarity index 69% rename from pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java rename to pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java index 91b53106a0..486f7c4dfe 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java @@ -1,6 +1,9 @@ -package org.bouncycastle.openpgp.api; +package org.bouncycastle.openpgp.api.bc; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcCFBSecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; @@ -19,7 +22,7 @@ public class BcOpenPGPV6KeyGenerator */ public BcOpenPGPV6KeyGenerator() { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM); + this(new Date()); } /** @@ -30,7 +33,7 @@ public BcOpenPGPV6KeyGenerator() */ public BcOpenPGPV6KeyGenerator(Date creationTime) { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime); + this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); } /** @@ -41,7 +44,7 @@ public BcOpenPGPV6KeyGenerator(Date creationTime) */ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) { - this(signatureHashAlgorithm, new Date()); + this(signatureHashAlgorithm, new Date(), true); } /** @@ -50,17 +53,25 @@ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm) * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures */ - public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime) + public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) { super( new BcPGPKeyPairGeneratorProvider(), new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), new BcPGPDigestCalculatorProvider(), - //* - new BcAEADSecretKeyEncryptorFactory(), - /*/ - new BcCFBSecretKeyEncryptorFactory(), - //*/ + keyEncryptorFactory(aeadProtection), creationTime); } + + private static PBESecretKeyEncryptorFactory keyEncryptorFactory(boolean aeadProtection) + { + if (aeadProtection) + { + return new BcAEADSecretKeyEncryptorFactory(); + } + else + { + return new BcCFBSecretKeyEncryptorFactory(); + } + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java new file mode 100644 index 0000000000..680473846f --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -0,0 +1,71 @@ +package org.bouncycastle.openpgp.api.jcajce; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaCFBSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilderProvider; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPairGeneratorProvider; + +import java.security.Provider; +import java.util.Date; + +public class JcaOpenPGPV6KeyGenerator + extends OpenPGPV6KeyGenerator +{ + + public JcaOpenPGPV6KeyGenerator(Provider provider) + throws PGPException + { + this(new Date(), provider); + } + + public JcaOpenPGPV6KeyGenerator(Date creationTime, Provider provider) + throws PGPException + { + this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, provider, true); + } + + public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Provider provider) + throws PGPException + { + this(signatureHashAlgorithm, new Date(), provider, true); + } + + /** + * Create a new OpenPGP key generator for v6 keys. + * + * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key + * @param creationTime creation time of the key and signatures + */ + public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, Provider provider, boolean aeadProtection) + throws PGPException + { + super( + new JcaPGPKeyPairGeneratorProvider() + .setProvider(provider), + new JcaPGPContentSignerBuilderProvider(signatureHashAlgorithm) + .setSecurityProvider(provider), + new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(provider) + .build(), + keyEncryptorFactory(provider, aeadProtection), + creationTime); + } + + private static PBESecretKeyEncryptorFactory keyEncryptorFactory(Provider provider, boolean aeadProtection) + throws PGPException + { + if (aeadProtection) + { + return new JcaAEADSecretKeyEncryptorFactory().setProvider(provider); + } + else + { + return new JcaCFBSecretKeyEncryptorFactory().setProvider(provider); + + } + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java index f0b02e3a98..8089d1e3b7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java @@ -21,6 +21,45 @@ public PGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) this.random = random; } + /** + * Generate a primary key. + * A primary key MUST use a signing-capable public key algorithm. + * + * @return primary key pair + * @throws PGPException + */ + public PGPKeyPair generatePrimaryKey() + throws PGPException + { + return generateEd25519KeyPair(); + } + + /** + * Generate an encryption subkey. + * An encryption subkey MUST use an encryption-capable public key algorithm. + * + * @return encryption subkey pair + * @throws PGPException + */ + public PGPKeyPair generateEncryptionSubkey() + throws PGPException + { + return generateX25519KeyPair(); + } + + /** + * Generate a signing subkey. + * A signing subkey MUST use a signing-capable public key algorithm. + * + * @return signing subkey pair + * @throws PGPException + */ + public PGPKeyPair generateSigningSubkey() + throws PGPException + { + return generateEd25519KeyPair(); + } + public PGPKeyPair generateRsaKeyPair(int bitStrength) throws PGPException { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java index 5b3df6c88f..ff9a2680ea 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java @@ -20,6 +20,10 @@ public class BcAEADSecretKeyEncryptorFactory { @Override public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) { + if (passphrase == null) + { + return null; + } return new BcAEADSecretKeyEncryptorBuilder( AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_256, diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java index 6979fedaa8..dc2dbb5440 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java @@ -22,6 +22,11 @@ public class BcCFBSecretKeyEncryptorFactory @Override public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) { + if (passphrase == null) + { + return null; + } + PGPDigestCalculator checksumCalc; try { diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java new file mode 100644 index 0000000000..02bafe954e --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java @@ -0,0 +1,35 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.bcpg.AEADAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.S2K; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; + +import java.security.Provider; + +public class JcaAEADSecretKeyEncryptorFactory + extends PBESecretKeyEncryptorFactory +{ + private JcaAEADSecretKeyEncryptorBuilder builder = new JcaAEADSecretKeyEncryptorBuilder( + AEADAlgorithmTags.OCB, + SymmetricKeyAlgorithmTags.AES_256, + S2K.Argon2Params.universallyRecommendedParameters()); + + public JcaAEADSecretKeyEncryptorFactory setProvider(Provider provider) + { + builder.setProvider(provider); + return this; + } + + @Override + public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) + { + if (passphrase == null) + { + return null; + } + return builder.build(passphrase, pubKeyPacket); + } +} diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java new file mode 100644 index 0000000000..d2bbd19a1c --- /dev/null +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java @@ -0,0 +1,52 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; + +import java.security.Provider; + +public class JcaCFBSecretKeyEncryptorFactory + extends PBESecretKeyEncryptorFactory +{ + private JcaPGPDigestCalculatorProviderBuilder digestCalcProviderBuilder = + new JcaPGPDigestCalculatorProviderBuilder(); + private JcePBESecretKeyEncryptorBuilder encBuilder; + + public JcaCFBSecretKeyEncryptorFactory() + throws PGPException + { + encBuilder = builder(); + } + + public JcaCFBSecretKeyEncryptorFactory setProvider(Provider provider) + throws PGPException + { + digestCalcProviderBuilder.setProvider(provider); + encBuilder = builder(); + return this; + } + + private JcePBESecretKeyEncryptorBuilder builder() + throws PGPException + { + return new JcePBESecretKeyEncryptorBuilder( + SymmetricKeyAlgorithmTags.AES_256, + digestCalcProviderBuilder.build().get(HashAlgorithmTags.SHA1), + 0x60 + ); + } + + @Override + public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) + { + if (passphrase == null) + { + return null; + } + return encBuilder.build(passphrase); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java deleted file mode 100644 index 6b5ddf55a8..0000000000 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/BcOpenPGPV6KeyGeneratorTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.bouncycastle.openpgp.api.test; - -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.bcpg.HashAlgorithmTags; -import org.bouncycastle.bcpg.PacketFormat; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.api.BcOpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; -import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; -import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Date; -import java.util.Iterator; - -public class BcOpenPGPV6KeyGeneratorTest - extends AbstractPgpKeyPairTest -{ - @Override - public String getName() - { - return "OpenPGPV6KeyGeneratorTest"; - } - - @Override - public void performTest() - throws Exception - { - testGenerateMinimalKey(); - } - - private void testGenerateMinimalKey() - throws PGPException, IOException { - Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator gen = new BcOpenPGPV6KeyGenerator( - HashAlgorithmTags.SHA3_512, - creationTime - ); - PGPSecretKeyRing secretKeys = gen.withPrimaryKey( - PGPKeyPairGenerator::generateEd25519KeyPair, - subpackets -> - { - subpackets.addNotationData(false, true, "foo@bouncycastle.org", "bar"); - return subpackets; - }, - "hello".toCharArray()) - .addUserId("Alice ") - .addEncryptionSubkey(null) - .addSigningSubkey(null) - .build(); - - // Test creation time - for (PGPPublicKey key : secretKeys.toCertificate()) - { - isEquals(creationTime, key.getCreationTime()); - for (Iterator it = key.getSignatures(); it.hasNext(); ) { - PGPSignature sig = it.next(); - isEquals(creationTime, sig.getCreationTime()); - } - } - - PGPPublicKey primaryKey = secretKeys.getPublicKey(); - // Test UIDs - Iterator uids = primaryKey.getUserIDs(); - isEquals("Alice ", uids.next()); - isFalse(uids.hasNext()); - - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ArmoredOutputStream aOut = new ArmoredOutputStream(bOut); - BCPGOutputStream pOut = new BCPGOutputStream(aOut, PacketFormat.CURRENT); - secretKeys.encode(pOut); - pOut.close(); - aOut.close(); - System.out.println(bOut); - } - - public static void main(String[] args) - { - runTest(new BcOpenPGPV6KeyGeneratorTest()); - } -} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java new file mode 100644 index 0000000000..8b40cd7d35 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -0,0 +1,226 @@ +package org.bouncycastle.openpgp.api.test; + +import org.bouncycastle.bcpg.PublicKeyPacket; +import org.bouncycastle.bcpg.PublicKeyUtils; +import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; + +public class OpenPGPV6KeyGeneratorTest + extends AbstractPgpKeyPairTest +{ + @Override + public String getName() + { + return "OpenPGPV6KeyGeneratorTest"; + } + + @Override + public void performTest() + throws Exception + { + // Run tests using BC implementation + performTests(new ImplementationProvider() + { + @Override + public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + { + return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); + } + }); + + // Run tests using Jca implementation + performTests(new ImplementationProvider() + { + @Override + public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + throws PGPException + { + return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, new BouncyCastleProvider(), aeadProtection); + } + }); + } + + private void performTests(ImplementationProvider implementationProvider) + throws PGPException, IOException + { + testGenerateSignOnlyKeyBaseCase(implementationProvider); + testGenerateAEADProtectedSignOnlyKey(implementationProvider); + testGenerateCFBProtectedSignOnlyKey(implementationProvider); + + testGenerateClassicKeyBaseCase(implementationProvider); + testGenerateProtectedTypicalKey(implementationProvider); + } + + private void testGenerateSignOnlyKeyBaseCase(ImplementationProvider implementationProvider) + throws PGPException + { + OpenPGPV6KeyGenerator generator = implementationProvider.get(); + PGPSecretKeyRing secretKeys = generator.signOnlyKey(); + + Iterator it = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = it.next(); + isFalse("sign-only key MUST consists of only a single key", it.hasNext()); + PGPSignature directKeySignature = primaryKey.getPublicKey().getKeySignatures().next(); + isNotNull("Key MUST have direct-key signature", directKeySignature); + isEquals("Sign-Only primary key MUST carry CS flags", + KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, directKeySignature.getHashedSubPackets().getKeyFlags()); + + isEquals("Key version mismatch", 6, primaryKey.getPublicKey().getVersion()); + isEquals("Key MUST be unprotected", SecretKeyPacket.USAGE_NONE, primaryKey.getS2KUsage()); + } + + private void testGenerateAEADProtectedSignOnlyKey(ImplementationProvider implementationProvider) + throws PGPException + { + OpenPGPV6KeyGenerator generator = implementationProvider.get(true); + PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); + + Iterator it = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = it.next(); + isFalse("sign-only key MUST consists of only a single key", it.hasNext()); + + isEquals("Key MUST be AEAD-protected", SecretKeyPacket.USAGE_AEAD, primaryKey.getS2KUsage()); + isNotNull("Secret key MUST be retrievable using the proper passphrase", + primaryKey.extractKeyPair( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build("passphrase".toCharArray()))); + } + + private void testGenerateCFBProtectedSignOnlyKey(ImplementationProvider implementationProvider) + throws PGPException + { + OpenPGPV6KeyGenerator generator = implementationProvider.get(false); + PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); + + Iterator it = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = it.next(); + isFalse("sign-only key MUST consists of only a single key", it.hasNext()); + + isEquals("Key MUST be CFB-protected", SecretKeyPacket.USAGE_SHA1, primaryKey.getS2KUsage()); + isNotNull("Secret key MUST be retrievable using the proper passphrase", + primaryKey.extractKeyPair( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build("passphrase".toCharArray()))); + } + + private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator generator = provider.get(creationTime); + PGPSecretKeyRing secretKeys = generator + .classicKey("Alice ", null); + + Iterator keys = secretKeys.getSecretKeys(); + PGPSecretKey primaryKey = keys.next(); + isEquals("Primary key version mismatch", PublicKeyPacket.VERSION_6, + primaryKey.getPublicKey().getVersion()); + isEquals(creationTime, primaryKey.getPublicKey().getCreationTime()); + isTrue("Primary key uses signing-capable algorithm", + PublicKeyUtils.isSigningAlgorithm(primaryKey.getPublicKey().getAlgorithm())); + // Test UIDs + Iterator uids = primaryKey.getUserIDs(); + isEquals("Alice ", uids.next()); + isFalse(uids.hasNext()); + + // Test signing subkey + PGPSecretKey signingSubkey = keys.next(); + isEquals("Signing key version mismatch", PublicKeyPacket.VERSION_6, + signingSubkey.getPublicKey().getVersion()); + isTrue("Signing subkey uses signing-capable algorithm", + PublicKeyUtils.isSigningAlgorithm(signingSubkey.getPublicKey().getAlgorithm())); + isEquals(creationTime, signingSubkey.getPublicKey().getCreationTime()); + + // Test encryption subkey + PGPSecretKey encryptionSubkey = keys.next(); + isEquals("Encryption key version mismatch", PublicKeyPacket.VERSION_6, + encryptionSubkey.getPublicKey().getVersion()); + isTrue("Encryption subkey uses encryption-capable algorithm", + PublicKeyUtils.isEncryptionAlgorithm(encryptionSubkey.getPublicKey().getAlgorithm())); + isEquals(creationTime, encryptionSubkey.getPublicKey().getCreationTime()); + + // Test has no additional keys + isFalse(keys.hasNext()); + + // Test all keys are unprotected + for (PGPSecretKey key : secretKeys) + { + isEquals("(Sub-)keys MUST be unprotected", SecretKeyPacket.USAGE_NONE, key.getS2KUsage()); + } + } + + private void testGenerateProtectedTypicalKey(ImplementationProvider provider) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator generator = provider.get(creationTime); + PGPSecretKeyRing secretKeys = generator + .classicKey("Alice ", "passphrase".toCharArray()); + + // Test creation time + for (PGPPublicKey key : secretKeys.toCertificate()) + { + isEquals(creationTime, key.getCreationTime()); + for (Iterator it = key.getSignatures(); it.hasNext(); ) { + PGPSignature sig = it.next(); + isEquals(creationTime, sig.getCreationTime()); + } + } + + PGPPublicKey primaryKey = secretKeys.getPublicKey(); + // Test UIDs + Iterator uids = primaryKey.getUserIDs(); + isEquals("Alice ", uids.next()); + isFalse(uids.hasNext()); + + for (PGPSecretKey key : secretKeys) + { + isEquals("(Sub-)keys MUST be protected", SecretKeyPacket.USAGE_AEAD, key.getS2KUsage()); + } + } + + private abstract static class ImplementationProvider + { + public OpenPGPV6KeyGenerator get() + throws PGPException + { + return get(new Date()); + } + + public OpenPGPV6KeyGenerator get(Date creationTime) + throws PGPException + { + return get(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); + } + + public OpenPGPV6KeyGenerator get(boolean aeadProtection) + throws PGPException + { + return get(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, new Date(), aeadProtection); + } + + public abstract OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + throws PGPException; + } + + public static void main(String[] args) + { + runTest(new OpenPGPV6KeyGeneratorTest()); + } +} diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index 2bf6629e13..c4be38d411 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -3,7 +3,7 @@ import java.security.Security; import org.bouncycastle.bcpg.test.SignatureSubpacketsTest; -import org.bouncycastle.openpgp.api.test.BcOpenPGPV6KeyGeneratorTest; +import org.bouncycastle.openpgp.api.test.OpenPGPV6KeyGeneratorTest; import org.bouncycastle.util.test.SimpleTest; import org.bouncycastle.util.test.Test; @@ -88,7 +88,7 @@ public class RegressionTest new PGPv5MessageDecryptionTest(), new PGPv6SignatureTest(), new PGPKeyPairGeneratorTest(), - new BcOpenPGPV6KeyGeneratorTest() + new OpenPGPV6KeyGeneratorTest() }; public static void main(String[] args) From 842611703689700908cd5f0d1a090a2d425b6c9e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 15:52:52 +0200 Subject: [PATCH 26/53] Checkstyle --- .../openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java | 3 ++- .../openpgp/api/test/OpenPGPV6KeyGeneratorTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java index ff9a2680ea..013752e331 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java @@ -19,7 +19,8 @@ public class BcAEADSecretKeyEncryptorFactory extends PBESecretKeyEncryptorFactory { @Override - public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) { + public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) + { if (passphrase == null) { return null; diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 8b40cd7d35..b3f3a7875d 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -177,7 +177,8 @@ private void testGenerateProtectedTypicalKey(ImplementationProvider provider) for (PGPPublicKey key : secretKeys.toCertificate()) { isEquals(creationTime, key.getCreationTime()); - for (Iterator it = key.getSignatures(); it.hasNext(); ) { + for (Iterator it = key.getSignatures(); it.hasNext(); ) + { PGPSignature sig = it.next(); isEquals(creationTime, sig.getCreationTime()); } From 5890b45df8c51a52e30f878bd8fffca5b52c8e1c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 17:56:23 +0200 Subject: [PATCH 27/53] Permutate provider argument in JcaOpenPGPV6KeyGenerator --- .../api/jcajce/JcaOpenPGPV6KeyGenerator.java | 6 ++-- .../api/test/OpenPGPV6KeyGeneratorTest.java | 28 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java index 680473846f..282f24fcd9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -25,13 +25,13 @@ public JcaOpenPGPV6KeyGenerator(Provider provider) public JcaOpenPGPV6KeyGenerator(Date creationTime, Provider provider) throws PGPException { - this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, provider, true); + this(DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true, provider); } public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Provider provider) throws PGPException { - this(signatureHashAlgorithm, new Date(), provider, true); + this(signatureHashAlgorithm, new Date(), true, provider); } /** @@ -40,7 +40,7 @@ public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Provider provider) * @param signatureHashAlgorithm ID of the hash algorithm used for signatures on the key * @param creationTime creation time of the key and signatures */ - public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, Provider provider, boolean aeadProtection) + public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection, Provider provider) throws PGPException { super( diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index b3f3a7875d..6ff6d01cc7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -34,24 +34,29 @@ public String getName() public void performTest() throws Exception { - // Run tests using BC implementation + // Run tests using the BC implementation performTests(new ImplementationProvider() { @Override - public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) { return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); } }); - // Run tests using Jca implementation + // Run tests using the JCA/JCE implementation performTests(new ImplementationProvider() { @Override - public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) throws PGPException { - return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, new BouncyCastleProvider(), aeadProtection); + return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection, + new BouncyCastleProvider()); } }); } @@ -134,6 +139,10 @@ private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) isEquals(creationTime, primaryKey.getPublicKey().getCreationTime()); isTrue("Primary key uses signing-capable algorithm", PublicKeyUtils.isSigningAlgorithm(primaryKey.getPublicKey().getAlgorithm())); + PGPSignature directKeySig = primaryKey.getPublicKey().getKeySignatures().next(); + isEquals("Primary key of a classic key MUST carry C key flag.", + KeyFlags.CERTIFY_OTHER, directKeySig.getHashedSubPackets().getKeyFlags()); + // Test UIDs Iterator uids = primaryKey.getUserIDs(); isEquals("Alice ", uids.next()); @@ -146,6 +155,11 @@ private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) isTrue("Signing subkey uses signing-capable algorithm", PublicKeyUtils.isSigningAlgorithm(signingSubkey.getPublicKey().getAlgorithm())); isEquals(creationTime, signingSubkey.getPublicKey().getCreationTime()); + PGPSignature signingKeyBinding = signingSubkey.getPublicKey().getKeySignatures().next(); + isEquals("Signing subkey MUST carry S key flag.", + KeyFlags.SIGN_DATA, signingKeyBinding.getHashedSubPackets().getKeyFlags()); + isNotNull("Signing subkey binding MUST carry primary key binding sig", + signingKeyBinding.getHashedSubPackets().getEmbeddedSignatures().get(0)); // Test encryption subkey PGPSecretKey encryptionSubkey = keys.next(); @@ -154,6 +168,10 @@ private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) isTrue("Encryption subkey uses encryption-capable algorithm", PublicKeyUtils.isEncryptionAlgorithm(encryptionSubkey.getPublicKey().getAlgorithm())); isEquals(creationTime, encryptionSubkey.getPublicKey().getCreationTime()); + PGPSignature encryptionKeyBinding = encryptionSubkey.getPublicKey().getKeySignatures().next(); + isEquals("Encryption key MUST carry encryption flags", + KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, + encryptionKeyBinding.getHashedSubPackets().getKeyFlags()); // Test has no additional keys isFalse(keys.hasNext()); From eab51e5615c76df8d10fecfc65d680c11d70cc6b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 18:40:44 +0200 Subject: [PATCH 28/53] Javadoc and more testing --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 143 ++++++++++++++++-- .../api/test/OpenPGPV6KeyGeneratorTest.java | 44 ++++++ 2 files changed, 174 insertions(+), 13 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index c46f234ec4..a8de7ae792 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -223,9 +223,7 @@ public WithPrimaryKey withPrimaryKey( SignatureSubpacketsFunction directKeySubpackets) throws PGPException { - PGPKeyPair pkPair = keyGenCallback.generateFrom( - impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); - return withPrimaryKey(pkPair, directKeySubpackets); + return withPrimaryKey(keyGenCallback, directKeySubpackets, null); } public WithPrimaryKey withPrimaryKey( @@ -239,6 +237,19 @@ public WithPrimaryKey withPrimaryKey( null); } + public WithPrimaryKey withPrimaryKey( + KeyPairGeneratorCallback keyGenCallback, + SignatureSubpacketsFunction directKeySubpackets, + char[] passphrase) + throws PGPException + { + PGPKeyPair pkPair = keyGenCallback.generateFrom( + impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider + .build(passphrase, pkPair.getPublicKey().getPublicKeyPacket()); + return withPrimaryKey(pkPair, directKeySubpackets, keyEncryptor); + } + public WithPrimaryKey withPrimaryKey( PGPKeyPair keyPair, SignatureSubpacketsFunction directKeySubpackets, @@ -417,7 +428,8 @@ public WithPrimaryKey addUserId( } /** - * Add an encryption-capable X25519 subkey to the OpenPGP key. + * Add an encryption-capable subkey to the OpenPGP key. + * See {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the key type. * @return builder * @throws PGPException */ @@ -425,12 +437,29 @@ public WithPrimaryKey addEncryptionSubkey() throws PGPException { - return addEncryptionSubkey(PGPKeyPairGenerator::generateEncryptionSubkey, null); + return addEncryptionSubkey(PGPKeyPairGenerator::generateEncryptionSubkey); + } + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * The type of the subkey can be decided by implementing the {@link KeyPairGeneratorCallback}. + * + * @param keyGenCallback callback to decide the encryption subkey type + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback) + throws PGPException + { + return addEncryptionSubkey(keyGenCallback, (char[]) null); } /** * Add an encryption-capable subkey to the OpenPGP key. + * The type of the subkey can be decided by implementing the {@link KeyPairGeneratorCallback}. + * The binding signature can be modified by implementing the {@link SignatureSubpacketsFunction}. * @param generatorCallback callback to specify the encryption key type. + * @param bindingSubpacketsCallback nullable callback to modify the binding signature subpackets * @return builder * @throws PGPException */ @@ -450,9 +479,78 @@ public WithPrimaryKey addEncryptionSubkey( /** * Add an encryption-capable subkey to the OpenPGP key. + * The subkey will be protected using the provided subkey passphrase. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * See {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the key type. + * + * @param passphrase nullable subkey passphrase + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey(char[] passphrase) + throws PGPException + { + return addEncryptionSubkey(PGPKeyPairGenerator::generateEncryptionSubkey, passphrase); + } + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. + * The subkey will be protected using the provided subkey passphrase. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * @param keyGenCallback callback to specify the key type + * @param passphrase nullable passphrase for the encryption subkey + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, + char[] passphrase) + throws PGPException + { + return addEncryptionSubkey(keyGenCallback, null, passphrase); + } + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. + * The binding signatures subpackets can be modified by overriding the {@link SignatureSubpacketsFunction}. + * The subkey will be protected using the provided subkey passphrase. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * @param keyGenCallback callback to specify the key type + * @param bindingSignatureCallback nullable callback to modify the binding signature subpackets + * @param passphrase nullable passphrase for the encryption subkey + * @return builder + * @throws PGPException + */ + public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, + SignatureSubpacketsFunction bindingSignatureCallback, + char[] passphrase) + throws PGPException + { + PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); + return addEncryptionSubkey(subkey, bindingSignatureCallback, keyEncryptor); + } + + + /** + * Add an encryption-capable subkey to the OpenPGP key. + * IMPORTANT: The custom key encryptor will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific encryptor is overwritten with an encryptor + * built from the argument passed into {@link #build(char[])}. * @param encryptionSubkey encryption subkey - * @param bindingSubpacketsCallback callback to modify the subkey binding signature subpackets - * @param encryptor encryptor to encrypt the encryption subkey + * @param bindingSubpacketsCallback nullable callback to modify the subkey binding signature subpackets + * @param encryptor nullable encryptor to encrypt the encryption subkey * @return builder * @throws PGPException */ @@ -500,12 +598,31 @@ public WithPrimaryKey addSigningSubkey() public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback) throws PGPException { - PGPKeyPair signingSubkey = generatorCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); - return addSigningSubkey( - signingSubkey, - null, - null, - null); + return addSigningSubkey(generatorCallback, null); + } + + public WithPrimaryKey addSigningSubkey(char[] passphrase) + throws PGPException + { + return addSigningSubkey(PGPKeyPairGenerator::generateSigningSubkey, passphrase); + } + + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, + char[] passphrase) + throws PGPException + { + return addSigningSubkey(keyGenCallback, null, null, passphrase); + } + + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, + SignatureSubpacketsFunction bindingSignatureCallback, + SignatureSubpacketsFunction backSignatureCallback, + char[] passphrase) + throws PGPException + { + PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); + return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); } public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 6ff6d01cc7..6f1b7eee0f 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -3,6 +3,8 @@ import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.SignatureSubpacketTags; +import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; @@ -70,6 +72,8 @@ private void performTests(ImplementationProvider implementationProvider) testGenerateClassicKeyBaseCase(implementationProvider); testGenerateProtectedTypicalKey(implementationProvider); + + testGenerateCustomKey(implementationProvider); } private void testGenerateSignOnlyKeyBaseCase(ImplementationProvider implementationProvider) @@ -214,6 +218,46 @@ private void testGenerateProtectedTypicalKey(ImplementationProvider provider) } } + private void testGenerateCustomKey(ImplementationProvider implementationProvider) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPV6KeyGenerator generator = implementationProvider.get(creationTime); + + PGPSecretKeyRing secretKey = generator + .withPrimaryKey( + keyGen -> keyGen.generateRsaKeyPair(4096), + subpackets -> + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER); + + subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); + subpackets.setFeature(false, Features.FEATURE_SEIPD_V2); + + subpackets.addNotationData(false, true, + "notation@example.com", "CYBER"); + + subpackets.setPreferredKeyServer(false, "https://example.com/openpgp/cert.asc"); + return subpackets; + }, + "primary-key-passphrase".toCharArray()) + .addUserId("Alice ", PGPSignature.DEFAULT_CERTIFICATION, null) + .addSigningSubkey( + keyGen -> keyGen.generateEd448KeyPair(), + bindingSubpackets -> + { + bindingSubpackets.addNotationData(false, true, + "notation@example.com", "ZAUBER"); + return bindingSubpackets; + }, + null, + "signing-key-passphrase".toCharArray()) + .addEncryptionSubkey(keyGenCallback -> keyGenCallback.generateX448KeyPair(), + "encryption-key-passphrase".toCharArray()) + .build(); + } + private abstract static class ImplementationProvider { public OpenPGPV6KeyGenerator get() From 027a3e60e46eb1aed5c78243fe6b9b36e09cfaa5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Oct 2024 19:23:33 +0200 Subject: [PATCH 29/53] Add more javadoc to OpenPGPV6KeyGenerator --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 257 +++++++++++++++--- 1 file changed, 224 insertions(+), 33 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index a8de7ae792..355e01d349 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -129,6 +129,19 @@ public OpenPGPV6KeyGenerator( this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); } + /** + * Generate an OpenPGP key consisting of a certify-only primary key, + * a dedicated signing-subkey and dedicated encryption-subkey. + * The key will carry the provided user-id and be protected using the provided passphrase. + * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type, + * {@link PGPKeyPairGenerator#generateSigningSubkey()} for the signing-subkey type and + * {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the encryption-subkey key type. + * + * @param userId user id + * @param passphrase nullable passphrase. + * @return OpenPGP key + * @throws PGPException if the key cannot be generated + */ public PGPSecretKeyRing classicKey(String userId, char[] passphrase) throws PGPException { @@ -142,8 +155,9 @@ public PGPSecretKeyRing classicKey(String userId, char[] passphrase) * Generate a sign-only OpenPGP key. * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. * It carries a single direct-key signature with signing-related preferences. - * @return sign-only OpenPGP key - * @throws PGPException + * + * @return sign-only (+certify) OpenPGP key + * @throws PGPException if the key cannot be generated */ public PGPSecretKeyRing signOnlyKey() throws PGPException @@ -153,20 +167,30 @@ public PGPSecretKeyRing signOnlyKey() /** * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences whose subpackets can be - * modified by providing a {@link SignatureSubpacketsFunction}. - * @param passphrase passphrase to protect the key with - * @return sign-only OpenPGP key - * @throws PGPException + * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. + * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the key type. + * + * @param passphrase nullable passphrase to protect the key with + * @return sign-only (+certify) OpenPGP key + * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing signOnlyKey( - char[] passphrase) + public PGPSecretKeyRing signOnlyKey(char[] passphrase) throws PGPException { return signOnlyKey(passphrase, null); } + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences whose subpackets can be + * modified by providing a {@link SignatureSubpacketsFunction}. + * + * @param passphrase nullable passphrase to protect the key with + * @param userSubpackets callback to modify the direct-key signature subpackets with + * @return sign-only (+certify) OpenPGP key + * @throws PGPException if the key cannot be generated + */ public PGPSecretKeyRing signOnlyKey( char[] passphrase, SignatureSubpacketsFunction userSubpackets) @@ -179,6 +203,18 @@ public PGPSecretKeyRing signOnlyKey( return signOnlyKey(primaryKey, encryptor, userSubpackets); } + /** + * Generate a sign-only OpenPGP key. + * The key consists of a single, user-id-less primary key, which is capable of signing and certifying. + * It carries a single direct-key signature with signing-related preferences whose subpackets can be + * modified by providing a {@link SignatureSubpacketsFunction}. + * + * @param primaryKey signing-capable primary key + * @param keyEncryptor nullable encryptor to protect the primary key with + * @param userSubpackets callback to modify the direct-key signature subpackets with + * @return sign-only (+certify) OpenPGP key + * @throws PGPException if the key cannot be generated + */ public PGPSecretKeyRing signOnlyKey( PGPKeyPair primaryKey, PBESecretKeyEncryptor keyEncryptor, @@ -203,12 +239,29 @@ public PGPSecretKeyRing signOnlyKey( .build(); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type + * + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey() throws PGPException { return withPrimaryKey(null); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * + * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( SignatureSubpacketsFunction directKeySubpackets) throws PGPException @@ -218,6 +271,17 @@ public WithPrimaryKey withPrimaryKey( directKeySubpackets); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * + * @param keyGenCallback callback to specify the primary key type + * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback, SignatureSubpacketsFunction directKeySubpackets) @@ -226,6 +290,16 @@ public WithPrimaryKey withPrimaryKey( return withPrimaryKey(keyGenCallback, directKeySubpackets, null); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * + * @param keyPair primary key + * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( PGPKeyPair keyPair, SignatureSubpacketsFunction directKeySubpackets) @@ -237,6 +311,22 @@ public WithPrimaryKey withPrimaryKey( null); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * IMPORTANT: The custom primary key passphrase will only be used, if in the final step the key is retrieved + * using {@link WithPrimaryKey#build()}. + * If instead {@link WithPrimaryKey#build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link WithPrimaryKey#build(char[])}. + * + * @param keyGenCallback callback to specify the primary key type + * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets + * @param passphrase nullable passphrase to protect the primary key with + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( KeyPairGeneratorCallback keyGenCallback, SignatureSubpacketsFunction directKeySubpackets, @@ -250,6 +340,22 @@ public WithPrimaryKey withPrimaryKey( return withPrimaryKey(pkPair, directKeySubpackets, keyEncryptor); } + /** + * Generate an OpenPGP key with a certification-capable primary key. + * The {@link KeyPairGeneratorCallback} can be used to specify the primary key type. + * The key will carry a direct-key signature, whose subpackets can be modified by overriding the + * given {@link SignatureSubpacketsFunction}. + * IMPORTANT: The custom keyEncryptor will only be used, if in the final step the key is retrieved + * using {@link WithPrimaryKey#build()}. + * If instead {@link WithPrimaryKey#build(char[])} is used, the key-specific encryptor is overwritten with + * an encryptor built from the argument passed into {@link WithPrimaryKey#build(char[])}. + * + * @param keyPair primary key + * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets + * @param keyEncryptor nullable encryptor to protect the primary key with + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey withPrimaryKey( PGPKeyPair keyPair, SignatureSubpacketsFunction directKeySubpackets, @@ -287,7 +393,7 @@ public WithPrimaryKey withPrimaryKey( * @param customSubpackets user-provided signature subpackets callback * @param encryptor key encryptor * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ private WithPrimaryKey primaryKeyWithDirectKeySig( PGPKeyPair primaryKeyPair, @@ -340,10 +446,11 @@ public static class WithPrimaryKey private final Implementation impl; private final Configuration conf; private Key primaryKey; - private List subkeys = new ArrayList(); + private final List subkeys = new ArrayList(); /** * Builder. + * * @param implementation cryptographic implementation * @param configuration key configuration * @param primaryKey specified primary key @@ -357,9 +464,10 @@ private WithPrimaryKey(Implementation implementation, Configuration configuratio /** * Attach a User-ID with a positive certification to the key. + * * @param userId user-id * @return builder - * @throws PGPException + * @throws PGPException if the user-id cannot be added */ public WithPrimaryKey addUserId(String userId) throws PGPException @@ -374,7 +482,7 @@ public WithPrimaryKey addUserId(String userId) * @param userId user-id * @param userIdSubpackets callback to modify the certification subpackets * @return builder - * @throws PGPException + * @throws PGPException if the user-id cannot be added */ public WithPrimaryKey addUserId( String userId, @@ -392,7 +500,7 @@ public WithPrimaryKey addUserId( * @param certificationType signature type * @param userIdSubpackets callback to modify the certification subpackets * @return builder - * @throws PGPException + * @throws PGPException if the user-id cannot be added */ public WithPrimaryKey addUserId( String userId, @@ -400,6 +508,11 @@ public WithPrimaryKey addUserId( SignatureSubpacketsFunction userIdSubpackets) throws PGPException { + if (userId == null || userId.trim().isEmpty()) + { + throw new IllegalArgumentException("User-ID cannot be null or empty."); + } + if (!PGPSignature.isCertification(certificationType)) { throw new IllegalArgumentException("Signature type MUST be a certification type (0x10 - 0x13)"); @@ -430,8 +543,9 @@ public WithPrimaryKey addUserId( /** * Add an encryption-capable subkey to the OpenPGP key. * See {@link PGPKeyPairGenerator#generateEncryptionSubkey()} for the key type. + * * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey() throws PGPException @@ -446,7 +560,7 @@ public WithPrimaryKey addEncryptionSubkey() * * @param keyGenCallback callback to decide the encryption subkey type * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback) throws PGPException @@ -458,10 +572,11 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * Add an encryption-capable subkey to the OpenPGP key. * The type of the subkey can be decided by implementing the {@link KeyPairGeneratorCallback}. * The binding signature can be modified by implementing the {@link SignatureSubpacketsFunction}. + * * @param generatorCallback callback to specify the encryption key type. * @param bindingSubpacketsCallback nullable callback to modify the binding signature subpackets * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey( KeyPairGeneratorCallback generatorCallback, @@ -488,7 +603,7 @@ public WithPrimaryKey addEncryptionSubkey( * * @param passphrase nullable subkey passphrase * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(char[] passphrase) throws PGPException @@ -504,10 +619,11 @@ public WithPrimaryKey addEncryptionSubkey(char[] passphrase) * using {@link #build()}. * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument * passed into {@link #build(char[])}. + * * @param keyGenCallback callback to specify the key type * @param passphrase nullable passphrase for the encryption subkey * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, char[] passphrase) @@ -525,11 +641,12 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * using {@link #build()}. * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument * passed into {@link #build(char[])}. + * * @param keyGenCallback callback to specify the key type * @param bindingSignatureCallback nullable callback to modify the binding signature subpackets * @param passphrase nullable passphrase for the encryption subkey * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, SignatureSubpacketsFunction bindingSignatureCallback, @@ -548,11 +665,12 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * using {@link #build()}. * If instead {@link #build(char[])} is used, the key-specific encryptor is overwritten with an encryptor * built from the argument passed into {@link #build(char[])}. + * * @param encryptionSubkey encryption subkey * @param bindingSubpacketsCallback nullable callback to modify the subkey binding signature subpackets * @param encryptor nullable encryptor to encrypt the encryption subkey * @return builder - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey( PGPKeyPair encryptionSubkey, @@ -589,24 +707,64 @@ public WithPrimaryKey addEncryptionSubkey( return this; } + /** + * Add a signing-capable subkey to the OpenPGP key. + * See {@link PGPKeyPairGenerator#generateSigningSubkey()} for the key type. + * + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey() throws PGPException { return addSigningSubkey(PGPKeyPairGenerator::generateSigningSubkey); } + /** + * Add a signing-capable subkey to the OpenPGP key. + * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. + * + * @param generatorCallback callback to specify the signing-subkey type + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback) throws PGPException { return addSigningSubkey(generatorCallback, null); } + /** + * Add a signing-capable subkey to the OpenPGP key. + * See {@link PGPKeyPairGenerator#generateSigningSubkey()} for the key type. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * + * @param passphrase nullable passphrase + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey(char[] passphrase) throws PGPException { return addSigningSubkey(PGPKeyPairGenerator::generateSigningSubkey, passphrase); } + /** + * Add a signing-capable subkey to the OpenPGP key. + * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * + * @param keyGenCallback callback to specify the signing-key type + * @param passphrase nullable passphrase + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, char[] passphrase) throws PGPException @@ -614,6 +772,23 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, return addSigningSubkey(keyGenCallback, null, null, passphrase); } + /** + * Add a signing-capable subkey to the OpenPGP key. + * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * The contents of the binding signature(s) can be modified by overriding the respective + * {@link SignatureSubpacketsFunction} instances. + * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument + * passed into {@link #build(char[])}. + * + * @param keyGenCallback callback to specify the signing-key type + * @param bindingSignatureCallback callback to modify the contents of the signing subkey binding signature + * @param backSignatureCallback callback to modify the contents of the embedded primary key binding signature + * @param passphrase nullable passphrase + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, SignatureSubpacketsFunction bindingSignatureCallback, SignatureSubpacketsFunction backSignatureCallback, @@ -625,6 +800,23 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); } + /** + * Add a signing-capable subkey to the OpenPGP key. + * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * The contents of the binding signature(s) can be modified by overriding the respective + * {@link SignatureSubpacketsFunction} instances. + * IMPORTANT: The custom key encryptor will only be used, if in the final step the key is retrieved + * using {@link #build()}. + * If instead {@link #build(char[])} is used, the key-specific encryptor is overwritten with an encryptor + * built from the argument passed into {@link #build(char[])}. + * + * @param signingKey signing subkey + * @param bindingSignatureCallback callback to modify the contents of the signing subkey binding signature + * @param backSignatureCallback callback to modify the contents of the embedded primary key binding signature + * @param keyEncryptor nullable encryptor to protect the signing subkey + * @return builder + * @throws PGPException if the key cannot be generated + */ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, SignatureSubpacketsFunction bindingSignatureCallback, SignatureSubpacketsFunction backSignatureCallback, @@ -647,7 +839,8 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); bindingSigSubpackets.setSignatureCreationTime(conf.creationTime); - bindingSigSubpackets.setKeyFlags(true, KeyFlags.SIGN_DATA); + + bindingSigSubpackets = SIGNING_SUBKEY_SUBPACKETS.apply(bindingSigSubpackets); PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( impl.contentSignerBuilderProvider.get(signingKey.getPublicKey()), @@ -689,8 +882,9 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, /** * Build the {@link PGPSecretKeyRing OpenPGP key}, allowing individual passphrases for the subkeys. + * * @return OpenPGP key - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public PGPSecretKeyRing build() throws PGPException @@ -721,8 +915,10 @@ public PGPSecretKeyRing build() /** * Build the {@link PGPSecretKeyRing OpenPGP key} using a single passphrase used to protect all subkeys. * The passphrase will override whichever key protectors were specified in previous builder steps. + * + * @param passphrase nullable passphrase * @return OpenPGP key - * @throws PGPException + * @throws PGPException if the key cannot be generated */ public PGPSecretKeyRing build(char[] passphrase) throws PGPException @@ -797,17 +993,12 @@ public Configuration(Date creationTime) } /** - * Pair of a {@link PGPKeyPair} and (nullable) {@link PBESecretKeyEncryptor}. + * Tuple of a {@link PGPKeyPair} and (nullable) {@link PBESecretKeyEncryptor}. */ private static class Key { - private PGPKeyPair pair; - - // We introduce Optional here, because that way we can reflect 3 states: - // * The user explicitly wants to encrypt a (sub) key using a dedicated password -> Optional.some(encryptor) - // * The user explicitly wants to keep the key unencrypted -> Optional.empty() - // * The user wants to use one single password for all subkeys -> null - private PBESecretKeyEncryptor encryptor; + private final PGPKeyPair pair; + private final PBESecretKeyEncryptor encryptor; public Key(PGPKeyPair key, PBESecretKeyEncryptor encryptor) { From 58abd3303012a0498455666a96f34fea0d25deb4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 11:36:30 +0200 Subject: [PATCH 30/53] Rename APIProvider, add base tests --- .../api/test/OpenPGPV6KeyGeneratorTest.java | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 6f1b7eee0f..bf164e6440 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -3,6 +3,7 @@ import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.SecretKeyPacket; +import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; @@ -12,6 +13,7 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPV6KeyGenerator; @@ -37,24 +39,24 @@ public void performTest() throws Exception { // Run tests using the BC implementation - performTests(new ImplementationProvider() + performTests(new APIProvider() { @Override - public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, - Date creationTime, - boolean aeadProtection) + public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) { return new BcOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection); } }); // Run tests using the JCA/JCE implementation - performTests(new ImplementationProvider() + performTests(new APIProvider() { @Override - public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, - Date creationTime, - boolean aeadProtection) + public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, + Date creationTime, + boolean aeadProtection) throws PGPException { return new JcaOpenPGPV6KeyGenerator(signatureHashAlgorithm, creationTime, aeadProtection, @@ -63,23 +65,23 @@ public OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, }); } - private void performTests(ImplementationProvider implementationProvider) + private void performTests(APIProvider apiProvider) throws PGPException, IOException { - testGenerateSignOnlyKeyBaseCase(implementationProvider); - testGenerateAEADProtectedSignOnlyKey(implementationProvider); - testGenerateCFBProtectedSignOnlyKey(implementationProvider); + testGenerateSignOnlyKeyBaseCase(apiProvider); + testGenerateAEADProtectedSignOnlyKey(apiProvider); + testGenerateCFBProtectedSignOnlyKey(apiProvider); - testGenerateClassicKeyBaseCase(implementationProvider); - testGenerateProtectedTypicalKey(implementationProvider); + testGenerateClassicKeyBaseCase(apiProvider); + testGenerateProtectedTypicalKey(apiProvider); - testGenerateCustomKey(implementationProvider); + testGenerateCustomKey(apiProvider); } - private void testGenerateSignOnlyKeyBaseCase(ImplementationProvider implementationProvider) + private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) throws PGPException { - OpenPGPV6KeyGenerator generator = implementationProvider.get(); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(); PGPSecretKeyRing secretKeys = generator.signOnlyKey(); Iterator it = secretKeys.getSecretKeys(); @@ -87,17 +89,26 @@ private void testGenerateSignOnlyKeyBaseCase(ImplementationProvider implementati isFalse("sign-only key MUST consists of only a single key", it.hasNext()); PGPSignature directKeySignature = primaryKey.getPublicKey().getKeySignatures().next(); isNotNull("Key MUST have direct-key signature", directKeySignature); + isEquals("Direct-key signature MUST be version 6", + SignaturePacket.VERSION_6, directKeySignature.getVersion()); + PGPSignatureSubpacketVector hPackets = directKeySignature.getHashedSubPackets(); + isNotNull("Subpackets MUST contain issuer-fingerprint subpacket", + hPackets.getIssuerFingerprint()); + isFalse("Subpackets MUST NOT contain issuer-key-id subpacket", + hPackets.hasSubpacket(SignatureSubpacketTags.ISSUER_KEY_ID)); + isNotNull("Subpackets MUST contain signature creation-time subpacket", + hPackets.getSignatureCreationTime()); isEquals("Sign-Only primary key MUST carry CS flags", - KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, directKeySignature.getHashedSubPackets().getKeyFlags()); + KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, hPackets.getKeyFlags()); isEquals("Key version mismatch", 6, primaryKey.getPublicKey().getVersion()); isEquals("Key MUST be unprotected", SecretKeyPacket.USAGE_NONE, primaryKey.getS2KUsage()); } - private void testGenerateAEADProtectedSignOnlyKey(ImplementationProvider implementationProvider) + private void testGenerateAEADProtectedSignOnlyKey(APIProvider apiProvider) throws PGPException { - OpenPGPV6KeyGenerator generator = implementationProvider.get(true); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(true); PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); Iterator it = secretKeys.getSecretKeys(); @@ -111,10 +122,10 @@ private void testGenerateAEADProtectedSignOnlyKey(ImplementationProvider impleme .build("passphrase".toCharArray()))); } - private void testGenerateCFBProtectedSignOnlyKey(ImplementationProvider implementationProvider) + private void testGenerateCFBProtectedSignOnlyKey(APIProvider apiProvider) throws PGPException { - OpenPGPV6KeyGenerator generator = implementationProvider.get(false); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(false); PGPSecretKeyRing secretKeys = generator.signOnlyKey("passphrase".toCharArray()); Iterator it = secretKeys.getSecretKeys(); @@ -128,11 +139,11 @@ private void testGenerateCFBProtectedSignOnlyKey(ImplementationProvider implemen .build("passphrase".toCharArray()))); } - private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) + private void testGenerateClassicKeyBaseCase(APIProvider apiProvider) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = provider.get(creationTime); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); PGPSecretKeyRing secretKeys = generator .classicKey("Alice ", null); @@ -187,11 +198,11 @@ private void testGenerateClassicKeyBaseCase(ImplementationProvider provider) } } - private void testGenerateProtectedTypicalKey(ImplementationProvider provider) + private void testGenerateProtectedTypicalKey(APIProvider apiProvider) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = provider.get(creationTime); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); PGPSecretKeyRing secretKeys = generator .classicKey("Alice ", "passphrase".toCharArray()); @@ -218,11 +229,11 @@ private void testGenerateProtectedTypicalKey(ImplementationProvider provider) } } - private void testGenerateCustomKey(ImplementationProvider implementationProvider) + private void testGenerateCustomKey(APIProvider apiProvider) throws PGPException { Date creationTime = currentTimeRounded(); - OpenPGPV6KeyGenerator generator = implementationProvider.get(creationTime); + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(creationTime); PGPSecretKeyRing secretKey = generator .withPrimaryKey( @@ -258,27 +269,27 @@ private void testGenerateCustomKey(ImplementationProvider implementationProvider .build(); } - private abstract static class ImplementationProvider + private abstract static class APIProvider { - public OpenPGPV6KeyGenerator get() + public OpenPGPV6KeyGenerator getKeyGenerator() throws PGPException { - return get(new Date()); + return getKeyGenerator(new Date()); } - public OpenPGPV6KeyGenerator get(Date creationTime) + public OpenPGPV6KeyGenerator getKeyGenerator(Date creationTime) throws PGPException { - return get(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); + return getKeyGenerator(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, creationTime, true); } - public OpenPGPV6KeyGenerator get(boolean aeadProtection) + public OpenPGPV6KeyGenerator getKeyGenerator(boolean aeadProtection) throws PGPException { - return get(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, new Date(), aeadProtection); + return getKeyGenerator(OpenPGPV6KeyGenerator.DEFAULT_SIGNATURE_HASH_ALGORITHM, new Date(), aeadProtection); } - public abstract OpenPGPV6KeyGenerator get(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) + public abstract OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, Date creationTime, boolean aeadProtection) throws PGPException; } From dcf6202f91a6640c0711969c6de484d1c58c0d85 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Oct 2024 15:37:45 +0200 Subject: [PATCH 31/53] Javadoc --- .../openpgp/api/SignatureSubpacketsFunction.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java index 2b2e0fd94b..177954b692 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/SignatureSubpacketsFunction.java @@ -1,9 +1,13 @@ package org.bouncycastle.openpgp.api; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; /** * Callback to modify the contents of a {@link PGPSignatureSubpacketGenerator}. + * The {@link OpenPGPV6KeyGenerator} already prepopulates the hashed subpacket areas of signatures during + * key generation. This callback is useful to apply custom changes to the hashed subpacket area during the + * generation process. */ @FunctionalInterface public interface SignatureSubpacketsFunction @@ -11,9 +15,14 @@ public interface SignatureSubpacketsFunction /** * Apply some changes to the given {@link PGPSignatureSubpacketGenerator} and return the result. * It is also possible to replace the whole {@link PGPSignatureSubpacketGenerator} by returning another instance. + * Tipp: In order to replace a subpacket, make sure to prevent duplicates by first removing subpackets + * of the same type using {@link PGPSignatureSubpacketGenerator#removePacketsOfType(int)}. + * To inspect the current contents of the generator, it is best to call + * {@link PGPSignatureSubpacketGenerator#generate()} and in turn inspect its contents using + * {@link PGPSignatureSubpacketVector#toArray()}. * * @param subpackets original subpackets - * @return modified subpackets + * @return non-null modified subpackets */ PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets); } From 340a86b748b72a0d6a4b2927a43a4eacdff5165a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Oct 2024 15:54:21 +0200 Subject: [PATCH 32/53] Refactore PGPSignatureSubpacketGenerator --- .../PGPSignatureSubpacketGenerator.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java index a0637326ba..b4279676b7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketGenerator.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; @@ -56,10 +57,7 @@ public PGPSignatureSubpacketGenerator(PGPSignatureSubpacketVector sigSubV) { if (sigSubV != null) { - for (int i = 0; i != sigSubV.packets.length; i++) - { - packets.add(sigSubV.packets[i]); - } + packets.addAll(Arrays.asList(sigSubV.packets)); } } @@ -651,9 +649,9 @@ public boolean removePacketsOfType(int subpacketType) public boolean hasSubpacket( int type) { - for (int i = 0; i != packets.size(); i++) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { return true; } @@ -672,30 +670,30 @@ public boolean hasSubpacket( public SignatureSubpacket[] getSubpackets( int type) { - List list = new ArrayList(); + List list = new ArrayList<>(); - for (int i = 0; i != packets.size(); i++) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { - list.add(packets.get(i)); + list.add(packet); } } - return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{}); + return list.toArray(new SignatureSubpacket[0]); } public PGPSignatureSubpacketVector generate() { return new PGPSignatureSubpacketVector( - (SignatureSubpacket[])packets.toArray(new SignatureSubpacket[packets.size()])); + packets.toArray(new SignatureSubpacket[0])); } private boolean contains(int type) { - for (int i = 0; i < packets.size(); ++i) + for (SignatureSubpacket packet : packets) { - if (((SignatureSubpacket)packets.get(i)).getType() == type) + if (packet.getType() == type) { return true; } From c233c42b6c04929b5a92c389366794c58b001f0c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Oct 2024 16:12:30 +0200 Subject: [PATCH 33/53] Refactore PGPSignatureSubpacketVector --- .../openpgp/PGPSignatureSubpacketVector.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java index 10e36a478e..078faa49f6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureSubpacketVector.java @@ -116,7 +116,7 @@ public boolean hasSubpacket( public SignatureSubpacket[] getSubpackets( int type) { - List list = new ArrayList(); + List list = new ArrayList<>(); for (int i = 0; i != packets.length; i++) { @@ -126,20 +126,20 @@ public SignatureSubpacket[] getSubpackets( } } - return (SignatureSubpacket[])list.toArray(new SignatureSubpacket[]{}); + return list.toArray(new SignatureSubpacket[0]); } public PGPSignatureList getEmbeddedSignatures() throws PGPException { SignatureSubpacket[] sigs = getSubpackets(SignatureSubpacketTags.EMBEDDED_SIGNATURE); - ArrayList l = new ArrayList(); + ArrayList l = new ArrayList<>(); - for (int i = 0; i < sigs.length; i++) + for (SignatureSubpacket sig : sigs) { try { - l.add(new PGPSignature(SignaturePacket.fromByteArray(sigs[i].getData()))); + l.add(new PGPSignature(SignaturePacket.fromByteArray(sig.getData()))); } catch (IOException e) { @@ -147,7 +147,7 @@ public PGPSignatureList getEmbeddedSignatures() } } - return new PGPSignatureList((PGPSignature[])l.toArray(new PGPSignature[l.size()])); + return new PGPSignatureList(l.toArray(new PGPSignature[0])); } public NotationData[] getNotationDataOccurrences() @@ -179,7 +179,7 @@ public NotationData[] getNotationDataOccurences() public NotationData[] getNotationDataOccurrences(String notationName) { NotationData[] notations = getNotationDataOccurrences(); - List notationsWithName = new ArrayList(); + List notationsWithName = new ArrayList<>(); for (int i = 0; i != notations.length; i++) { NotationData notation = notations[i]; @@ -188,7 +188,7 @@ public NotationData[] getNotationDataOccurrences(String notationName) notationsWithName.add(notation); } } - return (NotationData[])notationsWithName.toArray(new NotationData[0]); + return notationsWithName.toArray(new NotationData[0]); } public long getIssuerKeyID() From 69c3a499915504ebc8f5f1a21d2667831e708c67 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 17 Oct 2024 13:13:20 +0200 Subject: [PATCH 34/53] Add documentation about allowed S2K specifier + usages to PBESecretKeyEncryptor --- .../operator/PBESecretKeyEncryptor.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java index 956cc9067a..c9a9846877 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptor.java @@ -5,6 +5,52 @@ import org.bouncycastle.bcpg.S2K; import org.bouncycastle.openpgp.PGPException; +/** + * Class responsible for encrypting secret key material or data packets using a passphrase. + *

+ * RFC9580 recommends the following S2K specifiers + usages: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
S2K SpecifierS2K UsageNote
{@link S2K#ARGON_2}{@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_AEAD}RECOMMENDED; Argon2 MUST be used with AEAD
{@link S2K#SALTED_AND_ITERATED}{@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_SHA1}MAY be used if Argon2 is not available; Take care to use high octet count + strong passphrase
none{@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_NONE}Unprotected
+ *

+ * Additionally, implementations MAY use the following combinations with caution: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
S2K SpecifierS2K UsageNote
{@link S2K#SALTED_AND_ITERATED}{@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_AEAD}Does not provide memory hardness
{@link S2K#SIMPLE}{@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_SHA1}Only for reading secret keys in backwards compatibility mode
+ */ public abstract class PBESecretKeyEncryptor { protected int encAlgorithm; From 062ebbb7bd6e81aa4a1a635fd90af9e9dcd57fd5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 17 Oct 2024 15:14:28 +0200 Subject: [PATCH 35/53] Rename creationTime to keyCreationTime for clarity --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 355e01d349..e81b0a34e6 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -5,6 +5,7 @@ import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; +import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.Features; @@ -196,7 +197,7 @@ public PGPSecretKeyRing signOnlyKey( SignatureSubpacketsFunction userSubpackets) throws PGPException { - PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime) + PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime) .generatePrimaryKey(); PBESecretKeyEncryptor encryptor = impl.keyEncryptorBuilderProvider .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); @@ -334,7 +335,7 @@ public WithPrimaryKey withPrimaryKey( throws PGPException { PGPKeyPair pkPair = keyGenCallback.generateFrom( - impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider .build(passphrase, pkPair.getPublicKey().getPublicKeyPacket()); return withPrimaryKey(pkPair, directKeySubpackets, keyEncryptor); @@ -372,7 +373,7 @@ public WithPrimaryKey withPrimaryKey( subpackets -> { subpackets.setIssuerFingerprint(true, keyPair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.creationTime); + subpackets.setSignatureCreationTime(conf.keyCreationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); subpackets.setKeyExpirationTime(false, 5 * SECONDS_PER_YEAR); @@ -525,7 +526,7 @@ public WithPrimaryKey addUserId( PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.creationTime); + subpackets.setSignatureCreationTime(conf.keyCreationTime); if (userIdSubpackets != null) { @@ -585,7 +586,7 @@ public WithPrimaryKey addEncryptionSubkey( { PGPKeyPairGenerator generator = impl.kpGenProvider.get( primaryKey.pair.getPublicKey().getVersion(), - conf.creationTime + conf.keyCreationTime ); PGPKeyPair subkey = generatorCallback.generateFrom(generator); @@ -653,7 +654,7 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac char[] passphrase) throws PGPException { - PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addEncryptionSubkey(subkey, bindingSignatureCallback, keyEncryptor); } @@ -686,7 +687,7 @@ public WithPrimaryKey addEncryptionSubkey( PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); subpackets.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - subpackets.setSignatureCreationTime(conf.creationTime); + subpackets.setSignatureCreationTime(conf.keyCreationTime); // allow subpacket customization if (bindingSubpacketsCallback != null) @@ -795,7 +796,7 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, char[] passphrase) throws PGPException { - PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.creationTime)); + PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); } @@ -830,7 +831,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); backSigSubpackets.setIssuerFingerprint(true, signingKey.getPublicKey()); - backSigSubpackets.setSignatureCreationTime(conf.creationTime); + backSigSubpackets.setSignatureCreationTime(conf.keyCreationTime); if (backSignatureCallback != null) { backSigSubpackets = backSignatureCallback.apply(backSigSubpackets); @@ -838,7 +839,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, PGPSignatureSubpacketGenerator bindingSigSubpackets = new PGPSignatureSubpacketGenerator(); bindingSigSubpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); - bindingSigSubpackets.setSignatureCreationTime(conf.creationTime); + bindingSigSubpackets.setSignatureCreationTime(conf.keyCreationTime); bindingSigSubpackets = SIGNING_SUBKEY_SUBPACKETS.apply(bindingSigSubpackets); @@ -925,6 +926,7 @@ public PGPSecretKeyRing build(char[] passphrase) { PBESecretKeyEncryptor primaryKeyEncryptor = impl.keyEncryptorBuilderProvider .build(passphrase, primaryKey.pair.getPublicKey().getPublicKeyPacket()); + sanitizeKeyEncryptor(primaryKeyEncryptor); PGPSecretKey primarySecretKey = new PGPSecretKey( primaryKey.pair.getPrivateKey(), primaryKey.pair.getPublicKey(), @@ -938,6 +940,7 @@ public PGPSecretKeyRing build(char[] passphrase) { PBESecretKeyEncryptor subkeyEncryptor = impl.keyEncryptorBuilderProvider .build(passphrase, key.pair.getPublicKey().getPublicKeyPacket()); + sanitizeKeyEncryptor(subkeyEncryptor); PGPSecretKey subkey = new PGPSecretKey( key.pair.getPrivateKey(), key.pair.getPublicKey(), @@ -955,6 +958,28 @@ public PGPSecretKeyRing build(char[] passphrase) return new PGPSecretKeyRing(keys); } + + protected void sanitizeKeyEncryptor(PBESecretKeyEncryptor keyEncryptor) + { + if (keyEncryptor == null) + { + // Unprotected is okay + return; + } + + S2K s2k = keyEncryptor.getS2K(); + if (s2k.getType() == S2K.SIMPLE || s2k.getType() == S2K.SALTED) + { + throw new IllegalArgumentException("S2K specifiers SIMPLE and SALTED are not allowed for secret key encryption."); + } + else if (s2k.getType() == S2K.ARGON_2) + { + if (keyEncryptor.getAeadAlgorithm() == 0) + { + throw new IllegalArgumentException("Argon2 MUST be used with AEAD."); + } + } + } } /** @@ -984,11 +1009,11 @@ public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, */ private static class Configuration { - final Date creationTime; + final Date keyCreationTime; - public Configuration(Date creationTime) + public Configuration(Date keyCreationTime) { - this.creationTime = creationTime; + this.keyCreationTime = keyCreationTime; } } From 541b80d266c54b135824a9d14e63488468363b10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 17 Oct 2024 15:18:49 +0200 Subject: [PATCH 36/53] Use default callback for encryption subkey subpackets --- .../bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index e81b0a34e6..51897af9ea 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -97,6 +97,13 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + public static SignatureSubpacketsFunction ENCRYPTION_SUBKEY_SUBPACKETS = subpackets -> + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(true, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); + return subpackets; + }; + public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = subpackets -> { subpackets = DEFAULT_FEATURES.apply(subpackets); @@ -685,9 +692,9 @@ public WithPrimaryKey addEncryptionSubkey( } // generate binding signature PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator(); - subpackets.setKeyFlags(false, KeyFlags.ENCRYPT_STORAGE | KeyFlags.ENCRYPT_COMMS); subpackets.setIssuerFingerprint(true, primaryKey.pair.getPublicKey()); subpackets.setSignatureCreationTime(conf.keyCreationTime); + subpackets = ENCRYPTION_SUBKEY_SUBPACKETS.apply(subpackets); // allow subpacket customization if (bindingSubpacketsCallback != null) From bc9c82a9cb56c7f1cb711480e56eb060b48141b1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 17 Oct 2024 15:39:35 +0200 Subject: [PATCH 37/53] Add ed25519x25519Key(), ed448x448Key() factory methods --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 51897af9ea..3263179c40 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -159,18 +159,46 @@ public PGPSecretKeyRing classicKey(String userId, char[] passphrase) .addEncryptionSubkey() .build(passphrase); } + /** - * Generate a sign-only OpenPGP key. - * The key consists of a single, user-id-less Ed25519 key, which is capable of signing and certifying. - * It carries a single direct-key signature with signing-related preferences. + * Generate an OpenPGP key consisting of an Ed25519 certify-only primary key, + * a dedicated Ed25519 sign-only subkey and dedicated X25519 encryption-only subkey. + * The key will carry the provided user-id and be protected using the provided passphrase. * - * @return sign-only (+certify) OpenPGP key + * @param userId user id + * @param passphrase nullable passphrase + * @return OpenPGP key * @throws PGPException if the key cannot be generated */ - public PGPSecretKeyRing signOnlyKey() + public PGPSecretKeyRing ed25519x25519Key(String userId, char[] passphrase) throws PGPException { - return signOnlyKey(null); + return withPrimaryKey(PGPKeyPairGenerator::generateEd25519KeyPair) + .addSigningSubkey(PGPKeyPairGenerator::generateEd25519KeyPair) + .addEncryptionSubkey(PGPKeyPairGenerator::generateX25519KeyPair) + .addUserId(userId) + .build(passphrase); + } + + + /** + * Generate an OpenPGP key consisting of an Ed448 certify-only primary key, + * a dedicated Ed448 sign-only subkey and dedicated X448 encryption-only subkey. + * The key will carry the provided user-id and be protected using the provided passphrase. + * + * @param userId user id + * @param passphrase nullable passphrase + * @return OpenPGP key + * @throws PGPException if the key cannot be generated + */ + public PGPSecretKeyRing ed448x448Key(String userId, char[] passphrase) + throws PGPException + { + return withPrimaryKey(PGPKeyPairGenerator::generateEd448KeyPair) + .addSigningSubkey(PGPKeyPairGenerator::generateEd448KeyPair) + .addEncryptionSubkey(PGPKeyPairGenerator::generateX448KeyPair) + .addUserId(userId) + .build(passphrase); } /** @@ -257,7 +285,14 @@ public PGPSecretKeyRing signOnlyKey( public WithPrimaryKey withPrimaryKey() throws PGPException { - return withPrimaryKey(null); + return withPrimaryKey((SignatureSubpacketsFunction) null); + } + + public WithPrimaryKey withPrimaryKey( + KeyPairGeneratorCallback keyGenCallback) + throws PGPException + { + return withPrimaryKey(keyGenCallback, null); } /** From 5aa07f6c846b2a03c1b1739d70643446392488e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 17 Oct 2024 15:39:42 +0200 Subject: [PATCH 38/53] Fix tests --- .../openpgp/api/test/OpenPGPV6KeyGeneratorTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index bf164e6440..be0de114ce 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -17,6 +17,7 @@ import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPV6KeyGenerator; +import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; @@ -82,7 +83,7 @@ private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) throws PGPException { OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(); - PGPSecretKeyRing secretKeys = generator.signOnlyKey(); + PGPSecretKeyRing secretKeys = generator.signOnlyKey(null); Iterator it = secretKeys.getSecretKeys(); PGPSecretKey primaryKey = it.next(); @@ -255,7 +256,7 @@ private void testGenerateCustomKey(APIProvider apiProvider) "primary-key-passphrase".toCharArray()) .addUserId("Alice ", PGPSignature.DEFAULT_CERTIFICATION, null) .addSigningSubkey( - keyGen -> keyGen.generateEd448KeyPair(), + PGPKeyPairGenerator::generateEd448KeyPair, bindingSubpackets -> { bindingSubpackets.addNotationData(false, true, @@ -264,7 +265,7 @@ private void testGenerateCustomKey(APIProvider apiProvider) }, null, "signing-key-passphrase".toCharArray()) - .addEncryptionSubkey(keyGenCallback -> keyGenCallback.generateX448KeyPair(), + .addEncryptionSubkey(PGPKeyPairGenerator::generateX448KeyPair, "encryption-key-passphrase".toCharArray()) .build(); } From b86c2b99a3f01dbc338af2d73d9754af21e87637 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 18 Oct 2024 11:36:00 +0200 Subject: [PATCH 39/53] Fix subkey encryption --- .../org/bouncycastle/openpgp/PGPKeyPair.java | 15 +++++ .../openpgp/api/OpenPGPV6KeyGenerator.java | 15 ++++- .../api/bc/BcOpenPGPV6KeyGenerator.java | 2 + .../api/jcajce/JcaOpenPGPV6KeyGenerator.java | 2 + .../api/test/OpenPGPV6KeyGeneratorTest.java | 66 ++++++++++++++++++- 5 files changed, 96 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java index aad95acd7d..8c61092364 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java @@ -1,6 +1,8 @@ package org.bouncycastle.openpgp; import org.bouncycastle.bcpg.KeyIdentifier; +import org.bouncycastle.bcpg.PublicSubkeyPacket; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; /** * General class to handle JCA key pairs and convert them into OpenPGP ones. @@ -63,4 +65,17 @@ public PGPPrivateKey getPrivateKey() { return priv; } + + public PGPKeyPair asSubkey(KeyFingerPrintCalculator fingerPrintCalculator) + throws PGPException + { + PublicSubkeyPacket pubSubPkt = new PublicSubkeyPacket( + pub.getVersion(), + pub.getAlgorithm(), + pub.getCreationTime(), + pub.getPublicKeyPacket().getKey()); + return new PGPKeyPair( + new PGPPublicKey(pubSubPkt, fingerPrintCalculator), + new PGPPrivateKey(pub.getKeyID(), pubSubPkt, priv.getPrivateKeyDataPacket())); + } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 3263179c40..707eae89b7 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -19,12 +19,14 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.util.Arrays; import java.io.IOException; @@ -131,9 +133,10 @@ public OpenPGPV6KeyGenerator( PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, PBESecretKeyEncryptorFactory keyEncryptionBuilderProvider, + KeyFingerPrintCalculator keyFingerPrintCalculator, Date creationTime) { - this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider); + this.impl = new Implementation(kpGenProvider, contentSignerBuilderProvider, digestCalculatorProvider, keyEncryptionBuilderProvider, keyFingerPrintCalculator); this.conf = new Configuration(new Date((creationTime.getTime() / 1000) * 1000)); } @@ -696,7 +699,9 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac char[] passphrase) throws PGPException { - PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); + PGPKeyPair subkey = keyGenCallback.generateFrom( + impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); + subkey = subkey.asSubkey(impl.keyFingerprintCalculator); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addEncryptionSubkey(subkey, bindingSignatureCallback, keyEncryptor); } @@ -839,6 +844,7 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, throws PGPException { PGPKeyPair subkey = keyGenCallback.generateFrom(impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); + subkey = subkey.asSubkey(impl.keyFingerprintCalculator); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider.build(passphrase, subkey.getPublicKey().getPublicKeyPacket()); return addSigningSubkey(subkey, bindingSignatureCallback, backSignatureCallback, keyEncryptor); } @@ -1033,16 +1039,19 @@ private static class Implementation final PGPContentSignerBuilderProvider contentSignerBuilderProvider; final PGPDigestCalculatorProvider digestCalculatorProvider; final PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider; + final KeyFingerPrintCalculator keyFingerprintCalculator; public Implementation(PGPKeyPairGeneratorProvider keyPairGeneratorProvider, PGPContentSignerBuilderProvider contentSignerBuilderProvider, PGPDigestCalculatorProvider digestCalculatorProvider, - PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider) + PBESecretKeyEncryptorFactory keyEncryptorBuilderProvider, + KeyFingerPrintCalculator keyFingerPrintCalculator) { this.kpGenProvider = keyPairGeneratorProvider; this.contentSignerBuilderProvider = contentSignerBuilderProvider; this.digestCalculatorProvider = digestCalculatorProvider; this.keyEncryptorBuilderProvider = keyEncryptorBuilderProvider; + this.keyFingerprintCalculator = keyFingerPrintCalculator; } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java index 486f7c4dfe..a3de86fe0b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/bc/BcOpenPGPV6KeyGenerator.java @@ -4,6 +4,7 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.bc.BcCFBSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; @@ -60,6 +61,7 @@ public BcOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, bo new BcPGPContentSignerBuilderProvider(signatureHashAlgorithm), new BcPGPDigestCalculatorProvider(), keyEncryptorFactory(aeadProtection), + new BcKeyFingerprintCalculator(), creationTime); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java index 282f24fcd9..bf4a902804 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/jcajce/JcaOpenPGPV6KeyGenerator.java @@ -5,6 +5,7 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaCFBSecretKeyEncryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilderProvider; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPairGeneratorProvider; @@ -52,6 +53,7 @@ public JcaOpenPGPV6KeyGenerator(int signatureHashAlgorithm, Date creationTime, b .setProvider(provider) .build(), keyEncryptorFactory(provider, aeadProtection), + new JcaKeyFingerprintCalculator(), creationTime); } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index be0de114ce..2182c0d0a8 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.api.test; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.SecretKeyPacket; @@ -69,6 +70,8 @@ public OpenPGPV6KeyGenerator getKeyGenerator(int signatureHashAlgorithm, private void performTests(APIProvider apiProvider) throws PGPException, IOException { + testGenerateCustomKey(apiProvider); + testGenerateSignOnlyKeyBaseCase(apiProvider); testGenerateAEADProtectedSignOnlyKey(apiProvider); testGenerateCFBProtectedSignOnlyKey(apiProvider); @@ -76,7 +79,6 @@ private void performTests(APIProvider apiProvider) testGenerateClassicKeyBaseCase(apiProvider); testGenerateProtectedTypicalKey(apiProvider); - testGenerateCustomKey(apiProvider); } private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) @@ -268,6 +270,68 @@ private void testGenerateCustomKey(APIProvider apiProvider) .addEncryptionSubkey(PGPKeyPairGenerator::generateX448KeyPair, "encryption-key-passphrase".toCharArray()) .build(); + + Iterator keyIt = secretKey.getSecretKeys(); + PGPSecretKey primaryKey = keyIt.next(); + isEquals("Primary key MUST be RSA_GENERAL", + PublicKeyAlgorithmTags.RSA_GENERAL, primaryKey.getPublicKey().getAlgorithm()); + isEquals("Primary key MUST be 4096 bits", 4096, primaryKey.getPublicKey().getBitStrength()); + isEquals("Primary key creation time mismatch", + creationTime, primaryKey.getPublicKey().getCreationTime()); + PGPSignature directKeySig = primaryKey.getPublicKey().getKeySignatures().next(); + PGPSignatureSubpacketVector hashedSubpackets = directKeySig.getHashedSubPackets(); + isEquals("Primary key key flags mismatch", + KeyFlags.CERTIFY_OTHER, hashedSubpackets.getKeyFlags()); + isEquals("Primary key features mismatch", + Features.FEATURE_SEIPD_V2, hashedSubpackets.getFeatures().getFeatures()); + isEquals("Primary key sig notation data mismatch", + "CYBER", + hashedSubpackets.getNotationDataOccurrences("notation@example.com")[0].getNotationValue()); + + Iterator uids = primaryKey.getUserIDs(); + String uid = uids.next(); + isFalse("Unexpected additional UID", uids.hasNext()); + PGPSignature uidSig = primaryKey.getPublicKey().getSignaturesForID(uid).next(); + isEquals("UID binding sig type mismatch", + PGPSignature.DEFAULT_CERTIFICATION, uidSig.getSignatureType()); + + PGPSecretKey signingSubkey = keyIt.next(); + isEquals("Subkey MUST be Ed448", + PublicKeyAlgorithmTags.Ed448, signingSubkey.getPublicKey().getAlgorithm()); + isEquals("Subkey creation time mismatch", + creationTime, signingSubkey.getPublicKey().getCreationTime()); + PGPSignature sigSubBinding = signingSubkey.getPublicKey().getKeySignatures().next(); + PGPSignatureSubpacketVector sigSubBindHashPkts = sigSubBinding.getHashedSubPackets(); + isEquals("Encryption subkey key flags mismatch", + KeyFlags.SIGN_DATA, sigSubBindHashPkts.getKeyFlags()); + isEquals("Subkey notation data mismatch", + "ZAUBER", + sigSubBindHashPkts.getNotationDataOccurrences("notation@example.com")[0].getNotationValue()); + isFalse("Missing embedded primary key binding signature", + sigSubBindHashPkts.getEmbeddedSignatures().isEmpty()); + + PGPSecretKey encryptionSubkey = keyIt.next(); + isFalse("Unexpected additional subkey", keyIt.hasNext()); + isEquals("Subkey MUST be X448", + PublicKeyAlgorithmTags.X448, encryptionSubkey.getPublicKey().getAlgorithm()); + isEquals("Subkey creation time mismatch", + creationTime, encryptionSubkey.getPublicKey().getCreationTime()); + PGPSignature encryptionBinding = encryptionSubkey.getPublicKey().getKeySignatures().next(); + PGPSignatureSubpacketVector encBindHashPkts = encryptionBinding.getHashedSubPackets(); + isEquals("Encryption subkey key flags mismatch", + KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, encBindHashPkts.getKeyFlags()); + isTrue("Unexpected embedded primary key binding signature in encryption subkey binding", + encBindHashPkts.getEmbeddedSignatures().isEmpty()); + + BcPBESecretKeyDecryptorBuilder keyDecryptorBuilder = new BcPBESecretKeyDecryptorBuilder( + new BcPGPDigestCalculatorProvider()); + + isNotNull("Could not decrypt primary key using correct passphrase", + primaryKey.extractPrivateKey(keyDecryptorBuilder.build("primary-key-passphrase".toCharArray()))); + isNotNull("Could not decrypt signing subkey using correct passphrase", + signingSubkey.extractPrivateKey(keyDecryptorBuilder.build("signing-key-passphrase".toCharArray()))); + isNotNull("Could not decrypt encryption subkey using correct passphrase", + encryptionSubkey.extractPrivateKey(keyDecryptorBuilder.build("encryption-key-passphrase".toCharArray()))); } private abstract static class APIProvider From 8ea7c28a938af6fa0eec1e8c6987d74a9a57fe9f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 13:50:22 +0200 Subject: [PATCH 40/53] Add javadoc to PGPKeyPairGenerator --- .../openpgp/operator/PGPKeyPairGenerator.java | 93 ++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java index 8089d1e3b7..2a15ba2d36 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java @@ -1,5 +1,6 @@ package org.bouncycastle.openpgp.operator; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; @@ -14,6 +15,14 @@ public abstract class PGPKeyPairGenerator protected final int version; protected SecureRandom random; + /** + * Create an instance of the key pair generator. + * + * @param version public key version ({@link org.bouncycastle.bcpg.PublicKeyPacket#VERSION_4} + * or {@link org.bouncycastle.bcpg.PublicKeyPacket#VERSION_6}). + * @param creationTime key creation time + * @param random secure random number generator + */ public PGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) { this.creationTime = new Date((creationTime.getTime() / 1000) * 1000); @@ -26,7 +35,7 @@ public PGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) * A primary key MUST use a signing-capable public key algorithm. * * @return primary key pair - * @throws PGPException + * @throws PGPException if the key pair cannot be generated */ public PGPKeyPair generatePrimaryKey() throws PGPException @@ -39,7 +48,7 @@ public PGPKeyPair generatePrimaryKey() * An encryption subkey MUST use an encryption-capable public key algorithm. * * @return encryption subkey pair - * @throws PGPException + * @throws PGPException if the key pair cannot be generated */ public PGPKeyPair generateEncryptionSubkey() throws PGPException @@ -52,7 +61,7 @@ public PGPKeyPair generateEncryptionSubkey() * A signing subkey MUST use a signing-capable public key algorithm. * * @return signing subkey pair - * @throws PGPException + * @throws PGPException if the key pair cannot be generated */ public PGPKeyPair generateSigningSubkey() throws PGPException @@ -60,30 +69,108 @@ public PGPKeyPair generateSigningSubkey() return generateEd25519KeyPair(); } + /** + * Generate a RSA key pair with the given bit-strength. + * It is recommended to use at least 2048 bits or more. + * The key will be generated over the default exponent

65537
. + * RSA keys are deprecated for OpenPGP v6. + * + * @param bitStrength strength of the key pair in bits + * @return rsa key pair + * @throws PGPException if the key pair cannot be generated + */ public PGPKeyPair generateRsaKeyPair(int bitStrength) throws PGPException { return generateRsaKeyPair(BigInteger.valueOf(0x10001), bitStrength); } + /** + * Generate a RSA key pair with the given bit-strength over a custom exponent. + * It is recommended to use at least 2048 bits or more. + * RSA keys are deprecated for OpenPGP v6. + * + * @param exponent RSA exponent
e
+ * @param bitStrength strength of the key pair in bits + * @return rsa key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateRsaKeyPair(BigInteger exponent, int bitStrength) throws PGPException; + /** + * Generate an elliptic curve signing key over the twisted Edwards curve25519. + * The key will use {@link PublicKeyAlgorithmTags#Ed25519} which was introduced with RFC9580. + * For legacy Ed25519 keys use {@link #generateLegacyEd25519KeyPair()}. + * + * @see + * RFC9580 - Public Key Algorithms + * @return Ed25519 key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateEd25519KeyPair() throws PGPException; + /** + * Generate an elliptic curve signing key over the twisted Edwards curve448. + * The key will use {@link PublicKeyAlgorithmTags#Ed448} which was introduced with RFC9580. + * + * @see + * RFC9580 - Public Key Algorithms + * @return Ed448 signing key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateEd448KeyPair() throws PGPException; + /** + * Generate an elliptic curve Diffie-Hellman encryption key over curve25519. + * THe key will use {@link PublicKeyAlgorithmTags#X25519} which was introduced with RFC9580. + * For legacy X25519 keys use {@link #generateLegacyX25519KeyPair()} instead. + * + * @see + * RFC9580 - Public Key Algorithms + * @return X25519 encryption key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateX25519KeyPair() throws PGPException; + /** + * Generate an elliptic curve Diffie-Hellman encryption key over curve448. + * THe key will use {@link PublicKeyAlgorithmTags#X448} which was introduced with RFC9580. + * + * @see + * RFC9580 - Public Key Algorithms + * @return X448 encryption key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateX448KeyPair() throws PGPException; + /** + * Generate a legacy elliptic curve signing key pair over the twisted Edwards curve25519. + * Legacy keys have good application support, but MUST NOT be used as OpenPGP v6 keys. + * The key will use {@link PublicKeyAlgorithmTags#EDDSA_LEGACY} as algorithm ID. + * For OpenPGP v6 (RFC9580) use {@link #generateEd25519KeyPair()} instead. + * + * @see + * Legacy Draft: EdDSA for OpenPGP + * @return legacy Ed25519 key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateLegacyEd25519KeyPair() throws PGPException; + /** + * Generate a legacy elliptic curve Diffie-Hellman encryption key pair over curve25519. + * Legacy keys have good application support, but MUST NOT be used as OpenPGP v6 keys. + * The key will use {@link PublicKeyAlgorithmTags#ECDH} as algorithm ID. + * For OpenPGP v6 (RFC9580) use {@link #generateX25519KeyPair()} instead. + * + * @return legacy X25519 key pair + * @throws PGPException if the key pair cannot be generated + */ public abstract PGPKeyPair generateLegacyX25519KeyPair() throws PGPException; } From b75877b1b4cc04a73f52bdb08045032bc8e251c1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 13:54:10 +0200 Subject: [PATCH 41/53] Remove unused imports --- pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java | 1 - .../java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java | 1 - 2 files changed, 2 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java index 99827ef907..04c2191ac5 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKeyRing.java @@ -19,7 +19,6 @@ import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.bcpg.PacketTags; -import org.bouncycastle.bcpg.PublicKeyUtils; import org.bouncycastle.bcpg.PublicSubkeyPacket; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SecretSubkeyPacket; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 707eae89b7..84207e9bda 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -26,7 +26,6 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGeneratorProvider; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.util.Arrays; import java.io.IOException; From 14fc008d714c7fc59cf451b545efdcbf88ec4b8e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 16:10:15 +0200 Subject: [PATCH 42/53] Remove unused AEADSecretKeyEncryptorBuilderProvider class --- .../AEADSecretKeyEncryptorBuilderProvider.java | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java deleted file mode 100644 index 5f6056b391..0000000000 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilderProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.bouncycastle.openpgp.operator; - -import org.bouncycastle.bcpg.S2K; - -public abstract class AEADSecretKeyEncryptorBuilderProvider -{ - - public abstract AEADSecretKeyEncryptorBuilder get( - int aeadAlgorithm, - int symmetricAlgorithm, - S2K.Argon2Params argon2Params); -} From 584fc6bb283e23957cc9d18897c37d8185e8030b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 16:19:10 +0200 Subject: [PATCH 43/53] More javadoc --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 145 ++++++++++++------ .../AEADSecretKeyEncryptorBuilder.java | 13 ++ .../PBESecretKeyEncryptorFactory.java | 13 ++ .../operator/PGPAEADDataEncryptor.java | 3 +- .../PGPContentSignerBuilderProvider.java | 14 ++ 5 files changed, 141 insertions(+), 47 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 84207e9bda..e81b040985 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -38,13 +38,21 @@ */ public class OpenPGPV6KeyGenerator { + /** + * Hash algorithm for key signatures if no other one is provided during construction. + */ public static final int DEFAULT_SIGNATURE_HASH_ALGORITHM = HashAlgorithmTags.SHA3_512; + // SECONDS private static final long SECONDS_PER_MINUTE = 60; private static final long SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; private static final long SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; private static final long SECONDS_PER_YEAR = 365 * SECONDS_PER_DAY; + /** + * Standard AEAD encryption preferences (SEIPDv2). + * By default, only announce support for OCB + AES. + */ public static SignatureSubpacketsFunction DEFAULT_AEAD_ALGORITHM_PREFERENCES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_AEAD_ALGORITHMS); @@ -55,6 +63,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard symmetric-key encryption preferences (SEIPDv1). + * By default, announce support for AES. + */ public static SignatureSubpacketsFunction DEFAULT_SYMMETRIC_KEY_PREFERENCES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); @@ -64,6 +76,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard signature hash algorithm preferences. + * By default, only announce SHA3 and SHA2 algorithms. + */ public static SignatureSubpacketsFunction DEFAULT_HASH_ALGORITHM_PREFERENCES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_HASH_ALGS); @@ -74,6 +90,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard compression algorithm preferences. + * By default, announce support for all known algorithms. + */ public static SignatureSubpacketsFunction DEFAULT_COMPRESSION_ALGORITHM_PREFERENCES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); @@ -84,6 +104,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard features to announce. + * By default, announce SEIPDv1 (modification detection) and SEIPDv2. + */ public static SignatureSubpacketsFunction DEFAULT_FEATURES = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.FEATURES); @@ -91,6 +115,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard signature subpackets for signing subkey's binding signatures. + * Sets the keyflag subpacket to SIGN_DATA. + */ public static SignatureSubpacketsFunction SIGNING_SUBKEY_SUBPACKETS = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); @@ -98,6 +126,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard signature subpackets for encryption subkey's binding signatures. + * Sets the keyflag subpacket to ENCRYPT_STORAGE|ENCRYPT_COMMS. + */ public static SignatureSubpacketsFunction ENCRYPTION_SUBKEY_SUBPACKETS = subpackets -> { subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); @@ -105,6 +137,10 @@ public class OpenPGPV6KeyGenerator return subpackets; }; + /** + * Standard signature subpackets for the direct-key signature. + * Sets default features, hash-, compression-, symmetric-key-, and AEAD algorithm preferences. + */ public static SignatureSubpacketsFunction DIRECT_KEY_SIGNATURE_SUBPACKETS = subpackets -> { subpackets = DEFAULT_FEATURES.apply(subpackets); @@ -115,7 +151,7 @@ public class OpenPGPV6KeyGenerator return subpackets; }; - private final Implementation impl; + private final Implementation impl; // contains BC or JCA/JCE implementations private final Configuration conf; /** @@ -125,6 +161,7 @@ public class OpenPGPV6KeyGenerator * @param contentSignerBuilderProvider content signer builder provider * @param digestCalculatorProvider digest calculator provider * @param keyEncryptionBuilderProvider secret key encryption builder provider (AEAD) + * @param keyFingerPrintCalculator calculator for key fingerprints * @param creationTime key creation time */ public OpenPGPV6KeyGenerator( @@ -234,11 +271,11 @@ public PGPSecretKeyRing signOnlyKey( SignatureSubpacketsFunction userSubpackets) throws PGPException { - PGPKeyPair primaryKey = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime) + PGPKeyPair primaryKeyPair = impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime) .generatePrimaryKey(); PBESecretKeyEncryptor encryptor = impl.keyEncryptorBuilderProvider - .build(passphrase, primaryKey.getPublicKey().getPublicKeyPacket()); - return signOnlyKey(primaryKey, encryptor, userSubpackets); + .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); + return signOnlyKey(primaryKeyPair, encryptor, userSubpackets); } /** @@ -247,19 +284,24 @@ public PGPSecretKeyRing signOnlyKey( * It carries a single direct-key signature with signing-related preferences whose subpackets can be * modified by providing a {@link SignatureSubpacketsFunction}. * - * @param primaryKey signing-capable primary key + * @param primaryKeyPair signing-capable primary key * @param keyEncryptor nullable encryptor to protect the primary key with * @param userSubpackets callback to modify the direct-key signature subpackets with * @return sign-only (+certify) OpenPGP key * @throws PGPException if the key cannot be generated */ public PGPSecretKeyRing signOnlyKey( - PGPKeyPair primaryKey, + PGPKeyPair primaryKeyPair, PBESecretKeyEncryptor keyEncryptor, SignatureSubpacketsFunction userSubpackets) throws PGPException { - return primaryKeyWithDirectKeySig(primaryKey, + if (!primaryKeyPair.getPublicKey().isMasterKey()) + { + throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); + } + + return primaryKeyWithDirectKeySig(primaryKeyPair, baseSubpackets -> { // remove unrelated subpackets not needed for sign-only keys @@ -267,7 +309,7 @@ public PGPSecretKeyRing signOnlyKey( baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_SYM_ALGS); baseSubpackets.removePacketsOfType(SignatureSubpacketTags.PREFERRED_COMP_ALGS); - // replace key flags to add SIGN_DATA + // replace key flags -> CERTIFY_OTHER|SIGN_DATA baseSubpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); baseSubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); return baseSubpackets; @@ -340,18 +382,18 @@ public WithPrimaryKey withPrimaryKey( * The key will carry a direct-key signature, whose subpackets can be modified by overriding the * given {@link SignatureSubpacketsFunction}. * - * @param keyPair primary key + * @param primaryKeyPair primary key * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey withPrimaryKey( - PGPKeyPair keyPair, + PGPKeyPair primaryKeyPair, SignatureSubpacketsFunction directKeySubpackets) throws PGPException { return withPrimaryKey( - keyPair, + primaryKeyPair, directKeySubpackets, null); } @@ -378,11 +420,11 @@ public WithPrimaryKey withPrimaryKey( char[] passphrase) throws PGPException { - PGPKeyPair pkPair = keyGenCallback.generateFrom( + PGPKeyPair primaryKeyPair = keyGenCallback.generateFrom( impl.kpGenProvider.get(PublicKeyPacket.VERSION_6, conf.keyCreationTime)); PBESecretKeyEncryptor keyEncryptor = impl.keyEncryptorBuilderProvider - .build(passphrase, pkPair.getPublicKey().getPublicKeyPacket()); - return withPrimaryKey(pkPair, directKeySubpackets, keyEncryptor); + .build(passphrase, primaryKeyPair.getPublicKey().getPublicKeyPacket()); + return withPrimaryKey(primaryKeyPair, directKeySubpackets, keyEncryptor); } /** @@ -395,28 +437,33 @@ public WithPrimaryKey withPrimaryKey( * If instead {@link WithPrimaryKey#build(char[])} is used, the key-specific encryptor is overwritten with * an encryptor built from the argument passed into {@link WithPrimaryKey#build(char[])}. * - * @param keyPair primary key + * @param primaryKeyPair primary key * @param directKeySubpackets nullable callback to modify the direct-key signatures subpackets * @param keyEncryptor nullable encryptor to protect the primary key with * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey withPrimaryKey( - PGPKeyPair keyPair, + PGPKeyPair primaryKeyPair, SignatureSubpacketsFunction directKeySubpackets, PBESecretKeyEncryptor keyEncryptor) throws PGPException { - if (!PublicKeyUtils.isSigningAlgorithm(keyPair.getPublicKey().getAlgorithm())) + if (!primaryKeyPair.getPublicKey().isMasterKey()) + { + throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); + } + + if (!PublicKeyUtils.isSigningAlgorithm(primaryKeyPair.getPublicKey().getAlgorithm())) { throw new PGPException("Primary key MUST use signing-capable algorithm."); } return primaryKeyWithDirectKeySig( - keyPair, + primaryKeyPair, subpackets -> { - subpackets.setIssuerFingerprint(true, keyPair.getPublicKey()); + subpackets.setIssuerFingerprint(true, primaryKeyPair.getPublicKey()); subpackets.setSignatureCreationTime(conf.keyCreationTime); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER); subpackets = DIRECT_KEY_SIGNATURE_SUBPACKETS.apply(subpackets); @@ -436,7 +483,7 @@ public WithPrimaryKey withPrimaryKey( * @param primaryKeyPair primary key pair * @param baseSubpackets base signature subpackets callback * @param customSubpackets user-provided signature subpackets callback - * @param encryptor key encryptor + * @param keyEncryptor key encryptor * @return builder * @throws PGPException if the key cannot be generated */ @@ -444,7 +491,7 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( PGPKeyPair primaryKeyPair, SignatureSubpacketsFunction baseSubpackets, SignatureSubpacketsFunction customSubpackets, - PBESecretKeyEncryptor encryptor) + PBESecretKeyEncryptor keyEncryptor) throws PGPException { if (baseSubpackets != null || customSubpackets != null) @@ -476,7 +523,7 @@ private WithPrimaryKey primaryKeyWithDirectKeySig( primaryKeyPair.getPrivateKey()); } - Key primaryKey = new Key(primaryKeyPair, encryptor); + Key primaryKey = new Key(primaryKeyPair, keyEncryptor); return new WithPrimaryKey(impl, conf, primaryKey); } @@ -595,7 +642,6 @@ public WithPrimaryKey addUserId( public WithPrimaryKey addEncryptionSubkey() throws PGPException { - return addEncryptionSubkey(PGPKeyPairGenerator::generateEncryptionSubkey); } @@ -671,7 +717,7 @@ public WithPrimaryKey addEncryptionSubkey(char[] passphrase) * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, - char[] passphrase) + char[] passphrase) throws PGPException { return addEncryptionSubkey(keyGenCallback, null, passphrase); @@ -694,8 +740,8 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallback, - SignatureSubpacketsFunction bindingSignatureCallback, - char[] passphrase) + SignatureSubpacketsFunction bindingSignatureCallback, + char[] passphrase) throws PGPException { PGPKeyPair subkey = keyGenCallback.generateFrom( @@ -715,16 +761,21 @@ public WithPrimaryKey addEncryptionSubkey(KeyPairGeneratorCallback keyGenCallbac * * @param encryptionSubkey encryption subkey * @param bindingSubpacketsCallback nullable callback to modify the subkey binding signature subpackets - * @param encryptor nullable encryptor to encrypt the encryption subkey + * @param keyEncryptor nullable encryptor to encrypt the encryption subkey * @return builder * @throws PGPException if the key cannot be generated */ public WithPrimaryKey addEncryptionSubkey( PGPKeyPair encryptionSubkey, SignatureSubpacketsFunction bindingSubpacketsCallback, - PBESecretKeyEncryptor encryptor) + PBESecretKeyEncryptor keyEncryptor) throws PGPException { + if (encryptionSubkey.getPublicKey().isMasterKey()) + { + throw new IllegalArgumentException("Encryption subkey MUST NOT consist of a primary key packet."); + } + if (!PublicKeyUtils.isEncryptionAlgorithm(encryptionSubkey.getPublicKey().getAlgorithm())) { throw new PGPException("Encryption key MUST use encryption-capable algorithm."); @@ -749,7 +800,7 @@ public WithPrimaryKey addEncryptionSubkey( PGPSignature bindingSig = bindingSigGen.generateCertification(primaryKey.pair.getPublicKey(), encryptionSubkey.getPublicKey()); PGPPublicKey publicSubkey = PGPPublicKey.addCertification(encryptionSubkey.getPublicKey(), bindingSig); - Key subkey = new Key(new PGPKeyPair(publicSubkey, encryptionSubkey.getPrivateKey()), encryptor); + Key subkey = new Key(new PGPKeyPair(publicSubkey, encryptionSubkey.getPrivateKey()), keyEncryptor); subkeys.add(subkey); return this; } @@ -771,14 +822,14 @@ public WithPrimaryKey addSigningSubkey() * Add a signing-capable subkey to the OpenPGP key. * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. * - * @param generatorCallback callback to specify the signing-subkey type + * @param keyGenCallback callback to specify the signing-subkey type * @return builder * @throws PGPException if the key cannot be generated */ - public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback generatorCallback) + public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback) throws PGPException { - return addSigningSubkey(generatorCallback, null); + return addSigningSubkey(keyGenCallback, null); } /** @@ -858,26 +909,31 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, * If instead {@link #build(char[])} is used, the key-specific encryptor is overwritten with an encryptor * built from the argument passed into {@link #build(char[])}. * - * @param signingKey signing subkey + * @param signingSubkey signing subkey * @param bindingSignatureCallback callback to modify the contents of the signing subkey binding signature * @param backSignatureCallback callback to modify the contents of the embedded primary key binding signature * @param keyEncryptor nullable encryptor to protect the signing subkey * @return builder * @throws PGPException if the key cannot be generated */ - public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, + public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, SignatureSubpacketsFunction bindingSignatureCallback, SignatureSubpacketsFunction backSignatureCallback, PBESecretKeyEncryptor keyEncryptor) throws PGPException { - if (!PublicKeyUtils.isSigningAlgorithm(signingKey.getPublicKey().getAlgorithm())) + if (signingSubkey.getPublicKey().isMasterKey()) + { + throw new IllegalArgumentException("Signing subkey MUST NOT consist of primary key packet."); + } + + if (!PublicKeyUtils.isSigningAlgorithm(signingSubkey.getPublicKey().getAlgorithm())) { throw new PGPException("Signing key MUST use signing-capable algorithm."); } PGPSignatureSubpacketGenerator backSigSubpackets = new PGPSignatureSubpacketGenerator(); - backSigSubpackets.setIssuerFingerprint(true, signingKey.getPublicKey()); + backSigSubpackets.setIssuerFingerprint(true, signingSubkey.getPublicKey()); backSigSubpackets.setSignatureCreationTime(conf.keyCreationTime); if (backSignatureCallback != null) { @@ -891,12 +947,12 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, bindingSigSubpackets = SIGNING_SUBKEY_SUBPACKETS.apply(bindingSigSubpackets); PGPSignatureGenerator backSigGen = new PGPSignatureGenerator( - impl.contentSignerBuilderProvider.get(signingKey.getPublicKey()), - signingKey.getPublicKey()); - backSigGen.init(PGPSignature.PRIMARYKEY_BINDING, signingKey.getPrivateKey()); + impl.contentSignerBuilderProvider.get(signingSubkey.getPublicKey()), + signingSubkey.getPublicKey()); + backSigGen.init(PGPSignature.PRIMARYKEY_BINDING, signingSubkey.getPrivateKey()); backSigGen.setHashedSubpackets(backSigSubpackets.generate()); PGPSignature backSig = backSigGen.generateCertification( - primaryKey.pair.getPublicKey(), signingKey.getPublicKey()); + primaryKey.pair.getPublicKey(), signingSubkey.getPublicKey()); try { @@ -919,11 +975,11 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingKey, bindingSigGen.setHashedSubpackets(bindingSigSubpackets.generate()); PGPSignature bindingSig = bindingSigGen.generateCertification( - primaryKey.pair.getPublicKey(), signingKey.getPublicKey()); + primaryKey.pair.getPublicKey(), signingSubkey.getPublicKey()); - PGPPublicKey signingPubKey = PGPPublicKey.addCertification(signingKey.getPublicKey(), bindingSig); - signingKey = new PGPKeyPair(signingPubKey, signingKey.getPrivateKey()); - subkeys.add(new Key(signingKey, keyEncryptor)); + PGPPublicKey signingPubKey = PGPPublicKey.addCertification(signingSubkey.getPublicKey(), bindingSig); + signingSubkey = new PGPKeyPair(signingPubKey, signingSubkey.getPrivateKey()); + subkeys.add(new Key(signingSubkey, keyEncryptor)); return this; } @@ -1000,7 +1056,6 @@ public PGPSecretKeyRing build(char[] passphrase) if (passphrase != null) { Arrays.fill(passphrase, (char) 0); - passphrase = null; } return new PGPSecretKeyRing(keys); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java index 2d5d5e1108..26b1c033a4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/AEADSecretKeyEncryptorBuilder.java @@ -2,7 +2,20 @@ import org.bouncycastle.bcpg.PublicKeyPacket; +/** + * Implementation provider for AEAD-based {@link PBESecretKeyEncryptor PBESecretKeyEncryptors}. + */ public interface AEADSecretKeyEncryptorBuilder { + /** + * Build a new {@link PBESecretKeyEncryptor} using the given passphrase. + * Note: As the AEAD protection mechanism includes the public key packet of the key into the calculation, + * if the key you want to protect is supposed to be a subkey, you need to convert it to one before + * calling this method. See {@link org.bouncycastle.openpgp.PGPKeyPair#asSubkey(KeyFingerPrintCalculator)}. + * + * @param passphrase passphrase + * @param pubKey public primary or subkey packet + * @return encryptor using AEAD + */ PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKey); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java index 6317e41a9d..a0b7c807df 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java @@ -2,7 +2,20 @@ import org.bouncycastle.bcpg.PublicKeyPacket; +/** + * Factory class for password-based secret key encryptors. + * A concrete implementation of this class can not only choose the cryptographic backend (e.g. BC, JCA/JCE), + * but also, whether to use AEAD (RFC9580) or classic CFB (RFC4880). + */ public abstract class PBESecretKeyEncryptorFactory { + + /** + * Build a new {@link PBESecretKeyEncryptor} instance from the given passphrase and public key packet. + * + * @param passphrase passphrase + * @param pubKeyPacket public-key packet of the key to protect (needed for AEAD) + * @return key encryptor + */ public abstract PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPAEADDataEncryptor.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPAEADDataEncryptor.java index a2985263f3..6d791c992e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPAEADDataEncryptor.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPAEADDataEncryptor.java @@ -4,8 +4,7 @@ /** * A data encryptor, using AEAD. * There are two different flavours of AEAD encryption used with OpenPGP. - * OpenPGP v5 AEAD is slightly different from v6 AEAD. - *

+ * LibrePGP (v5) AEAD is slightly different from RFC9580 (v6) AEAD. * {@link PGPAEADDataEncryptor} instances are generally not constructed directly, but obtained from a * {@link PGPDataEncryptorBuilder}. *

diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java index 7a00036bf3..e45a97d31c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPContentSignerBuilderProvider.java @@ -2,14 +2,28 @@ import org.bouncycastle.openpgp.PGPPublicKey; +/** + * Provider class for {@link PGPContentSignerBuilder} instances. + * Concrete implementations of this class can choose the cryptographic backend (BC, JCA/JCE). + */ public abstract class PGPContentSignerBuilderProvider { protected final int hashAlgorithmId; + /** + * Constructor. + * + * @param hashAlgorithmId ID of the hash algorithm the {@link PGPContentSignerBuilder} shall use. + */ public PGPContentSignerBuilderProvider(int hashAlgorithmId) { this.hashAlgorithmId = hashAlgorithmId; } + /** + * Return a new instance of the {@link PGPContentSignerBuilder} for the given signing key. + * @param signingKey public part of the signing key + * @return content signer builder + */ public abstract PGPContentSignerBuilder get(PGPPublicKey signingKey); } From 20d0aa1be4bcdd49e59ce5a0bd375308c95c2ee0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 17:22:01 +0200 Subject: [PATCH 44/53] Fix key packet type enforcing and add test --- .../openpgp/api/OpenPGPV6KeyGenerator.java | 9 +++-- .../api/test/OpenPGPV6KeyGeneratorTest.java | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index e81b040985..7d74272c14 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -5,6 +5,7 @@ import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyPacket; import org.bouncycastle.bcpg.PublicKeyUtils; +import org.bouncycastle.bcpg.PublicSubkeyPacket; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SignatureSubpacketTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; @@ -296,7 +297,7 @@ public PGPSecretKeyRing signOnlyKey( SignatureSubpacketsFunction userSubpackets) throws PGPException { - if (!primaryKeyPair.getPublicKey().isMasterKey()) + if (primaryKeyPair.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket) { throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); } @@ -449,7 +450,7 @@ public WithPrimaryKey withPrimaryKey( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - if (!primaryKeyPair.getPublicKey().isMasterKey()) + if (primaryKeyPair.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket) { throw new IllegalArgumentException("Primary key MUST NOT consist of subkey packet."); } @@ -771,7 +772,7 @@ public WithPrimaryKey addEncryptionSubkey( PBESecretKeyEncryptor keyEncryptor) throws PGPException { - if (encryptionSubkey.getPublicKey().isMasterKey()) + if (!(encryptionSubkey.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket)) { throw new IllegalArgumentException("Encryption subkey MUST NOT consist of a primary key packet."); } @@ -922,7 +923,7 @@ public WithPrimaryKey addSigningSubkey(PGPKeyPair signingSubkey, PBESecretKeyEncryptor keyEncryptor) throws PGPException { - if (signingSubkey.getPublicKey().isMasterKey()) + if (!(signingSubkey.getPublicKey().getPublicKeyPacket() instanceof PublicSubkeyPacket)) { throw new IllegalArgumentException("Signing subkey MUST NOT consist of primary key packet."); } diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index 2182c0d0a8..b2853a1176 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -15,12 +15,15 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; +import org.bouncycastle.openpgp.api.KeyPairGeneratorCallback; import org.bouncycastle.openpgp.api.OpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.bc.BcOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.api.jcajce.JcaOpenPGPV6KeyGenerator; import org.bouncycastle.openpgp.operator.PGPKeyPairGenerator; +import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPairGeneratorProvider; import org.bouncycastle.openpgp.test.AbstractPgpKeyPairTest; import java.io.IOException; @@ -79,6 +82,7 @@ private void performTests(APIProvider apiProvider) testGenerateClassicKeyBaseCase(apiProvider); testGenerateProtectedTypicalKey(apiProvider); + testEnforcesPrimaryOrSubkeyType(apiProvider); } private void testGenerateSignOnlyKeyBaseCase(APIProvider apiProvider) @@ -334,6 +338,39 @@ private void testGenerateCustomKey(APIProvider apiProvider) encryptionSubkey.extractPrivateKey(keyDecryptorBuilder.build("encryption-key-passphrase".toCharArray()))); } + private void testEnforcesPrimaryOrSubkeyType(APIProvider apiProvider) + throws PGPException + { + isNotNull(testException( + "Primary key MUST NOT consist of subkey packet.", + "IllegalArgumentException", + () -> + apiProvider.getKeyGenerator().withPrimaryKey((KeyPairGeneratorCallback) keyGenCallback -> + keyGenCallback.generateSigningSubkey() + .asSubkey(new BcKeyFingerprintCalculator())) // subkey as primary key is illegal + )); + + isNotNull(testException( + "Encryption subkey MUST NOT consist of a primary key packet.", + "IllegalArgumentException", + () -> + apiProvider.getKeyGenerator().withPrimaryKey() + .addEncryptionSubkey(new BcPGPKeyPairGeneratorProvider() + .get(6, new Date()) + .generateX25519KeyPair(), null, null) // primary key as subkey is illegal + )); + + isNotNull(testException( + "Signing subkey MUST NOT consist of primary key packet.", + "IllegalArgumentException", + () -> + apiProvider.getKeyGenerator().withPrimaryKey() + .addSigningSubkey(new BcPGPKeyPairGeneratorProvider() + .get(6, new Date()) + .generateEd25519KeyPair(), null, null, null) // primary key as subkey is illegal + )); + } + private abstract static class APIProvider { public OpenPGPV6KeyGenerator getKeyGenerator() From bcd59fdd602ca9f5261547505ca432e4fa5b86e1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 21 Oct 2024 17:38:36 +0200 Subject: [PATCH 45/53] PGPKeyPairGenerator: convert subkeys --- .../java/org/bouncycastle/openpgp/PGPKeyPair.java | 5 +++++ .../openpgp/operator/PGPKeyPairGenerator.java | 11 ++++++++--- .../operator/bc/BcPGPKeyPairGeneratorProvider.java | 2 +- .../jcajce/JcaPGPKeyPairGeneratorProvider.java | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java index 8c61092364..c29b4abaa4 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPKeyPair.java @@ -69,6 +69,11 @@ public PGPPrivateKey getPrivateKey() public PGPKeyPair asSubkey(KeyFingerPrintCalculator fingerPrintCalculator) throws PGPException { + if (pub.getPublicKeyPacket() instanceof PublicSubkeyPacket) + { + return this; // is already subkey + } + PublicSubkeyPacket pubSubPkt = new PublicSubkeyPacket( pub.getVersion(), pub.getAlgorithm(), diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java index 2a15ba2d36..c1765861d0 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PGPKeyPairGenerator.java @@ -14,6 +14,7 @@ public abstract class PGPKeyPairGenerator protected final Date creationTime; protected final int version; protected SecureRandom random; + protected final KeyFingerPrintCalculator fingerPrintCalculator; /** * Create an instance of the key pair generator. @@ -23,11 +24,15 @@ public abstract class PGPKeyPairGenerator * @param creationTime key creation time * @param random secure random number generator */ - public PGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) + public PGPKeyPairGenerator(int version, + Date creationTime, + SecureRandom random, + KeyFingerPrintCalculator fingerPrintCalculator) { this.creationTime = new Date((creationTime.getTime() / 1000) * 1000); this.version = version; this.random = random; + this.fingerPrintCalculator = fingerPrintCalculator; } /** @@ -53,7 +58,7 @@ public PGPKeyPair generatePrimaryKey() public PGPKeyPair generateEncryptionSubkey() throws PGPException { - return generateX25519KeyPair(); + return generateX25519KeyPair().asSubkey(fingerPrintCalculator); } /** @@ -66,7 +71,7 @@ public PGPKeyPair generateEncryptionSubkey() public PGPKeyPair generateSigningSubkey() throws PGPException { - return generateEd25519KeyPair(); + return generateEd25519KeyPair().asSubkey(fingerPrintCalculator); } /** diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java index fba86093b1..19defd425c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPGPKeyPairGeneratorProvider.java @@ -46,7 +46,7 @@ private static class BcPGPKeyPairGenerator public BcPGPKeyPairGenerator(int version, Date creationTime, SecureRandom random) { - super(version, creationTime, random); + super(version, creationTime, random, new BcKeyFingerprintCalculator()); } @Override diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java index cd728f879b..5632f45dca 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaPGPKeyPairGeneratorProvider.java @@ -80,7 +80,7 @@ private static class JcaPGPKeyPairGenerator public JcaPGPKeyPairGenerator(int version, Date creationTime, OperatorHelper helper, SecureRandom random) { - super(version, creationTime, random); + super(version, creationTime, random, new JcaKeyFingerprintCalculator()); this.helper = helper; } From 314aaa38ff79c55faaf0a1a91071f0950d55d860 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Oct 2024 10:46:37 +0100 Subject: [PATCH 46/53] Add api classpaths to jdk-9 module-info --- pg/src/main/jdk1.9/module-info.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pg/src/main/jdk1.9/module-info.java b/pg/src/main/jdk1.9/module-info.java index b4b68b6d85..622ea41135 100644 --- a/pg/src/main/jdk1.9/module-info.java +++ b/pg/src/main/jdk1.9/module-info.java @@ -12,6 +12,9 @@ exports org.bouncycastle.gpg.keybox; exports org.bouncycastle.gpg.keybox.bc; exports org.bouncycastle.gpg.keybox.jcajce; + exports org.bouncycastle.openpgp.api; + exports org.bouncycastle.openpgp.api.bc; + exports org.bouncycastle.openpgp.api.jcajce; exports org.bouncycastle.openpgp.bc; exports org.bouncycastle.openpgp.examples; exports org.bouncycastle.openpgp.jcajce; From 06f663f4271314b64a63329ea9c4922d0cb2b7aa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 28 Oct 2024 10:50:05 +0100 Subject: [PATCH 47/53] Add info about primary-key backsig to OpenPGPV6KeyGenerator javadoc --- .../org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java index 7d74272c14..950f02b818 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPV6KeyGenerator.java @@ -808,6 +808,7 @@ public WithPrimaryKey addEncryptionSubkey( /** * Add a signing-capable subkey to the OpenPGP key. + * The binding signature will contain a primary-key back-signature. * See {@link PGPKeyPairGenerator#generateSigningSubkey()} for the key type. * * @return builder @@ -821,6 +822,7 @@ public WithPrimaryKey addSigningSubkey() /** * Add a signing-capable subkey to the OpenPGP key. + * The binding signature will contain a primary-key back-signature. * The key type can be specified by overriding {@link KeyPairGeneratorCallback}. * * @param keyGenCallback callback to specify the signing-subkey type @@ -836,6 +838,7 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback) /** * Add a signing-capable subkey to the OpenPGP key. * See {@link PGPKeyPairGenerator#generateSigningSubkey()} for the key type. + * The binding signature will contain a primary-key back-signature. * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved * using {@link #build()}. * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument @@ -854,6 +857,7 @@ public WithPrimaryKey addSigningSubkey(char[] passphrase) /** * Add a signing-capable subkey to the OpenPGP key. * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * The binding signature will contain a primary-key back-signature. * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved * using {@link #build()}. * If instead {@link #build(char[])} is used, the key-specific passphrase is overwritten with the argument @@ -874,6 +878,7 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, /** * Add a signing-capable subkey to the OpenPGP key. * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * The binding signature will contain a primary-key back-signature. * The contents of the binding signature(s) can be modified by overriding the respective * {@link SignatureSubpacketsFunction} instances. * IMPORTANT: The custom subkey passphrase will only be used, if in the final step the key is retrieved @@ -903,6 +908,7 @@ public WithPrimaryKey addSigningSubkey(KeyPairGeneratorCallback keyGenCallback, /** * Add a signing-capable subkey to the OpenPGP key. * The signing-key type can be specified by overriding the {@link KeyPairGeneratorCallback}. + * The binding signature will contain a primary-key back-signature. * The contents of the binding signature(s) can be modified by overriding the respective * {@link SignatureSubpacketsFunction} instances. * IMPORTANT: The custom key encryptor will only be used, if in the final step the key is retrieved From deeb22d5290495ff2cef0d48cf3f6fb2fba64af3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Oct 2024 12:24:13 +0200 Subject: [PATCH 48/53] Add test for reencryption using AEAD --- .../test/AEADProtectedPGPSecretKeyTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java index 2d2429bcb5..c9eb712af7 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java @@ -31,9 +31,12 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair; import org.bouncycastle.openpgp.operator.jcajce.JcaAEADSecretKeyEncryptorBuilder; @@ -65,6 +68,8 @@ public void performTest() testUnlockKeyWithWrongPassphraseBc(); testUnlockKeyWithWrongPassphraseJca(); + + reencryptKey(); } private void unlockTestVector() @@ -360,6 +365,58 @@ private void lockUnlockKeyJca( keyPair.getPrivateKey().getPrivateKeyDataPacket().getEncoded(), dec.getPrivateKeyDataPacket().getEncoded()); } + private void reencryptKey() throws PGPException { + reencryptKeyBc(); + reencryptKeyJca(); + } + + private void reencryptKeyJca() + { + + } + + private void reencryptKeyBc() + throws PGPException + { + Ed25519KeyPairGenerator gen = new Ed25519KeyPairGenerator(); + gen.init(new Ed25519KeyGenerationParameters(new SecureRandom())); + AsymmetricCipherKeyPair kp = gen.generateKeyPair(); + Date creationTime = currentTimeRounded(); + String passphrase = "recycle"; + PGPKeyPair keyPair = new BcPGPKeyPair(PublicKeyPacket.VERSION_6, PublicKeyAlgorithmTags.Ed25519, kp, creationTime); + + PBESecretKeyEncryptor cfbEncBuilder = new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128) + .build(passphrase.toCharArray()); + PGPDigestCalculatorProvider digestProv = new BcPGPDigestCalculatorProvider(); + + // Encrypt key using CFB mode + PGPSecretKey cfbEncKey = new PGPSecretKey( + keyPair.getPrivateKey(), + keyPair.getPublicKey(), + digestProv.get(HashAlgorithmTags.SHA1), + true, + cfbEncBuilder); + + PBESecretKeyDecryptor cfbDecryptor = new BcPBESecretKeyDecryptorBuilder(digestProv) + .build(passphrase.toCharArray()); + + BcAEADSecretKeyEncryptorBuilder aeadEncBuilder = new BcAEADSecretKeyEncryptorBuilder( + AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_128, + S2K.Argon2Params.memoryConstrainedParameters()); + + // Reencrypt key using AEAD + PGPSecretKey aeadEncKey = PGPSecretKey.copyWithNewPassword( + cfbEncKey, + cfbDecryptor, + aeadEncBuilder.build( + passphrase.toCharArray(), + cfbEncKey.getPublicKey().getPublicKeyPacket())); + + PBESecretKeyDecryptor aeadDecryptor = new BcPBESecretKeyDecryptorBuilder(digestProv) + .build(passphrase.toCharArray()); + isNotNull(aeadEncKey.extractPrivateKey(aeadDecryptor)); + } + public static void main(String[] args) { runTest(new AEADProtectedPGPSecretKeyTest()); From 6f5af4431ff1fc8770def655556489f67401003e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Oct 2024 12:59:50 +0200 Subject: [PATCH 49/53] PGPSecretKey.copyWithNewPassword: Fix S2KUsage if newKeyEncryptor is using AEAD --- .../java/org/bouncycastle/openpgp/PGPSecretKey.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java index febf9c4204..bbf298876a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSecretKey.java @@ -999,7 +999,15 @@ public static PGPSecretKey copyWithNewPassword( SecretKeyPacket secret; - secret = generateSecretKeyPacket(!(key.secret instanceof SecretSubkeyPacket), key.secret.getPublicKeyPacket(), newEncAlgorithm, s2kUsage, s2k, iv, keyData); + if (newKeyEncryptor.getAeadAlgorithm() > 0) + { + s2kUsage = SecretKeyPacket.USAGE_AEAD; + secret = generateSecretKeyPacket(!(key.secret instanceof SecretSubkeyPacket), key.secret.getPublicKeyPacket(), newEncAlgorithm, newKeyEncryptor.getAeadAlgorithm(), s2kUsage, s2k, iv, keyData); + } + else + { + secret = generateSecretKeyPacket(!(key.secret instanceof SecretSubkeyPacket), key.secret.getPublicKeyPacket(), newEncAlgorithm, s2kUsage, s2k, iv, keyData); + } return new PGPSecretKey(secret, key.pub); } From 8059076c4d95b6a4608f3b52ebca81acbd9b1373 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Nov 2024 21:59:13 +0100 Subject: [PATCH 50/53] Add more tests --- .../api/test/OpenPGPV6KeyGeneratorTest.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java index b2853a1176..161ff338ce 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java @@ -82,6 +82,9 @@ private void performTests(APIProvider apiProvider) testGenerateClassicKeyBaseCase(apiProvider); testGenerateProtectedTypicalKey(apiProvider); + testGenerateEd25519x25519Key(apiProvider); + testGenerateEd448x448Key(apiProvider); + testEnforcesPrimaryOrSubkeyType(apiProvider); } @@ -236,6 +239,100 @@ private void testGenerateProtectedTypicalKey(APIProvider apiProvider) } } + private void testGenerateEd25519x25519Key(APIProvider apiProvider) + throws PGPException + { + Date currentTime = currentTimeRounded(); + String userId = "Foo "; + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); + + PGPSecretKeyRing secretKey = generator.ed25519x25519Key(userId, null); + + Iterator iterator = secretKey.getSecretKeys(); + PGPSecretKey primaryKey = iterator.next(); + PGPSecretKey signingSubkey = iterator.next(); + PGPSecretKey encryptionSubkey = iterator.next(); + isFalse("Unexpected key", iterator.hasNext()); + + isEquals(PublicKeyAlgorithmTags.Ed25519, primaryKey.getPublicKey().getAlgorithm()); + Iterator keySignatures = primaryKey.getPublicKey().getKeySignatures(); + PGPSignature directKeySignature = keySignatures.next(); + isFalse(keySignatures.hasNext()); + PGPSignatureSubpacketVector hashedSubpackets = directKeySignature.getHashedSubPackets(); + isEquals(KeyFlags.CERTIFY_OTHER, hashedSubpackets.getKeyFlags()); + + Iterator userIds = primaryKey.getUserIDs(); + isEquals(userId, userIds.next()); + isFalse(userIds.hasNext()); + Iterator userIdSignatures = primaryKey.getPublicKey().getSignaturesForID(userId); + PGPSignature userIdSig = userIdSignatures.next(); + isFalse(userIdSignatures.hasNext()); + isEquals(PGPSignature.POSITIVE_CERTIFICATION, userIdSig.getSignatureType()); + + isEquals(PublicKeyAlgorithmTags.Ed25519, signingSubkey.getPublicKey().getAlgorithm()); + Iterator signingSubkeySigs = signingSubkey.getPublicKey().getKeySignatures(); + PGPSignature signingSubkeySig = signingSubkeySigs.next(); + isFalse(signingSubkeySigs.hasNext()); + isEquals(PGPSignature.SUBKEY_BINDING, signingSubkeySig.getSignatureType()); + hashedSubpackets = signingSubkeySig.getHashedSubPackets(); + isEquals(KeyFlags.SIGN_DATA, hashedSubpackets.getKeyFlags()); + + isEquals(PublicKeyAlgorithmTags.X25519, encryptionSubkey.getPublicKey().getAlgorithm()); + Iterator encryptionSubkeySigs = encryptionSubkey.getPublicKey().getKeySignatures(); + PGPSignature encryptionSubkeySig = encryptionSubkeySigs.next(); + isFalse(encryptionSubkeySigs.hasNext()); + isEquals(PGPSignature.SUBKEY_BINDING, encryptionSubkeySig.getSignatureType()); + hashedSubpackets = encryptionSubkeySig.getHashedSubPackets(); + isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags()); + } + + private void testGenerateEd448x448Key(APIProvider apiProvider) + throws PGPException + { + Date currentTime = currentTimeRounded(); + String userId = "Foo "; + OpenPGPV6KeyGenerator generator = apiProvider.getKeyGenerator(currentTime); + + PGPSecretKeyRing secretKey = generator.ed448x448Key(userId, null); + + Iterator iterator = secretKey.getSecretKeys(); + PGPSecretKey primaryKey = iterator.next(); + PGPSecretKey signingSubkey = iterator.next(); + PGPSecretKey encryptionSubkey = iterator.next(); + isFalse("Unexpected key", iterator.hasNext()); + + isEquals(PublicKeyAlgorithmTags.Ed448, primaryKey.getPublicKey().getAlgorithm()); + Iterator keySignatures = primaryKey.getPublicKey().getKeySignatures(); + PGPSignature directKeySignature = keySignatures.next(); + isFalse(keySignatures.hasNext()); + PGPSignatureSubpacketVector hashedSubpackets = directKeySignature.getHashedSubPackets(); + isEquals(KeyFlags.CERTIFY_OTHER, hashedSubpackets.getKeyFlags()); + + Iterator userIds = primaryKey.getUserIDs(); + isEquals(userId, userIds.next()); + isFalse(userIds.hasNext()); + Iterator userIdSignatures = primaryKey.getPublicKey().getSignaturesForID(userId); + PGPSignature userIdSig = userIdSignatures.next(); + isFalse(userIdSignatures.hasNext()); + isEquals(PGPSignature.POSITIVE_CERTIFICATION, userIdSig.getSignatureType()); + + isEquals(PublicKeyAlgorithmTags.Ed448, signingSubkey.getPublicKey().getAlgorithm()); + Iterator signingSubkeySigs = signingSubkey.getPublicKey().getKeySignatures(); + PGPSignature signingSubkeySig = signingSubkeySigs.next(); + isFalse(signingSubkeySigs.hasNext()); + isEquals(PGPSignature.SUBKEY_BINDING, signingSubkeySig.getSignatureType()); + hashedSubpackets = signingSubkeySig.getHashedSubPackets(); + isEquals(KeyFlags.SIGN_DATA, hashedSubpackets.getKeyFlags()); + + isEquals(PublicKeyAlgorithmTags.X448, encryptionSubkey.getPublicKey().getAlgorithm()); + Iterator encryptionSubkeySigs = encryptionSubkey.getPublicKey().getKeySignatures(); + PGPSignature encryptionSubkeySig = encryptionSubkeySigs.next(); + isFalse(encryptionSubkeySigs.hasNext()); + isEquals(PGPSignature.SUBKEY_BINDING, encryptionSubkeySig.getSignatureType()); + hashedSubpackets = encryptionSubkeySig.getHashedSubPackets(); + isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags()); + } + private void testGenerateCustomKey(APIProvider apiProvider) throws PGPException { From 88936f268cae58c88ad978ec3adfad737a1b5b6c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Nov 2024 22:55:02 +0100 Subject: [PATCH 51/53] Relax Argon2 parameters to be more memory friendly --- .../operator/bc/BcAEADSecretKeyEncryptorFactory.java | 6 +++--- .../operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java index 013752e331..594da40247 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java @@ -12,8 +12,8 @@ * a key-encryption-key using {@link org.bouncycastle.bcpg.S2K#ARGON_2} S2K and apply * that key using {@link org.bouncycastle.bcpg.SecretKeyPacket#USAGE_AEAD}. *

- * This particular factory uses OCB + AES256 for secret key protection and requires 2GiB of RAM - * for the Argon2 key derivation (see {@link S2K.Argon2Params#universallyRecommendedParameters()}). + * This particular factory uses OCB + AES256 for secret key protection and requires 64MiB of RAM + * for the Argon2 key derivation (see {@link S2K.Argon2Params#memoryConstrainedParameters()}). */ public class BcAEADSecretKeyEncryptorFactory extends PBESecretKeyEncryptorFactory @@ -28,7 +28,7 @@ public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPack return new BcAEADSecretKeyEncryptorBuilder( AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_256, - S2K.Argon2Params.universallyRecommendedParameters()) + S2K.Argon2Params.memoryConstrainedParameters()) .build(passphrase, pubKeyPacket); } } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java index 02bafe954e..fc1036fc3e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java @@ -15,7 +15,7 @@ public class JcaAEADSecretKeyEncryptorFactory private JcaAEADSecretKeyEncryptorBuilder builder = new JcaAEADSecretKeyEncryptorBuilder( AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_256, - S2K.Argon2Params.universallyRecommendedParameters()); + S2K.Argon2Params.memoryConstrainedParameters()); public JcaAEADSecretKeyEncryptorFactory setProvider(Provider provider) { From 3620a948abd8374b91dd4a081d73ecbc5703452c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Nov 2024 12:47:52 +0100 Subject: [PATCH 52/53] Make PBESecretKeyEncryptorFactory an Interface --- .../openpgp/operator/PBESecretKeyEncryptorFactory.java | 4 ++-- .../openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java | 4 ++-- .../openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java | 2 +- .../operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java | 2 +- .../operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java index a0b7c807df..ff18584612 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/PBESecretKeyEncryptorFactory.java @@ -7,7 +7,7 @@ * A concrete implementation of this class can not only choose the cryptographic backend (e.g. BC, JCA/JCE), * but also, whether to use AEAD (RFC9580) or classic CFB (RFC4880). */ -public abstract class PBESecretKeyEncryptorFactory +public interface PBESecretKeyEncryptorFactory { /** @@ -17,5 +17,5 @@ public abstract class PBESecretKeyEncryptorFactory * @param pubKeyPacket public-key packet of the key to protect (needed for AEAD) * @return key encryptor */ - public abstract PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket); + PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java index 594da40247..3fa0dc0b27 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADSecretKeyEncryptorFactory.java @@ -16,7 +16,7 @@ * for the Argon2 key derivation (see {@link S2K.Argon2Params#memoryConstrainedParameters()}). */ public class BcAEADSecretKeyEncryptorFactory - extends PBESecretKeyEncryptorFactory + implements PBESecretKeyEncryptorFactory { @Override public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) @@ -25,7 +25,7 @@ public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPack { return null; } - return new BcAEADSecretKeyEncryptorBuilder( + return new org.bouncycastle.openpgp.operator.bc.BcAEADSecretKeyEncryptorBuilder( AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_256, S2K.Argon2Params.memoryConstrainedParameters()) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java index dc2dbb5440..7b86fb3f34 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcCFBSecretKeyEncryptorFactory.java @@ -17,7 +17,7 @@ * and uses AES256 for secret key protection. */ public class BcCFBSecretKeyEncryptorFactory - extends PBESecretKeyEncryptorFactory + implements PBESecretKeyEncryptorFactory { @Override public PBESecretKeyEncryptor build(char[] passphrase, PublicKeyPacket pubKeyPacket) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java index fc1036fc3e..199c2af3b1 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaAEADSecretKeyEncryptorFactory.java @@ -10,7 +10,7 @@ import java.security.Provider; public class JcaAEADSecretKeyEncryptorFactory - extends PBESecretKeyEncryptorFactory + implements PBESecretKeyEncryptorFactory { private JcaAEADSecretKeyEncryptorBuilder builder = new JcaAEADSecretKeyEncryptorBuilder( AEADAlgorithmTags.OCB, diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java index d2bbd19a1c..1a11c4e86b 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcaCFBSecretKeyEncryptorFactory.java @@ -10,7 +10,7 @@ import java.security.Provider; public class JcaCFBSecretKeyEncryptorFactory - extends PBESecretKeyEncryptorFactory + implements PBESecretKeyEncryptorFactory { private JcaPGPDigestCalculatorProviderBuilder digestCalcProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder(); From 72d6228febe82868fc28f41db0839aa81144a0e2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Nov 2024 11:37:13 +0100 Subject: [PATCH 53/53] Implement AEADProtectedPGPSecretKeyTest.reencryptKeyJca() --- .../test/AEADProtectedPGPSecretKeyTest.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java index c9eb712af7..df48813c75 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/AEADProtectedPGPSecretKeyTest.java @@ -20,6 +20,7 @@ import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator; import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -45,6 +46,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.util.Strings; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.util.encoders.Hex; public class AEADProtectedPGPSecretKeyTest @@ -365,14 +367,57 @@ private void lockUnlockKeyJca( keyPair.getPrivateKey().getPrivateKeyDataPacket().getEncoded(), dec.getPrivateKeyDataPacket().getEncoded()); } - private void reencryptKey() throws PGPException { + private void reencryptKey() + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException + { reencryptKeyBc(); reencryptKeyJca(); } private void reencryptKeyJca() + throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, PGPException { + BouncyCastleProvider prov = new BouncyCastleProvider(); + KeyPairGenerator eddsaGen = KeyPairGenerator.getInstance("EdDSA", prov); + eddsaGen.initialize(new ECNamedCurveGenParameterSpec("ed25519")); + KeyPair kp = eddsaGen.generateKeyPair(); + Date creationTime = currentTimeRounded(); + String passphrase = "recycle"; + + PGPKeyPair keyPair = new JcaPGPKeyPair(PublicKeyPacket.VERSION_6, PublicKeyAlgorithmTags.Ed25519, kp, creationTime); + PBESecretKeyEncryptor cfbEncBuilder = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_128) + .setProvider(prov) + .setSecureRandom(CryptoServicesRegistrar.getSecureRandom()) + .build(passphrase.toCharArray()); + PGPDigestCalculatorProvider digestProv = new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(prov) + .build(); + + // Encrypt key using CFB mode + PGPSecretKey cfbEncKey = new PGPSecretKey( + keyPair.getPrivateKey(), + keyPair.getPublicKey(), + digestProv.get(HashAlgorithmTags.SHA1), + true, + cfbEncBuilder); + + PBESecretKeyDecryptor cfbDecryptor = new JcePBESecretKeyDecryptorBuilder(digestProv) + .setProvider(prov) + .build(passphrase.toCharArray()); + + JcaAEADSecretKeyEncryptorBuilder aeadEncBuilder = new JcaAEADSecretKeyEncryptorBuilder( + AEADAlgorithmTags.OCB, SymmetricKeyAlgorithmTags.AES_128, S2K.Argon2Params.memoryConstrainedParameters()) + .setProvider(prov); + + PGPSecretKey aeadEncKey = PGPSecretKey.copyWithNewPassword( + cfbEncKey, + cfbDecryptor, + aeadEncBuilder.build(passphrase.toCharArray(), cfbEncKey.getPublicKey().getPublicKeyPacket())); + PBESecretKeyDecryptor aeadDecryptor = new JcePBESecretKeyDecryptorBuilder(digestProv) + .setProvider(prov) + .build(passphrase.toCharArray()); + isNotNull(aeadEncKey.extractPrivateKey(aeadDecryptor)); } private void reencryptKeyBc()