This commit is contained in:
Paul Schaub 2021-06-24 13:57:53 +02:00
parent 0958915b4c
commit 5c2910f6c1
3 changed files with 198 additions and 4 deletions

View File

@ -29,7 +29,12 @@ import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProv
import org.pgpainless.util.Passphrase;
/**
* Interface that is used to provide secret key ring encryptors and decryptors.
* Task of the {@link SecretKeyRingProtector} is to map encryptor/decryptor objects to key-ids.
* {@link PBESecretKeyEncryptor PBESecretKeyEncryptors}/{@link PBESecretKeyDecryptor PBESecretKeyDecryptors} are used
* to encrypt/decrypt secret keys using a passphrase.
*
* While it is easy to create an implementation of this interface that fits your needs, there are a bunch of
* implementations ready for use.
*/
public interface SecretKeyRingProtector {
@ -57,6 +62,8 @@ public interface SecretKeyRingProtector {
* The protector maintains an in-memory cache of passphrases and can be extended with new passphrases
* at runtime.
*
* See {@link CachingSecretKeyRingProtector} for how to memorize/forget additional passphrases during runtime.
*
* @param missingPassphraseCallback callback that is used to provide missing passphrases.
* @return caching secret key protector
*/
@ -70,6 +77,9 @@ public interface SecretKeyRingProtector {
/**
* Use the provided passphrase to lock/unlock all subkeys in the provided key ring.
*
* This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object.
* For other keys that are not present in the ring, it will return null.
*
* @param passphrase passphrase
* @param keys key ring
* @return protector
@ -84,6 +94,10 @@ public interface SecretKeyRingProtector {
/**
* Use the provided passphrase to lock/unlock only the provided (sub-)key.
* This protector will only return a non-null encryptor/decryptor based on the provided passphrase if
* {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} is getting called with the key-id of the provided key.
*
* Otherwise this protector will always return null.
*
* @param passphrase passphrase
* @param key key to lock/unlock
@ -95,6 +109,12 @@ public interface SecretKeyRingProtector {
/**
* Protector for unprotected keys.
* This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls,
* no matter what the key-id is.
*
* As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will
* leave keys unprotected, should it be used to "protect" a key
* (eg. in {@link org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor#changePassphraseFromOldPassphrase(Passphrase)}).
*
* @return protector
*/

View File

@ -16,6 +16,7 @@
package org.pgpainless.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
@ -26,6 +27,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.AlgorithmSuite;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.Feature;
@ -34,6 +36,7 @@ import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.KeySpecBuilderInterface;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
@ -136,9 +139,11 @@ public class GenerateKeys {
*/
@Test
public void generateSimpleECKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
// Define a primary user-id
String userId = "mhelms@pgpainless.org";
// Set a password to protect the secret key
String password = "tr4ns";
// Generate the OpenPGP key
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.simpleEcKeyRing(userId, password);
@ -153,6 +158,37 @@ public class GenerateKeys {
* Among user-id and password, the user can add an arbitrary number of subkeys and specify their algorithms and
* algorithm preferences.
*
* If the target key amalgamation (key ring) should consist of more than just a single (sub-)key, start by providing
* the specifications for the subkeys first (in {@link org.pgpainless.key.generation.KeyRingBuilderInterface#withSubKey(KeySpec)})
* and add the primary key specification last (in {@link org.pgpainless.key.generation.KeyRingBuilderInterface#withPrimaryKey(KeySpec)}.
*
* {@link KeySpec} objects can best be obtained by using the {@link KeySpec#getBuilder(KeyType)} method and providing a {@link KeyType}.
* There are a bunch of factory methods for different {@link KeyType} implementations present in {@link KeyType} itself
* (such as {@link KeyType#ECDH(EllipticCurve)}.
*
* After that, the {@link org.pgpainless.key.generation.KeySpecBuilder} needs to be further configured.
* First of all, the keys {@link KeyFlag KeyFlags} need to be specified. {@link KeyFlag KeyFlags} determine
* the use of the key, like encryption, signing data or certifying subkeys.
* KeyFlags can be set with {@link org.pgpainless.key.generation.KeySpecBuilder#withKeyFlags(KeyFlag...)}.
*
* Next is algorithm setup. You can either trust PGPainless' defaults (see {@link AlgorithmSuite#getDefaultAlgorithmSuite()}),
* or specify your own algorithm preferences.
* To go with the defaults, call {@link KeySpecBuilderInterface.WithDetailedConfiguration#withDefaultAlgorithms()},
* otherwise start detailed config with {@link KeySpecBuilderInterface.WithDetailedConfiguration#withDetailedConfiguration()}.
*
* Note, that if you set preferred algorithms, the preference lists are sorted from high priority to low priority.
*
* When setting the primary key spec ({@link org.pgpainless.key.generation.KeyRingBuilder#withPrimaryKey(KeySpec)}),
* make sure that the primary key spec has the {@link KeyFlag} {@link KeyFlag#CERTIFY_OTHER} set, as this is an requirement
* for primary keys.
*
* Furthermore you have to set at least the primary user-id via
* {@link org.pgpainless.key.generation.KeyRingBuilderInterface.WithPrimaryUserId#withPrimaryUserId(String)},
* but you can also add additional user-ids via
* {@link org.pgpainless.key.generation.KeyRingBuilderInterface.WithAdditionalUserIdOrPassphrase#withAdditionalUserId(String)}.
*
* Lastly you can decide whether or not to set a passphrase to protect the secret key.
*
* @throws PGPException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
@ -166,6 +202,7 @@ public class GenerateKeys {
.withEmail("mcarpenter@pgpainless.org")
.withComment("Pride!")
.build();
String additionalUserId = "mcarpenter@christopher.street";
// It is recommended to use the Passphrase class, as it can be used to safely invalidate passwords from memory
Passphrase passphrase = Passphrase.fromPassword("1nters3x");
@ -204,10 +241,14 @@ public class GenerateKeys {
.withPrimaryKey(
KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
// The primary key MUST carry the CERTIFY_OTHER flag, but CAN carry additional flags
.withKeyFlags(KeyFlag.CERTIFY_OTHER)
.withDefaultAlgorithms()
.withKeyFlags(KeyFlag.CERTIFY_OTHER)
.withDefaultAlgorithms()
)
// Set primary user-id
.withPrimaryUserId(userId)
// Add an additional user id. This step can be repeated
.withAdditionalUserId(additionalUserId)
// Set passphrase. Alternatively use .withoutPassphrase() to leave key unprotected.
.withPassphrase(passphrase)
.build();
@ -215,6 +256,7 @@ public class GenerateKeys {
KeyRingInfo keyInfo = new KeyRingInfo(secretKey);
assertEquals(3, keyInfo.getSecretKeys().size());
assertEquals("Morgan Carpenter (Pride!) <mcarpenter@pgpainless.org>", keyInfo.getPrimaryUserId());
assertTrue(keyInfo.isUserIdValid(additionalUserId));
}
}

View File

@ -0,0 +1,132 @@
package org.pgpainless.example;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.exception.WrongPassphraseException;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.Passphrase;
public class ModifyKeys {
private final String userId = "alice@pgpainless.org";
private final String originalPassphrase = "p4ssw0rd";
private PGPSecretKeyRing secretKey;
private long primaryKeyId;
private long encryptionSubkeyId;
private long signingSubkeyId;
@BeforeEach
public void generateKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
secretKey = PGPainless.generateKeyRing()
.modernKeyRing(userId, originalPassphrase);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
primaryKeyId = info.getKeyId();
encryptionSubkeyId = info.getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS).get(0).getKeyID();
signingSubkeyId = info.getSigningSubkeys().get(0).getKeyID();
}
/**
* This example demonstrates how to change the passphrase of a secret key and all its subkeys.
*
* @throws PGPException
*/
@Test
public void changePassphrase() throws PGPException {
secretKey = PGPainless.modifyKeyRing(secretKey)
.changePassphraseFromOldPassphrase(Passphrase.fromPassword(originalPassphrase))
.withSecureDefaultSettings()
.toNewPassphrase(Passphrase.fromPassword("n3wP4ssW0rD"))
.done();
// Old passphrase no longer works
assertThrows(WrongPassphraseException.class, () ->
UnlockSecretKey.unlockSecretKey(secretKey.getSecretKey(), Passphrase.fromPassword(originalPassphrase)));
// But the new one does
UnlockSecretKey.unlockSecretKey(secretKey.getSecretKey(), Passphrase.fromPassword("n3wP4ssW0rD"));
}
/**
* This example demonstrates how to change the passphrase of a single subkey in a key to a new passphrase.
* Only the passphrase of the targeted key will be changed. All other keys remain untouched.
*
* @throws PGPException
*/
@Test
public void changeSingleSubkeyPassphrase() throws PGPException {
secretKey = PGPainless.modifyKeyRing(secretKey)
// Here we change the passphrase of the encryption subkey
.changeSubKeyPassphraseFromOldPassphrase(encryptionSubkeyId, Passphrase.fromPassword(originalPassphrase))
.withSecureDefaultSettings()
.toNewPassphrase(Passphrase.fromPassword("cryptP4ssphr4s3"))
.done();
// encryption key can now only be unlocked using the new passphrase
assertThrows(WrongPassphraseException.class, () ->
UnlockSecretKey.unlockSecretKey(secretKey.getSecretKey(encryptionSubkeyId), Passphrase.fromPassword(originalPassphrase)));
UnlockSecretKey.unlockSecretKey(secretKey.getSecretKey(encryptionSubkeyId), Passphrase.fromPassword("cryptP4ssphr4s3"));
// primary key remains unchanged
UnlockSecretKey.unlockSecretKey(secretKey.getSecretKey(primaryKeyId), Passphrase.fromPassword(originalPassphrase));
}
/**
* This example demonstrates how to add an additional user-id to a key.
*
* @throws PGPException
*/
@Test
public void addUserId() throws PGPException {
SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.fromPassword(originalPassphrase), secretKey);
secretKey = PGPainless.modifyKeyRing(secretKey)
.addUserId("additional@user.id", protector)
.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
assertTrue(info.isUserIdValid("additional@user.id"));
assertFalse(info.isUserIdValid("another@user.id"));
}
@Test
public void addSubkey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
// Protector for unlocking the existing secret key
SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.fromPassword(originalPassphrase), secretKey);
Passphrase subkeyPassphrase = Passphrase.fromPassword("subk3yP4ssphr4s3");
assertEquals(1, new KeyRingInfo(secretKey).getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS).size());
secretKey = PGPainless.modifyKeyRing(secretKey)
.addSubKey(
KeySpec.getBuilder(KeyType.ECDH(EllipticCurve._BRAINPOOLP512R1))
.withKeyFlags(KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)
.withDefaultAlgorithms(),
subkeyPassphrase,
protector)
.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
assertEquals(4, info.getSecretKeys().size());
assertEquals(4, info.getPublicKeys().size());
assertEquals(2, info.getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS).size());
}
}