Skip to content

Commit 7b094ad

Browse files
committed
Merge branch 'pgpainless-rsaKeyGeneration'
2 parents 13ce3ad + a1553af commit 7b094ad

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPKeyGenerator.java

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,13 +235,99 @@ public WithPrimaryKey signOnlyKey()
235235
@Override
236236
public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets)
237237
{
238-
subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS);
239238
subpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA);
240239
return subpackets;
241240
}
242241
}));
243242
}
244243

244+
/**
245+
* Generate an OpenPGP key based on a single, multipurpose RSA key of the given strength.
246+
* The key will carry a direct-key signature containing default preferences and flags.
247+
* If the passed in userId is non-null, it will be added to the key.
248+
*
249+
* @param bitStrength strength of the RSA key in bits (recommended values: 3072 and above).
250+
* @param userId optional user-id
251+
* @return builder
252+
* @throws PGPException if the key cannot be generated.
253+
*/
254+
public WithPrimaryKey singletonRSAKey(int bitStrength, String userId)
255+
throws PGPException
256+
{
257+
WithPrimaryKey builder = withPrimaryKey(
258+
new KeyPairGeneratorCallback()
259+
{
260+
@Override
261+
public PGPKeyPair generateFrom(PGPKeyPairGenerator generator)
262+
throws PGPException
263+
{
264+
return generator.generateRsaKeyPair(bitStrength);
265+
}
266+
},
267+
SignatureParameters.Callback.Util.modifyHashedSubpackets(
268+
new SignatureSubpacketsFunction()
269+
{
270+
@Override
271+
public PGPSignatureSubpacketGenerator apply(PGPSignatureSubpacketGenerator subpackets)
272+
{
273+
subpackets.removePacketsOfType(SignatureSubpacketTags.KEY_FLAGS);
274+
subpackets.setKeyFlags(KeyFlags.CERTIFY_OTHER
275+
| KeyFlags.SIGN_DATA
276+
| KeyFlags.ENCRYPT_STORAGE
277+
| KeyFlags.ENCRYPT_COMMS);
278+
return subpackets;
279+
}
280+
}
281+
)
282+
);
283+
284+
if (userId != null)
285+
{
286+
builder.addUserId(userId);
287+
}
288+
289+
return builder;
290+
}
291+
292+
/**
293+
* Generate an OpenPGP key composed of 3 individual component keys based on RSA of the given strength in bits.
294+
* The primary key will be used to certify third-party keys.
295+
* A subkey is used for signing and a third subkey is used for encryption of messages both for storage and
296+
* communications.
297+
* The primary key will carry a direct-key signature containing default preferences and flags.
298+
* The subkeys will be bound with subkey binding signatures.
299+
* If the passed in userId is non-null, it will be added to the key.
300+
*
301+
* @param bitStrength strength of the keys in bits (recommended values: 3072 and above)
302+
* @param userId optional user-id
303+
* @return builder
304+
* @throws PGPException if the key cannot be generated
305+
*/
306+
public WithPrimaryKey compositeRSAKey(int bitStrength, String userId)
307+
throws PGPException
308+
{
309+
KeyPairGeneratorCallback generatorCallback = new KeyPairGeneratorCallback()
310+
{
311+
@Override
312+
public PGPKeyPair generateFrom(PGPKeyPairGenerator generator)
313+
throws PGPException
314+
{
315+
return generator.generateRsaKeyPair(bitStrength);
316+
}
317+
};
318+
319+
WithPrimaryKey builder = withPrimaryKey(generatorCallback)
320+
.addSigningSubkey(generatorCallback)
321+
.addEncryptionSubkey(generatorCallback);
322+
323+
if (userId != null)
324+
{
325+
builder.addUserId(userId);
326+
}
327+
328+
return builder;
329+
}
330+
245331
/**
246332
* Generate an OpenPGP key with a certification-capable primary key.
247333
* See {@link PGPKeyPairGenerator#generatePrimaryKey()} for the primary key type

pg/src/test/java/org/bouncycastle/openpgp/api/test/OpenPGPV6KeyGeneratorTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ protected void performTestWith(OpenPGPApi api)
6060
testGenerateEd25519x25519Key(api);
6161
testGenerateEd448x448Key(api);
6262

63+
testGenerateSingletonRSAKey(api);
64+
testGenerateCompositeRSAKey(api);
65+
6366
testEnforcesPrimaryOrSubkeyType(api);
6467
testGenerateKeyWithoutSignatures(api);
6568
}
@@ -320,6 +323,59 @@ private void testGenerateEd448x448Key(OpenPGPApi api)
320323
isEquals(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, hashedSubpackets.getKeyFlags());
321324
}
322325

326+
private void testGenerateSingletonRSAKey(OpenPGPApi api)
327+
throws PGPException
328+
{
329+
Date creationTime = currentTimeRounded();
330+
OpenPGPKeyGenerator generator = api.generateKey(creationTime, false);
331+
332+
OpenPGPKey key = generator.singletonRSAKey(4096, "Alice <alice@example.com>")
333+
.build();
334+
335+
isEquals("Singleton RSA key MUST consist of only a single primary key.", 1, key.getKeys().size());
336+
OpenPGPCertificate.OpenPGPComponentKey primaryKey = key.getPrimaryKey();
337+
isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, primaryKey.getAlgorithm());
338+
isEquals("Primary key MUST have a strength of 4096 bits.", 4096, primaryKey.getPGPPublicKey().getBitStrength());
339+
340+
isEquals("The primary key MUST be the certification key", primaryKey, key.getCertificationKeys().get(0));
341+
isEquals("The primary key MUST be the encryption key", primaryKey, key.getEncryptionKeys().get(0));
342+
isEquals("The primary key MUST be the signing key", primaryKey, key.getSigningKeys().get(0));
343+
344+
isNotNull(key.getUserId("Alice <alice@example.com>"));
345+
}
346+
347+
private void testGenerateCompositeRSAKey(OpenPGPApi api)
348+
throws PGPException
349+
{
350+
Date creationTime = currentTimeRounded();
351+
OpenPGPKeyGenerator generator = api.generateKey(creationTime, false);
352+
353+
OpenPGPKey key = generator.compositeRSAKey(4096, "Alice <alice@example.com>")
354+
.build();
355+
356+
isEquals("The composite RSA key MUST consist of 3 component keys", 3, key.getKeys().size());
357+
358+
OpenPGPCertificate.OpenPGPComponentKey primaryKey = key.getPrimaryKey();
359+
isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, primaryKey.getAlgorithm());
360+
isEquals("Primary key MUST have a strength of 4096 bits.", 4096, primaryKey.getPGPPublicKey().getBitStrength());
361+
isEquals("There MUST be only one certification key", 1, key.getCertificationKeys().size());
362+
isEquals("The primary key MUST be the certification key", primaryKey, key.getCertificationKeys().get(0));
363+
364+
isEquals("There MUST be only one signing key", 1, key.getSigningKeys().size());
365+
OpenPGPCertificate.OpenPGPComponentKey signingKey = key.getSigningKeys().get(0);
366+
isEquals("Signing key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, signingKey.getAlgorithm());
367+
isEquals("Signing key MUST have a strength of 4096 bits.", 4096, signingKey.getPGPPublicKey().getBitStrength());
368+
isFalse("The signing key MUST NOT be the primary key", primaryKey.equals(signingKey));
369+
370+
isEquals("There MUST be only one encryption key", 1, key.getEncryptionKeys().size());
371+
OpenPGPCertificate.OpenPGPComponentKey encryptionKey = key.getEncryptionKeys().get(0);
372+
isEquals("Primary key MUST be an RSA key", PublicKeyAlgorithmTags.RSA_GENERAL, encryptionKey.getAlgorithm());
373+
isEquals("Encryption key MUST have a strength of 4096 bits.", 4096, encryptionKey.getPGPPublicKey().getBitStrength());
374+
isFalse("The encryption key MUST NOT be the primary key", primaryKey.equals(encryptionKey));
375+
376+
isFalse("The signing key MUST NOT be the encryption key", signingKey.equals(encryptionKey));
377+
}
378+
323379
private void testGenerateCustomKey(OpenPGPApi api)
324380
throws PGPException
325381
{

0 commit comments

Comments
 (0)