diff --git a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java index 612320cd4d..aaf3e2299c 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java @@ -235,13 +235,99 @@ public WithPrimaryKey signOnlyKey() @Override public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) { - subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA); return subpackets; } })); } + /** + * Generate an OpenPGP key based on a single, multipurpose RSA key of the given strength. + * The key will carry a direct-key signature containing default preferences and flags. + * If the passed in userId is non-null, it will be added to the key. + * + * @param bitStrength strength of the RSA key in bits (recommended values: 3072 and above). + * @param userId optional user-id + * @return builder + * @throws PGPException if the key cannot be generated. + */ + public WithPrimaryKey singletonRSAKey(int bitStrength, String userId) + throws PGPException + { + WithPrimaryKey builder = withPrimaryKey( + new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateRsaKeyPair(bitStrength); + } + }, + SignatureParameters.Callback.Util.modifyHashedSubpackets( + new SignatureSubpacketsFunction() + { + @Override + public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets) + { + subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS); + subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER + | KeyFlags.SIGN_DATA + | KeyFlags.ENCRYPT_STORAGE + | KeyFlags.ENCRYPT_COMMS); + return subpackets; + } + } + ) + ); + + if (userId != null) + { + builder.addUserId(userId); + } + + return builder; + } + + /** + * Generate an OpenPGP key composed of 3 individual component keys based on RSA of the given strength in bits. + * The primary key will be used to certify third-party keys. + * A subkey is used for signing and a third subkey is used for encryption of messages both for storage and + * communications. + * The primary key will carry a direct-key signature containing default preferences and flags. + * The subkeys will be bound with subkey binding signatures. + * If the passed in userId is non-null, it will be added to the key. + * + * @param bitStrength strength of the keys in bits (recommended values: 3072 and above) + * @param userId optional user-id + * @return builder + * @throws PGPException if the key cannot be generated + */ + public WithPrimaryKey compositeRSAKey(int bitStrength, String userId) + throws PGPException + { + KeyPairGeneratorCallback generatorCallback = new KeyPairGeneratorCallback() + { + @Override + public PGPKeyPair generateFrom(PGPKeyPairGenerator generator) + throws PGPException + { + return generator.generateRsaKeyPair(bitStrength); + } + }; + + WithPrimaryKey builder = withPrimaryKey(generatorCallback) + .addSigningSubkey(generatorCallback) + .addEncryptionSubkey(generatorCallback); + + if (userId != null) + { + builder.addUserId(userId); + } + + return builder; + } + /** * Generate an OpenPGP key with a certification-capable primary key. * See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type 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 87a3dd5b8b..d8bc32966c 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 @@ -60,6 +60,9 @@ protected void performTestWith(OpenPGPApi api) testGenerateEd25519x25519Key(api); testGenerateEd448x448Key(api); + testGenerateSingletonRSAKey(api); + testGenerateCompositeRSAKey(api); + testEnforcesPrimaryOrSubkeyType(api); testGenerateKeyWithoutSignatures(api); } @@ -320,6 +323,59 @@ private void testGenerateEd448x448Key(OpenPGPApi api) isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags()); } + private void testGenerateSingletonRSAKey(OpenPGPApi api) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPKeyGenerator generator = api.generateKey(creationTime, false); + + OpenPGPKey key = generator.singletonRSAKey(4096, "Alice ") + .build(); + + isEquals("Singleton RSA key MUST consist of only a single primary key.", 1, key.getKeys().size()); + OpenPGPCertificate.OpenPGPComponentKey primaryKey = key.getPrimaryKey(); + isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, primaryKey.getAlgorithm()); + isEquals("Primary key MUST have a strength of 4096 bits.", 4096, primaryKey.getPGPPublicKey().getBitStrength()); + + isEquals("The primary key MUST be the certification key", primaryKey, key.getCertificationKeys().get(0)); + isEquals("The primary key MUST be the encryption key", primaryKey, key.getEncryptionKeys().get(0)); + isEquals("The primary key MUST be the signing key", primaryKey, key.getSigningKeys().get(0)); + + isNotNull(key.getUserId("Alice ")); + } + + private void testGenerateCompositeRSAKey(OpenPGPApi api) + throws PGPException + { + Date creationTime = currentTimeRounded(); + OpenPGPKeyGenerator generator = api.generateKey(creationTime, false); + + OpenPGPKey key = generator.compositeRSAKey(4096, "Alice ") + .build(); + + isEquals("The composite RSA key MUST consist of 3 component keys", 3, key.getKeys().size()); + + OpenPGPCertificate.OpenPGPComponentKey primaryKey = key.getPrimaryKey(); + isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, primaryKey.getAlgorithm()); + isEquals("Primary key MUST have a strength of 4096 bits.", 4096, primaryKey.getPGPPublicKey().getBitStrength()); + isEquals("There MUST be only one certification key", 1, key.getCertificationKeys().size()); + isEquals("The primary key MUST be the certification key", primaryKey, key.getCertificationKeys().get(0)); + + isEquals("There MUST be only one signing key", 1, key.getSigningKeys().size()); + OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0); + isEquals("Signing key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, signingKey.getAlgorithm()); + isEquals("Signing key MUST have a strength of 4096 bits.", 4096, signingKey.getPGPPublicKey().getBitStrength()); + isFalse("The signing key MUST NOT be the primary key", primaryKey.equals(signingKey)); + + isEquals("There MUST be only one encryption key", 1, key.getEncryptionKeys().size()); + OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getEncryptionKeys().get(0); + isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, encryptionKey.getAlgorithm()); + isEquals("Encryption key MUST have a strength of 4096 bits.", 4096, encryptionKey.getPGPPublicKey().getBitStrength()); + isFalse("The encryption key MUST NOT be the primary key", primaryKey.equals(encryptionKey)); + + isFalse("The signing key MUST NOT be the encryption key", signingKey.equals(encryptionKey)); + } + private void testGenerateCustomKey(OpenPGPApi api) throws PGPException {