1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-12-23 11:27:57 +01:00

Work on KeyRingInfo

This commit is contained in:
Paul Schaub 2021-05-24 16:10:26 +02:00
parent 6cb9091b2a
commit 909f0e7be3
4 changed files with 224 additions and 72 deletions

View file

@ -15,10 +15,12 @@
*/
package org.pgpainless.encryption_signing;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -32,15 +34,46 @@ import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.util.Passphrase;
/**
* Options for the encryption process.
* This class can be used to set encryption parameters, like encryption keys and passphrases, algorithms etc.
*
* A typical use might look like follows:
* <pre>
* {@code
* EncryptionOptions opt = new EncryptionOptions();
* opt.addRecipient(aliceKey, "Alice <alice@wonderland.lit>");
* opt.addPassphrase(Passphrase.fromPassword("AdditionalDecryptionPassphrase123"));
* }
* </pre>
*
* To use a custom symmetric encryption algorithm, use {@link #overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}.
* This will cause PGPainless to use the provided algorithm for message encryption, instead of negotiating an algorithm
* by inspecting the provided recipient keys.
*
* By default, PGPainless will only encrypt to a single encryption capable subkey per recipient key.
* This behavior can be changed, eg. by calling
* <pre>
* {@code
* opt.addRecipient(aliceKey, EncryptionOptions.encryptToAllCapableSubkeys());
* }
* </pre>
* when adding the recipient key.
*/
public class EncryptionOptions {
private final EncryptionStream.Purpose purpose;
private final Set<PGPKeyEncryptionMethodGenerator> encryptionMethods = new LinkedHashSet<>();
private final Set<SubkeyIdentifier> encryptionKeys = new LinkedHashSet<>();
private final Map<SubkeyIdentifier, KeyRingInfo> keyRingInfo = new HashMap<>();
private final EncryptionKeySelector encryptionKeySelector = encryptToFirstSubkey();
private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null;
/**
* Encrypt to keys both carrying the key flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}
* or {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*/
public EncryptionOptions() {
this(EncryptionStream.Purpose.STORAGE_AND_COMMUNICATIONS);
}
@ -49,10 +82,22 @@ public class EncryptionOptions {
this.purpose = purpose;
}
/**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
* which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
*
* @return encryption options
*/
public static EncryptionOptions encryptCommunications() {
return new EncryptionOptions(EncryptionStream.Purpose.COMMUNICATIONS);
}
/**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
* which carry the flag {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*
* @return encryption options
*/
public static EncryptionOptions encryptDataAtRest() {
return new EncryptionOptions(EncryptionStream.Purpose.STORAGE);
}
@ -64,14 +109,32 @@ public class EncryptionOptions {
* @param key key ring
* @param userId user id
*/
public void addRecipient(PGPPublicKeyRing key, String userId) {
public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId) {
return addRecipient(key, userId, encryptionKeySelector);
}
/**
* Add a recipient by providing a key and recipient user-id, as well as a strategy for selecting one or multiple
* encryption capable subkeys from the key.
*
* @param key key
* @param userId user-id
* @param encryptionKeySelectionStrategy strategy to select one or more encryption subkeys to encrypt to
*/
public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId, EncryptionKeySelector encryptionKeySelectionStrategy) {
KeyRingInfo info = new KeyRingInfo(key, new Date());
PGPPublicKey encryptionSubkey = info.getEncryptionSubkey(userId, purpose);
if (encryptionSubkey == null) {
throw new AssertionError("Key has no encryption subkey.");
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose));
if (encryptionSubkeys.isEmpty()) {
throw new AssertionError("Key has no suitable encryption subkeys.");
}
addRecipientKey(key, encryptionSubkey);
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
addRecipientKey(key, encryptionSubkey);
}
return this;
}
/**
@ -79,13 +142,30 @@ public class EncryptionOptions {
*
* @param key key ring
*/
public void addRecipient(PGPPublicKeyRing key) {
public EncryptionOptions addRecipient(PGPPublicKeyRing key) {
return addRecipient(key, encryptionKeySelector);
}
/**
* Add a recipient by providing a key and an encryption key selection strategy.
*
* @param key key ring
* @param encryptionKeySelectionStrategy strategy used to select one or multiple encryption subkeys.
*/
public EncryptionOptions addRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy) {
KeyRingInfo info = new KeyRingInfo(key, new Date());
PGPPublicKey encryptionSubkey = info.getEncryptionSubkey(purpose);
if (encryptionSubkey == null) {
throw new IllegalArgumentException("Key has no encryption subkey.");
List<PGPPublicKey> encryptionSubkeys = encryptionKeySelectionStrategy
.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose));
if (encryptionSubkeys.isEmpty()) {
throw new IllegalArgumentException("Key has no suitable encryption subkeys.");
}
addRecipientKey(key, encryptionSubkey);
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
addRecipientKey(key, encryptionSubkey);
}
return this;
}
private void addRecipientKey(PGPPublicKeyRing keyRing, PGPPublicKey key) {
@ -100,13 +180,13 @@ public class EncryptionOptions {
*
* @param passphrase passphrase
*/
public void addPassphrase(Passphrase passphrase) {
public EncryptionOptions addPassphrase(Passphrase passphrase) {
if (passphrase.isEmpty()) {
throw new IllegalArgumentException("Passphrase must not be empty.");
}
PBEKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory
.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase);
addEncryptionMethod(encryptionMethod);
return addEncryptionMethod(encryptionMethod);
}
/**
@ -119,8 +199,9 @@ public class EncryptionOptions {
*
* @param encryptionMethod encryption method
*/
public void addEncryptionMethod(PGPKeyEncryptionMethodGenerator encryptionMethod) {
public EncryptionOptions addEncryptionMethod(PGPKeyEncryptionMethodGenerator encryptionMethod) {
encryptionMethods.add(encryptionMethod);
return this;
}
public Set<PGPKeyEncryptionMethodGenerator> getEncryptionMethods() {
@ -141,4 +222,38 @@ public class EncryptionOptions {
}
this.encryptionAlgorithmOverride = encryptionAlgorithm;
}
public interface EncryptionKeySelector {
List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys);
}
/**
* Only encrypt to the first valid encryption capable subkey we stumble upon.
*
* @return encryption key selector
*/
public static EncryptionKeySelector encryptToFirstSubkey() {
return new EncryptionKeySelector() {
@Override
public List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys) {
return encryptionCapableKeys.isEmpty() ? Collections.emptyList() : Collections.singletonList(encryptionCapableKeys.get(0));
}
};
}
/**
* Encrypt to any valid, encryption capable subkey on the key ring.
*
* @return encryption key selector
*/
public static EncryptionKeySelector encryptToAllCapableSubkeys() {
return new EncryptionKeySelector() {
@Override
public List<PGPPublicKey> selectEncryptionSubkeys(List<PGPPublicKey> encryptionCapableKeys) {
return encryptionCapableKeys;
}
};
}
// TODO: Create encryptToBestSubkey() method
}

View file

@ -82,18 +82,21 @@ public final class SigningOptions {
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
if (userId != null) {
if (!keyRingInfo.isUserIdValid(userId)) {
throw new KeyValidationException(userId, keyRingInfo.getCurrentUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
throw new KeyValidationException(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
}
}
PGPPublicKey signingPubKey = keyRingInfo.getSigningSubkey();
if (signingPubKey == null) {
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new AssertionError("Key has no valid signing key.");
}
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, false);
for (PGPPublicKey signingPubKey : signingPubKeys) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, false);
}
}
public void addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
@ -111,18 +114,21 @@ public final class SigningOptions {
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
if (userId != null) {
if (!keyRingInfo.isUserIdValid(userId)) {
throw new KeyValidationException(userId, keyRingInfo.getCurrentUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
throw new KeyValidationException(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
}
}
PGPPublicKey signingPubKey = keyRingInfo.getSigningSubkey();
if (signingPubKey == null) {
List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
if (signingPubKeys.isEmpty()) {
throw new AssertionError("Key has no valid signing key.");
}
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, true);
for (PGPPublicKey signingPubKey : signingPubKeys) {
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, true);
}
}
private void addSigningMethod(PGPSecretKeyRing secretKey,

View file

@ -29,7 +29,6 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.bcpg.sig.PrimaryUserID;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
@ -116,7 +115,7 @@ public class KeyRingInfo {
* @return public key or null
*/
public PGPPublicKey getPublicKey(long keyId) {
return keys.getPublicKey(keyId);
return getPublicKey(keys, keyId);
}
/**
@ -143,12 +142,36 @@ public class KeyRingInfo {
}
if (publicKey == getPublicKey()) {
if (signatures.primaryKeyRevocation != null) {
if (SignatureUtils.isHardRevocation(signatures.primaryKeyRevocation)) {
return false;
}
}
return signatures.primaryKeyRevocation == null;
}
PGPSignature binding = signatures.subkeyBindings.get(keyId);
PGPSignature revocation = signatures.subkeyRevocations.get(keyId);
return binding != null && revocation == null;
// No valid binding
if (binding == null || SignatureUtils.isSignatureExpired(binding)) {
return false;
}
// Revocation
if (revocation != null) {
if (SignatureUtils.isHardRevocation(revocation)) {
// Subkey is hard revoked
return false;
} else {
if (!SignatureUtils.isSignatureExpired(revocation) && revocation.getCreationTime().after(binding.getCreationTime())) {
// Key is soft-revoked, not yet re-bound
return false;
}
}
}
return true;
}
/**
@ -322,11 +345,13 @@ public class KeyRingInfo {
}
/**
* Return the current direct-key self signature.
* Return the latest direct-key self signature.
*
* @return
* Note: This signature might be expired (check with {@link SignatureUtils#isSignatureExpired(PGPSignature)}).
*
* @return latest direct key self-signature
*/
public PGPSignature getCurrentDirectKeySelfSignature() {
public PGPSignature getLatestDirectKeySelfSignature() {
return signatures.primaryKeySelfSignature;
}
@ -334,7 +359,7 @@ public class KeyRingInfo {
return signatures.primaryKeyRevocation;
}
public PGPSignature getCurrentUserIdCertification(String userId) {
public PGPSignature getLatestUserIdCertification(String userId) {
return signatures.userIdCertifications.get(userId);
}
@ -353,27 +378,28 @@ public class KeyRingInfo {
public List<KeyFlag> getKeyFlagsOf(long keyId) {
if (getPublicKey().getKeyID() == keyId) {
PGPSignature directKeySignature = getCurrentDirectKeySelfSignature();
PGPSignature directKeySignature = getLatestDirectKeySelfSignature();
if (directKeySignature != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(directKeySignature);
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(directKeySignature);
if (keyFlags != null) {
return keyFlags;
}
}
String primaryUserId = getPrimaryUserId();
if (primaryUserId != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(getCurrentUserIdCertification(primaryUserId));
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
PGPSignature userIdSignature = getLatestUserIdCertification(primaryUserId);
List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdSignature);
if (keyFlags != null) {
return keyFlags;
}
}
} else {
PGPSignature bindingSignature = getCurrentSubkeyBindingSignature(keyId);
if (bindingSignature != null) {
KeyFlags flags = SignatureSubpacketsUtil.getKeyFlags(bindingSignature);
if (flags != null) {
return KeyFlag.fromBitmask(flags.getFlags());
List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(bindingSignature);
if (keyFlags != null) {
return keyFlags;
}
}
}
@ -385,14 +411,14 @@ public class KeyRingInfo {
return Collections.emptyList();
}
PGPSignature userIdCertification = getCurrentUserIdCertification(userId);
PGPSignature userIdCertification = getLatestUserIdCertification(userId);
if (userIdCertification == null) {
return Collections.emptyList();
throw new AssertionError("While user-id '" + userId + "' was reported as valid, there appears to be no certification for it.");
}
KeyFlags keyFlags = SignatureSubpacketsUtil.getKeyFlags(userIdCertification);
List<KeyFlag> keyFlags = SignatureSubpacketsUtil.parseKeyFlags(userIdCertification);
if (keyFlags != null) {
return KeyFlag.fromBitmask(keyFlags.getFlags());
return keyFlags;
}
return Collections.emptyList();
}
@ -428,7 +454,7 @@ public class KeyRingInfo {
private PGPSignature getMostRecentSignature() {
Set<PGPSignature> allSignatures = new HashSet<>();
PGPSignature mostRecentSelfSignature = getCurrentDirectKeySelfSignature();
PGPSignature mostRecentSelfSignature = getLatestDirectKeySelfSignature();
PGPSignature revocationSelfSignature = getRevocationSelfSignature();
if (mostRecentSelfSignature != null) allSignatures.add(mostRecentSelfSignature);
if (revocationSelfSignature != null) allSignatures.add(revocationSelfSignature);
@ -462,12 +488,12 @@ public class KeyRingInfo {
*/
public Date getPrimaryKeyExpirationDate() {
Date lastExpiration = null;
if (getCurrentDirectKeySelfSignature() != null) {
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), getCurrentDirectKeySelfSignature());
if (getLatestDirectKeySelfSignature() != null) {
lastExpiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), getLatestDirectKeySelfSignature());
}
for (String userId : getValidUserIds()) {
PGPSignature signature = getCurrentUserIdCertification(userId);
PGPSignature signature = getLatestUserIdCertification(userId);
Date expiration = SignatureUtils.getKeyExpirationDate(getCreationDate(), signature);
if (expiration != null && (lastExpiration == null || expiration.after(lastExpiration))) {
lastExpiration = expiration;
@ -543,8 +569,9 @@ public class KeyRingInfo {
return false;
}
public PGPPublicKey getEncryptionSubkey(EncryptionStream.Purpose purpose) {
public List<PGPPublicKey> getEncryptionSubkeys(EncryptionStream.Purpose purpose) {
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
List<PGPPublicKey> encryptionKeys = new ArrayList<>();
while (subkeys.hasNext()) {
PGPPublicKey subKey = subkeys.next();
@ -560,36 +587,37 @@ public class KeyRingInfo {
switch (purpose) {
case COMMUNICATIONS:
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS)) {
return subKey;
encryptionKeys.add(subKey);
}
break;
case STORAGE:
if (keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
return subKey;
encryptionKeys.add(subKey);
}
break;
case STORAGE_AND_COMMUNICATIONS:
if (keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)) {
return subKey;
encryptionKeys.add(subKey);
}
break;
}
}
return null;
return encryptionKeys;
}
public PGPPublicKey getEncryptionSubkey(String userId, EncryptionStream.Purpose purpose) {
public List<PGPPublicKey> getEncryptionSubkeys(String userId, EncryptionStream.Purpose purpose) {
if (userId != null) {
if (!isUserIdValid(userId)) {
throw new KeyValidationException(userId, getCurrentUserIdCertification(userId), getUserIdRevocation(userId));
throw new KeyValidationException(userId, getLatestUserIdCertification(userId), getUserIdRevocation(userId));
}
}
return getEncryptionSubkey(purpose);
return getEncryptionSubkeys(purpose);
}
public PGPPublicKey getSigningSubkey() {
public List<PGPPublicKey> getSigningSubkeys() {
Iterator<PGPPublicKey> subkeys = keys.getPublicKeys();
List<PGPPublicKey> signingKeys = new ArrayList<>();
while (subkeys.hasNext()) {
PGPPublicKey subKey = subkeys.next();
@ -599,16 +627,16 @@ public class KeyRingInfo {
List<KeyFlag> keyFlags = getKeyFlagsOf(subKey.getKeyID());
if (keyFlags.contains(KeyFlag.SIGN_DATA)) {
return subKey;
signingKeys.add(subKey);
}
}
return null;
return signingKeys;
}
public List<HashAlgorithm> getPreferredHashAlgorithms(String userId, long keyID) {
PGPSignature signature = getCurrentUserIdCertification(userId == null ? getPrimaryUserId() : userId);
PGPSignature signature = getLatestUserIdCertification(userId == null ? getPrimaryUserId() : userId);
if (signature == null) {
signature = getCurrentDirectKeySelfSignature();
signature = getLatestDirectKeySelfSignature();
}
if (signature == null) {
signature = getCurrentSubkeyBindingSignature(keyID);

View file

@ -26,11 +26,12 @@ import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.encryption_signing.EncryptionOptions;
import org.pgpainless.encryption_signing.EncryptionResult;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.key.WeirdKeys;
import org.pgpainless.key.util.KeyRingUtils;
@ -46,23 +47,25 @@ public class TestTwoSubkeysEncryption {
* {@link WeirdKeys#TWO_CRYPT_SUBKEYS} is a key that has two subkeys which both carry the key flags
* {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS} and {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
*
* This test makes sure that both subkeys are used for encryption.
* This test verifies that {@link EncryptionOptions#addRecipient(PGPPublicKeyRing, EncryptionOptions.EncryptionKeySelector)}
* works properly, if {@link EncryptionOptions#encryptToAllCapableSubkeys()} is provided as argument.
*
* @throws IOException not expected
* @throws PGPException not expected
*/
@Test
@Disabled("We may not want to encrypt to all enc capable subkeys.")
public void testEncryptsToBothSubkeys() throws IOException, PGPException {
PGPSecretKeyRing twoSuitableSubkeysKeyRing = WeirdKeys.getTwoCryptSubkeysKey();
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(twoSuitableSubkeysKeyRing);
ByteArrayOutputStream out = new ByteArrayOutputStream();
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign(EncryptionStream.Purpose.STORAGE)
.onOutputStream(out)
.toRecipient(publicKeys)
.and()
.doNotSign()
.noArmor();
.withOptions(
ProducerOptions.encrypt(new EncryptionOptions(EncryptionStream.Purpose.STORAGE_AND_COMMUNICATIONS)
.addRecipient(publicKeys, EncryptionOptions.encryptToAllCapableSubkeys())
)
.setAsciiArmor(false)
);
Streams.pipeAll(getPlainIn(), encryptionStream);
encryptionStream.close();