mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-23 12:52:07 +01:00
Wip
This commit is contained in:
parent
0958915b4c
commit
5c2910f6c1
3 changed files with 198 additions and 4 deletions
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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");
|
||||
|
@ -207,7 +244,11 @@ public class GenerateKeys {
|
|||
.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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue