mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 03:17:58 +01:00
Work on signaturePicker
This commit is contained in:
parent
a30767eb91
commit
6cb9091b2a
9 changed files with 614 additions and 337 deletions
|
@ -16,17 +16,132 @@
|
|||
package org.pgpainless.key;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.UserAttributePacket;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
import org.pgpainless.util.NonEmptyList;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
|
||||
public interface EvaluatedKeyRing {
|
||||
|
||||
PGPSignature getUserIdCertification(String userId);
|
||||
class EvaluatedSignature {
|
||||
private final PGPSignature signature;
|
||||
private final SignatureValidationException exception;
|
||||
|
||||
PGPSignature getUserIdRevocation(String userId);
|
||||
PGPSignature getValidSignature() throws SignatureValidationException {
|
||||
if (getException() != null) {
|
||||
throw new SignatureValidationException("Signature is not valid.", getException());
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
SignatureValidationException getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public EvaluatedSignature(PGPSignature signature, SignatureValidationException exception) {
|
||||
this.signature = signature;
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of user-ids and associated user-id certification signatures.
|
||||
* Each map entry consists of a user-id and a {@link NonEmptyList} of associated certification signatures which
|
||||
* contains the latest non-revoking certification signature as its first element.
|
||||
*
|
||||
* @return map of user-ids and certifications
|
||||
*/
|
||||
Map<String, NonEmptyList<PGPSignature>> getUserIdCertifications();
|
||||
|
||||
/**
|
||||
* Return the latest user-id certification signature associated to the provided user-id.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @return latest user-id certification signature
|
||||
* @throws IllegalArgumentException if the key doesn't have at least one certification signature for the
|
||||
* provided user-id.
|
||||
*/
|
||||
default PGPSignature getUserIdCertification(String userId) {
|
||||
NonEmptyList<PGPSignature> userIdCerts = getUserIdCertifications().get(userId);
|
||||
if (userIdCerts == null) {
|
||||
throw new IllegalArgumentException("No user-id '" + userId + "' found on the key.");
|
||||
}
|
||||
return userIdCerts.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of user-ids and associated user-id revocation signatures.
|
||||
*
|
||||
* @return map of user-ids and revocations
|
||||
*/
|
||||
Map<String, List<PGPSignature>> getUserIdRevocations();
|
||||
|
||||
/**
|
||||
* Return the latest, hardest revocation signature for the passed in user-id.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @return latest hardest revocation signature
|
||||
* @throws IllegalArgumentException if the key doesn't have at least one certification signature for the given user-id.
|
||||
*/
|
||||
default PGPSignature getUserIdRevocation(String userId) {
|
||||
List<PGPSignature> userIdRevs = getUserIdRevocations().get(userId);
|
||||
if (userIdRevs == null) {
|
||||
throw new IllegalArgumentException("No user-id '" + userId + "' found on the key.");
|
||||
}
|
||||
return userIdRevs.isEmpty() ? null : userIdRevs.get(0);
|
||||
}
|
||||
|
||||
default boolean isRevoked(String userId) {
|
||||
PGPSignature latestCertification = getUserIdCertification(userId);
|
||||
PGPSignature latestRevocation = getUserIdRevocation(userId);
|
||||
|
||||
if (latestRevocation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return latestRevocation.getCreationTime().after(latestCertification.getCreationTime())
|
||||
|| SignatureUtils.isHardRevocation(latestRevocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of {@link UserAttributePacket UserAttributePackets} and associated certification signatures.
|
||||
* Each map entry consists of a {@link UserAttributePacket} and a {@link NonEmptyList} of associated certification
|
||||
* signatures which contains the latest non-revoking certification signtaure as its first element.
|
||||
*
|
||||
* @return map of user-attributes and certifications
|
||||
*/
|
||||
Map<UserAttributePacket, NonEmptyList<PGPSignature>> getUserAttributeCertifications();
|
||||
|
||||
/**
|
||||
* Return the latest certification signature for the provided {@link UserAttributePacket}.
|
||||
*
|
||||
* @param userAttribute user attribute
|
||||
* @return latest certification signature
|
||||
* @throws IllegalArgumentException if the key doesn't carry such user-attribute
|
||||
*/
|
||||
default PGPSignature getUserAttributeCertification(UserAttributePacket userAttribute) {
|
||||
NonEmptyList<PGPSignature> userAttrCerts = getUserAttributeCertifications().get(userAttribute);
|
||||
if (userAttrCerts == null) {
|
||||
throw new IllegalArgumentException("No such user-attribute found on the key.");
|
||||
}
|
||||
return userAttrCerts.get();
|
||||
}
|
||||
|
||||
Map<UserAttributePacket, List<PGPSignature>> getUserAttributeRevocations();
|
||||
|
||||
default PGPSignature getUserAttributeRevocation(UserAttributePacket userAttribute) {
|
||||
List<PGPSignature> userAttrRevs = getUserAttributeRevocations().get(userAttribute);
|
||||
if (userAttrRevs == null) {
|
||||
throw new IllegalArgumentException("No such user-attribute found on the key.");
|
||||
}
|
||||
return userAttrRevs.isEmpty() ? null : userAttrRevs.get(0);
|
||||
}
|
||||
|
||||
PGPSignature getSubkeyBinding(long subkeyId);
|
||||
|
||||
|
|
|
@ -26,17 +26,13 @@ import org.bouncycastle.openpgp.PGPException;
|
|||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.SelectSignatureFromKey;
|
||||
import org.pgpainless.signature.SignatureCreationDateComparator;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.SignatureCreationDateComparator;
|
||||
import org.pgpainless.signature.SignatureValidator;
|
||||
import org.pgpainless.util.CollectionUtils;
|
||||
|
||||
|
@ -146,115 +142,4 @@ public class KeyRingValidator {
|
|||
return blank;
|
||||
}
|
||||
|
||||
public static <R extends PGPKeyRing> R getKeyRingAtDate(R keyRing, KeyRingInfo info) {
|
||||
Iterator<PGPPublicKey> iterator = keyRing.getPublicKeys();
|
||||
while (iterator.hasNext()) {
|
||||
PGPPublicKey publicKey = iterator.next();
|
||||
if (publicKey.isMasterKey()) {
|
||||
keyRing = assessPrimaryKeyAtDate(publicKey, keyRing, info);
|
||||
} else {
|
||||
keyRing = assessSubkeyAtDate(publicKey, keyRing, info);
|
||||
}
|
||||
}
|
||||
return keyRing;
|
||||
}
|
||||
|
||||
private static <R extends PGPKeyRing> R assessPrimaryKeyAtDate(PGPPublicKey primaryKey, PGPKeyRing keyRing, KeyRingInfo info) {
|
||||
if (!primaryKey.isMasterKey()) {
|
||||
throw new IllegalArgumentException("Passed in key is not a primary key");
|
||||
}
|
||||
|
||||
// Direct Key Signatures
|
||||
PGPSignature latestSelfSig = info.getCurrentDirectKeySelfSignature();
|
||||
PGPSignature latestSelfRevocation = info.getRevocationSelfSignature();
|
||||
|
||||
|
||||
// User-ID certifications
|
||||
Iterator<String> userIdIterator = primaryKey.getUserIDs();
|
||||
while (userIdIterator.hasNext()) {
|
||||
String userId = userIdIterator.next();
|
||||
boolean isUserIdBound = false;
|
||||
Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
|
||||
while (userIdSigIterator.hasNext()) {
|
||||
PGPSignature userIdSig = userIdSigIterator.next();
|
||||
if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey)
|
||||
.accept(userIdSig, primaryKey, keyRing)) {
|
||||
primaryKey = PGPPublicKey.removeCertification(primaryKey, userId, userIdSig);
|
||||
continue;
|
||||
}
|
||||
isUserIdBound = true;
|
||||
}
|
||||
if (!isUserIdBound) {
|
||||
primaryKey = PGPPublicKey.removeCertification(primaryKey, userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Revocations
|
||||
Iterator<PGPSignature> revocationSignatures = primaryKey.getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode());
|
||||
while (revocationSignatures.hasNext()) {
|
||||
PGPSignature revocationSig = revocationSignatures.next();
|
||||
if (!SelectSignatureFromKey.isValidKeyRevocationSignature(primaryKey)
|
||||
.accept(revocationSig, primaryKey, keyRing)) {
|
||||
primaryKey = PGPPublicKey.removeCertification(primaryKey, revocationSig);
|
||||
}
|
||||
}
|
||||
|
||||
return (R) replacePublicKey(keyRing, primaryKey);
|
||||
}
|
||||
|
||||
private static <R extends PGPKeyRing> R assessSubkeyAtDate(PGPPublicKey subkey, PGPKeyRing keyRing, KeyRingInfo info) {
|
||||
if (subkey.isMasterKey()) {
|
||||
throw new IllegalArgumentException("Passed in key is not a subkey");
|
||||
}
|
||||
|
||||
// Subkey binding sigs
|
||||
Iterator<PGPSignature> subkeyBindingSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_BINDING.getCode());
|
||||
while (subkeyBindingSigIterator.hasNext()) {
|
||||
PGPSignature signature = subkeyBindingSigIterator.next();
|
||||
if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(keyRing.getPublicKey(), subkey)
|
||||
.accept(signature, subkey, keyRing)) {
|
||||
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// Subkey revocation sigs
|
||||
Iterator<PGPSignature> revocationSigIterator = subkey.getSignaturesOfType(SignatureType.SUBKEY_REVOCATION.getCode());
|
||||
while (revocationSigIterator.hasNext()) {
|
||||
PGPSignature signature = revocationSigIterator.next();
|
||||
if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) {
|
||||
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<PGPSignature> directKeySigIterator = subkey.getSignaturesOfType(SignatureType.DIRECT_KEY.getCode());
|
||||
while (directKeySigIterator.hasNext()) {
|
||||
PGPSignature signature = directKeySigIterator.next();
|
||||
PGPPublicKey creator = keyRing.getPublicKey(signature.getKeyID());
|
||||
if (creator == null) {
|
||||
// remove external signature
|
||||
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidDirectKeySignature(creator, subkey)
|
||||
.accept(signature, subkey, keyRing)) {
|
||||
subkey = PGPPublicKey.removeCertification(subkey, signature);
|
||||
}
|
||||
}
|
||||
|
||||
return (R) replacePublicKey(keyRing, subkey);
|
||||
}
|
||||
|
||||
private static PGPKeyRing replacePublicKey(PGPKeyRing keyRing, PGPPublicKey publicKey) {
|
||||
if (keyRing instanceof PGPPublicKeyRing) {
|
||||
keyRing = PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) keyRing, publicKey);
|
||||
} else if (keyRing instanceof PGPSecretKeyRing) {
|
||||
PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keyRing;
|
||||
PGPPublicKeyRing publicKeys = KeyRingUtils.publicKeyRingFrom(secretKeys);
|
||||
publicKeys = PGPPublicKeyRing.insertPublicKey(publicKeys, publicKey);
|
||||
secretKeys = PGPSecretKeyRing.replacePublicKeys(secretKeys, publicKeys);
|
||||
keyRing = secretKeys;
|
||||
}
|
||||
return keyRing;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -629,8 +629,8 @@ public class KeyRingInfo {
|
|||
private final Map<Long, PGPSignature> subkeyBindings;
|
||||
|
||||
public Signatures(PGPKeyRing keyRing, Date evaluationDate, Policy policy) {
|
||||
primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, evaluationDate);
|
||||
primaryKeySelfSignature = SignaturePicker.pickCurrentDirectKeySelfSignature(keyRing, evaluationDate);
|
||||
primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, policy, evaluationDate);
|
||||
primaryKeySelfSignature = SignaturePicker.pickLatestDirectKeySignature(keyRing, policy, evaluationDate);
|
||||
userIdRevocations = new HashMap<>();
|
||||
userIdCertifications = new HashMap<>();
|
||||
subkeyRevocations = new HashMap<>();
|
||||
|
@ -638,11 +638,11 @@ public class KeyRingInfo {
|
|||
|
||||
for (Iterator<String> it = keyRing.getPublicKey().getUserIDs(); it.hasNext(); ) {
|
||||
String userId = it.next();
|
||||
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, evaluationDate);
|
||||
PGPSignature revocation = SignaturePicker.pickCurrentUserIdRevocationSignature(keyRing, userId, policy, evaluationDate);
|
||||
if (revocation != null) {
|
||||
userIdRevocations.put(userId, revocation);
|
||||
}
|
||||
PGPSignature certification = SignaturePicker.pickCurrentUserIdCertificationSignature(keyRing, userId, evaluationDate);
|
||||
PGPSignature certification = SignaturePicker.pickLatestUserIdCertificationSignature(keyRing, userId, policy, evaluationDate);
|
||||
if (certification != null) {
|
||||
userIdCertifications.put(userId, certification);
|
||||
}
|
||||
|
@ -652,11 +652,11 @@ public class KeyRingInfo {
|
|||
keys.next(); // Skip primary key
|
||||
while (keys.hasNext()) {
|
||||
PGPPublicKey subkey = keys.next();
|
||||
PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, evaluationDate);
|
||||
PGPSignature subkeyRevocation = SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keyRing, subkey, policy, evaluationDate);
|
||||
if (subkeyRevocation != null) {
|
||||
subkeyRevocations.put(subkey.getKeyID(), subkeyRevocation);
|
||||
}
|
||||
PGPSignature subkeyBinding = SignaturePicker.pickCurrentSubkeyBindingSignature(keyRing, subkey, evaluationDate);
|
||||
PGPSignature subkeyBinding = SignaturePicker.pickLatestSubkeyBindingSignature(keyRing, subkey, policy, evaluationDate);
|
||||
if (subkeyBinding != null) {
|
||||
subkeyBindings.put(subkey.getKeyID(), subkeyBinding);
|
||||
}
|
||||
|
|
|
@ -20,17 +20,12 @@ import java.util.Date;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.bcpg.sig.RevocationReason;
|
||||
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.SignatureType;
|
||||
import org.pgpainless.exception.SignatureValidationException;
|
||||
import org.pgpainless.key.util.RevocationAttributes;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
import org.pgpainless.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
|
@ -49,14 +44,14 @@ import org.pgpainless.util.CollectionUtils;
|
|||
public class SignaturePicker {
|
||||
|
||||
/**
|
||||
* Pick the most current (at the time of evaluation) key revocation signature.
|
||||
* If there is a hard revocation signature, it is picked, regardless of expiration or creation time.
|
||||
* Pick the, at validation date most recent valid key revocation signature.
|
||||
* If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
|
||||
* validationDate or if it is already expired.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @return most recent, valid key revocation signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||
Policy policy = PGPainless.getPolicy();
|
||||
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
|
||||
|
@ -76,42 +71,36 @@ public class SignaturePicker {
|
|||
}
|
||||
|
||||
/**
|
||||
* Pick the current direct key self-signature on the primary key.
|
||||
* Pick the, at validationDate most recent, valid direct key signature.
|
||||
* This method might return null, if there is no direct key self-signature which is valid at validationDate.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param validationDate validation date
|
||||
* @return direct-key self-signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Date validationDate) {
|
||||
public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
return pickCurrentDirectKeySignature(primaryKey, primaryKey, keyRing, validationDate);
|
||||
return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the current direct-key signature made by the signing key on the signed key.
|
||||
* Pick the, at validationDate, latest, valid direct key signature made by signingKey on signedKey.
|
||||
* This method might return null, if there is no direct key self signature which is valid at validationDate.
|
||||
*
|
||||
* @param signingKey key that created the signature
|
||||
* @param signedKey key that carries the signature
|
||||
* @param keyRing key ring
|
||||
* @param validationDate validation date
|
||||
* @return direct key sig
|
||||
*/
|
||||
public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, PGPKeyRing keyRing, Date validationDate) {
|
||||
public static PGPSignature pickCurrentDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) {
|
||||
List<PGPSignature> directKeySignatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY);
|
||||
|
||||
PGPSignature mostRecentDirectKeySigBySigningKey = null;
|
||||
for (PGPSignature signature : directKeySignatures) {
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, signingKey, keyRing)) {
|
||||
// signature is not well formed
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, signedKey, keyRing)) {
|
||||
// Signature is either expired or not yet valid
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidDirectKeySignature(signingKey, signedKey).accept(signature, signedKey, keyRing)) {
|
||||
// signature does not check out.
|
||||
try {
|
||||
SignatureValidator.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// Direct key sig is not valid
|
||||
continue;
|
||||
}
|
||||
mostRecentDirectKeySigBySigningKey = signature;
|
||||
|
@ -121,57 +110,94 @@ public class SignaturePicker {
|
|||
}
|
||||
|
||||
/**
|
||||
* Pick the most recent user-id revocation signature.
|
||||
* Pick the, at validationDate, latest direct key signature.
|
||||
* This method might return an expired signature.
|
||||
* If there are more than one direct-key signature, and some of those are not expired, the latest non-expired
|
||||
* yet already effective direct-key signature will be returned.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param validationDate validation date
|
||||
* @return latest direct key signature
|
||||
*/
|
||||
public static PGPSignature pickLatestDirectKeySignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
return pickLatestDirectKeySignature(primaryKey, primaryKey, policy, validationDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the, at validationDate, latest direct key signature made by signingKey on signedKey.
|
||||
* This method might return an expired signature.
|
||||
* If a non-expired direct-key signature exists, the latest non-expired yet already effective direct-key
|
||||
* signature will be returned.
|
||||
*
|
||||
* @param signingKey signing key (key that made the sig)
|
||||
* @param signedKey signed key (key that carries the sig)
|
||||
* @param validationDate date of validation
|
||||
* @return latest direct key sig
|
||||
*/
|
||||
public static PGPSignature pickLatestDirectKeySignature(PGPPublicKey signingKey, PGPPublicKey signedKey, Policy policy, Date validationDate) {
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY);
|
||||
|
||||
PGPSignature latestDirectKeySignature = null;
|
||||
for (PGPSignature signature : signatures) {
|
||||
try {
|
||||
SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(signature);
|
||||
SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(signature);
|
||||
SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
|
||||
// if the currently latest signature is not yet expired, check if the next candidate is not yet expired
|
||||
if (latestDirectKeySignature != null && !SignatureUtils.isSignatureExpired(latestDirectKeySignature, validationDate)) {
|
||||
SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
|
||||
}
|
||||
SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(signature);
|
||||
} catch (SignatureValidationException e) {
|
||||
// Direct key signature is not valid
|
||||
continue;
|
||||
}
|
||||
latestDirectKeySignature = signature;
|
||||
}
|
||||
|
||||
return latestDirectKeySignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the, at validationDate most recent, valid user-id revocation signature.
|
||||
* If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
|
||||
* validationDate or if it is already expired.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param userId user-Id that gets revoked
|
||||
* @param validationDate validation date
|
||||
* @return revocation signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Date validationDate) {
|
||||
public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION);
|
||||
|
||||
Iterator<PGPSignature> certificationRevocations = primaryKey.getSignaturesOfType(SignatureType.CERTIFICATION_REVOCATION.getCode());
|
||||
List<PGPSignature> signatures = CollectionUtils.iteratorToList(certificationRevocations);
|
||||
Collections.sort(signatures, new SignatureCreationDateComparator());
|
||||
|
||||
PGPSignature mostRecentUserIdRevocation = null;
|
||||
PGPSignature latestUserIdRevocation = null;
|
||||
for (PGPSignature signature : signatures) {
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||
// Sig is not well formed.
|
||||
continue;
|
||||
}
|
||||
|
||||
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||
// reason code states soft revocation
|
||||
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||
// Soft revocation is either expired or not yet valid
|
||||
try {
|
||||
SignatureValidator.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// User-id revocation is not valid
|
||||
continue;
|
||||
}
|
||||
latestUserIdRevocation = signature;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidCertificationRevocationSignature(primaryKey, userId)
|
||||
.accept(signature, primaryKey, keyRing)) {
|
||||
// sig does not check out for userid
|
||||
continue;
|
||||
}
|
||||
|
||||
mostRecentUserIdRevocation = signature;
|
||||
}
|
||||
|
||||
return mostRecentUserIdRevocation;
|
||||
return latestUserIdRevocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the most current certification self-signature for the given user-id.
|
||||
* Pick the, at validationDate latest, valid certification self-signature for the given user-id.
|
||||
* This method might return null, if there is no certification self signature for that user-id which is valid
|
||||
* at validationDate.
|
||||
*
|
||||
* @param keyRing keyring
|
||||
* @param userId userid
|
||||
* @param validationDate validation date
|
||||
* @return user-id certification
|
||||
*/
|
||||
public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Date validationDate) {
|
||||
public static PGPSignature pickCurrentUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
|
||||
Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
|
||||
|
@ -180,21 +206,12 @@ public class SignaturePicker {
|
|||
|
||||
PGPSignature mostRecentUserIdCertification = null;
|
||||
for (PGPSignature signature : signatures) {
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||
// Sig not well formed
|
||||
try {
|
||||
SignatureValidator.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// User-id certification is not valid
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||
// Sig is either expired or not valid yet
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidSignatureOnUserId(userId, primaryKey).accept(signature, primaryKey, keyRing)) {
|
||||
// Sig does not check out
|
||||
continue;
|
||||
}
|
||||
|
||||
mostRecentUserIdCertification = signature;
|
||||
}
|
||||
|
||||
|
@ -202,57 +219,88 @@ public class SignaturePicker {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the current subkey binding revocation signature for the given subkey.
|
||||
* Pick the, at validationDate latest certification self-signature for the given user-id.
|
||||
* This method might return an expired signature.
|
||||
* If a non-expired user-id certification signature exists, the latest non-expired yet already effective
|
||||
* user-id certification signature for the given user-id will be returned.
|
||||
*
|
||||
* @param keyRing keyring
|
||||
* @param userId userid
|
||||
* @param validationDate validation date
|
||||
* @return user-id certification
|
||||
*/
|
||||
public static PGPSignature pickLatestUserIdCertificationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
|
||||
Iterator<PGPSignature> userIdSigIterator = primaryKey.getSignaturesForID(userId);
|
||||
List<PGPSignature> signatures = CollectionUtils.iteratorToList(userIdSigIterator);
|
||||
Collections.sort(signatures, new SignatureCreationDateComparator());
|
||||
|
||||
PGPSignature latestUserIdCert = null;
|
||||
for (PGPSignature signature : signatures) {
|
||||
try {
|
||||
SignatureValidator.signatureIsCertification().verify(signature);
|
||||
SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||
SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
|
||||
// if the currently latest signature is not yet expired, check if the next candidate is not yet expired
|
||||
if (latestUserIdCert != null && !SignatureUtils.isSignatureExpired(latestUserIdCert, validationDate)) {
|
||||
SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
|
||||
}
|
||||
SignatureValidator.correctSignatureOverUserId(userId, primaryKey, primaryKey).verify(signature);
|
||||
} catch (SignatureValidationException e) {
|
||||
// User-id certification is not valid
|
||||
continue;
|
||||
}
|
||||
|
||||
latestUserIdCert = signature;
|
||||
}
|
||||
|
||||
return latestUserIdCert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the, at validationDate most recent, valid subkey revocation signature.
|
||||
* If there are hard revocation signatures, the latest hard revocation sig is picked, even if it was created after
|
||||
* validationDate or if it is already expired.
|
||||
*
|
||||
* @param keyRing keyring
|
||||
* @param subkey subkey
|
||||
* @param validationDate validation date
|
||||
* @return subkey revocation signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) {
|
||||
public static PGPSignature pickCurrentSubkeyBindingRevocationSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||
throw new IllegalArgumentException("Primary key cannot have subkey binding revocations.");
|
||||
}
|
||||
|
||||
List<PGPSignature> subkeyRevocationSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
|
||||
PGPSignature mostRecentSubkeyRevocation = null;
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
|
||||
PGPSignature latestSubkeyRevocation = null;
|
||||
|
||||
for (PGPSignature signature : subkeyRevocationSigs) {
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||
// Signature is not well formed
|
||||
continue;
|
||||
}
|
||||
|
||||
RevocationReason reason = SignatureSubpacketsUtil.getRevocationReason(signature);
|
||||
if (reason != null && !RevocationAttributes.Reason.isHardRevocation(reason.getRevocationReason())) {
|
||||
// reason code states soft revocation
|
||||
if (!SelectSignatureFromKey.isValidAt(validationDate).accept(signature, primaryKey, keyRing)) {
|
||||
// Soft revocation is either expired or not yet valid
|
||||
for (PGPSignature signature : signatures) {
|
||||
try {
|
||||
SignatureValidator.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// subkey binding revocation is not valid
|
||||
continue;
|
||||
}
|
||||
latestSubkeyRevocation = signature;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) {
|
||||
// Signature does not check out
|
||||
continue;
|
||||
}
|
||||
mostRecentSubkeyRevocation = signature;
|
||||
}
|
||||
|
||||
return mostRecentSubkeyRevocation;
|
||||
return latestSubkeyRevocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the (at the time of validation) most recent, valid subkey binding signature
|
||||
* made by the primary key of the key ring on the subkey.
|
||||
* Pick the, at validationDate latest, valid subkey binding signature for the given subkey.
|
||||
* This method might return null, if there is no subkey binding signature which is valid
|
||||
* at validationDate.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param subkey subkey
|
||||
* @param validationDate date of validation
|
||||
* @return most recent valid subkey binding signature
|
||||
*/
|
||||
public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Date validationDate) {
|
||||
public static PGPSignature pickCurrentSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||
throw new IllegalArgumentException("Primary key cannot have subkey binding signature.");
|
||||
|
@ -262,35 +310,68 @@ public class SignaturePicker {
|
|||
PGPSignature mostCurrentValidSig = null;
|
||||
|
||||
for (PGPSignature signature : subkeyBindingSigs) {
|
||||
// has hashed creation time, does not predate signing key creation date
|
||||
if (!SelectSignatureFromKey.isWellFormed().accept(signature, primaryKey, keyRing)) {
|
||||
// Signature is not well-formed. Reject.
|
||||
try {
|
||||
SignatureValidator.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate);
|
||||
} catch (SignatureValidationException validationException) {
|
||||
// Subkey binding sig is not valid
|
||||
continue;
|
||||
}
|
||||
|
||||
SignatureCreationTime creationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature);
|
||||
if (creationTime.getTime().after(validationDate)) {
|
||||
// signature is not yet valid
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SignatureUtils.isSignatureExpired(signature, validationDate)) {
|
||||
// Signature is expired
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidSubkeyBindingSignature(primaryKey, subkey)
|
||||
.accept(signature, subkey, keyRing)) {
|
||||
// Invalid subkey binding signature
|
||||
continue;
|
||||
}
|
||||
|
||||
mostCurrentValidSig = signature;
|
||||
}
|
||||
|
||||
return mostCurrentValidSig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick the, at validationDate latest subkey binding signature for the given subkey.
|
||||
* This method might return an expired signature.
|
||||
* If a non-expired subkey binding signature exists, the latest non-expired yet already effective
|
||||
* subkey binding signature for the given subkey will be returned.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param subkey subkey
|
||||
* @param validationDate validationDate
|
||||
* @return subkey binding signature
|
||||
*/
|
||||
public static PGPSignature pickLatestSubkeyBindingSignature(PGPKeyRing keyRing, PGPPublicKey subkey, Policy policy, Date validationDate) {
|
||||
PGPPublicKey primaryKey = keyRing.getPublicKey();
|
||||
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||
throw new IllegalArgumentException("Primary key cannot have subkey binding signature.");
|
||||
}
|
||||
|
||||
List<PGPSignature> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
|
||||
PGPSignature latestSubkeyBinding = null;
|
||||
|
||||
for (PGPSignature signature : signatures) {
|
||||
try {
|
||||
SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(signature);
|
||||
SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||
SignatureValidator.signatureIsAlreadyEffective(validationDate).verify(signature);
|
||||
// if the currently latest signature is not yet expired, check if the next candidate is not yet expired
|
||||
if (latestSubkeyBinding != null && !SignatureUtils.isSignatureExpired(latestSubkeyBinding, validationDate)) {
|
||||
SignatureValidator.signatureIsNotYetExpired(validationDate).verify(signature);
|
||||
}
|
||||
SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(signature);
|
||||
} catch (SignatureValidationException e) {
|
||||
// Subkey binding sig is not valid
|
||||
continue;
|
||||
}
|
||||
latestSubkeyBinding = signature;
|
||||
}
|
||||
|
||||
return latestSubkeyBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all signatures of the given {@link SignatureType} on the given key, sorted using a
|
||||
* {@link SignatureCreationDateComparator}.
|
||||
*
|
||||
* The returned list will be sorted first by ascending signature creation time.
|
||||
*
|
||||
* @param key key
|
||||
* @param type type of signatures which shall be collected and sorted
|
||||
* @return sorted list of signatures
|
||||
*/
|
||||
private static List<PGPSignature> getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) {
|
||||
Iterator<PGPSignature> signaturesOfType = key.getSignaturesOfType(type.getCode());
|
||||
List<PGPSignature> signatureList = CollectionUtils.iteratorToList(signaturesOfType);
|
||||
|
|
|
@ -178,7 +178,7 @@ public abstract class SignatureValidator {
|
|||
signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
|
||||
signatureIsEffective(validationDate).verify(signature);
|
||||
hasValidPrimaryKeyBindingSignatureIfRequired(primaryKey, subkey, policy, validationDate).verify(signature);
|
||||
correctSignatureOverKey(primaryKey, subkey).verify(signature);
|
||||
correctSubkeyBindingSignature(primaryKey, subkey).verify(signature);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -274,7 +274,24 @@ public abstract class SignatureValidator {
|
|||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
HashAlgorithm hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy);
|
||||
|
||||
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
|
||||
throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + hashAlgorithm);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the applicable {@link Policy.HashAlgorithmPolicy} for the given {@link PGPSignature}.
|
||||
* Revocation signatures are being policed using a different policy than non-revocation signatures.
|
||||
*
|
||||
* @param signature signature
|
||||
* @param policy revocation policy for revocation sigs, normal policy for non-rev sigs
|
||||
* @return policy
|
||||
*/
|
||||
private static Policy.HashAlgorithmPolicy getHashAlgorithmPolicyForSignature(PGPSignature signature, Policy policy) {
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = null;
|
||||
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||
if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || type == SignatureType.SUBKEY_REVOCATION) {
|
||||
|
@ -282,12 +299,7 @@ public abstract class SignatureValidator {
|
|||
} else {
|
||||
hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||
}
|
||||
|
||||
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
|
||||
throw new SignatureValidationException("Signature uses inacceptable hash algorithm " + hashAlgorithm);
|
||||
}
|
||||
}
|
||||
};
|
||||
return hashAlgorithmPolicy;
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) {
|
||||
|
@ -324,16 +336,40 @@ public abstract class SignatureValidator {
|
|||
}
|
||||
|
||||
public static SignatureValidator signatureIsEffective(Date validationDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
signatureIsAlreadyEffective(validationDate).verify(signature);
|
||||
signatureIsNotYetExpired(validationDate).verify(signature);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureIsAlreadyEffective(Date validationDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
Date signatureCreationTime = SignatureSubpacketsUtil.getSignatureCreationTime(signature).getTime();
|
||||
// For hard revocations, skip the creation time check
|
||||
if (!SignatureUtils.isHardRevocation(signature)) {
|
||||
// Hard revocations are always effective
|
||||
if (SignatureUtils.isHardRevocation(signature)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signatureCreationTime.after(validationDate)) {
|
||||
throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureIsNotYetExpired(Date validationDate) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
// Hard revocations do not expire
|
||||
if (SignatureUtils.isHardRevocation(signature)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date signatureExpirationTime = SignatureSubpacketsUtil.getSignatureExpirationTimeAsDate(signature);
|
||||
if (signatureExpirationTime != null && signatureExpirationTime.before(validationDate)) {
|
||||
|
@ -420,6 +456,26 @@ public abstract class SignatureValidator {
|
|||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator correctSubkeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
if (primaryKey.getKeyID() == subkey.getKeyID()) {
|
||||
throw new SignatureValidationException("Primary key cannot be its own subkey.");
|
||||
}
|
||||
try {
|
||||
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), primaryKey);
|
||||
boolean valid = signature.verifyCertification(primaryKey, subkey);
|
||||
if (!valid) {
|
||||
throw new SignatureValidationException("Signature is not correct.");
|
||||
}
|
||||
} catch (PGPException e) {
|
||||
throw new SignatureValidationException("Cannot verify subkey binding signature correctness", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static SignatureValidator correctPrimaryKeyBindingSignature(PGPPublicKey primaryKey, PGPPublicKey subkey) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Utility class of an immutable list which cannot be empty.
|
||||
* The first element can be accessed via {@link #get()} which is guaranteed to return a non-null value.
|
||||
* The rest of the list can be accessed via {@link #getOthers()}, which is guaranteed to return a non-null list which is possibly empty.
|
||||
* Lastly, the whole list can be accessed via {@link #getAll()}, which is guaranteed to return a non-empty list.
|
||||
*
|
||||
* @param <E> element type
|
||||
*/
|
||||
public class NonEmptyList<E> {
|
||||
|
||||
private final List<E> elements;
|
||||
|
||||
/**
|
||||
* Create a singleton list from the given element.
|
||||
*
|
||||
* @param element element
|
||||
*/
|
||||
public NonEmptyList(E element) {
|
||||
if (element == null) {
|
||||
throw new IllegalArgumentException("Singleton element cannot be null.");
|
||||
}
|
||||
this.elements = Collections.singletonList(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a non-empty list from the given list of elements.
|
||||
*
|
||||
* @param elements elements
|
||||
* @throws IllegalArgumentException if the provided list of elements is empty.
|
||||
*/
|
||||
public NonEmptyList(List<E> elements) {
|
||||
if (elements.isEmpty()) {
|
||||
throw new IllegalArgumentException("Underlying list cannot be empty.");
|
||||
}
|
||||
this.elements = Collections.unmodifiableList(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first element of the list.
|
||||
*
|
||||
* @return first
|
||||
*/
|
||||
public @Nonnull E get() {
|
||||
return elements.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all elements of the list except the first.
|
||||
*
|
||||
* @return list of all but the first element
|
||||
*/
|
||||
public @Nonnull List<E> getOthers() {
|
||||
List<E> others = new LinkedList<>(elements);
|
||||
others.remove(0);
|
||||
return Collections.unmodifiableList(others);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a non-empty list of all elements of this list.
|
||||
*
|
||||
* @return all elements
|
||||
*/
|
||||
public List<E> getAll() {
|
||||
return elements;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.util;
|
||||
|
||||
public class SignatureTree {
|
||||
|
||||
public interface Node {
|
||||
long getKeyId();
|
||||
}
|
||||
|
||||
public interface PrimaryKeyNode extends Node {
|
||||
|
||||
}
|
||||
|
||||
public interface SubkeyNode extends Node {
|
||||
|
||||
}
|
||||
|
||||
public interface SignatureNode extends Node {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class NonEmptyListTest {
|
||||
|
||||
@Test
|
||||
public void testEmptyListThrows() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new NonEmptyList<>(Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleElementList() {
|
||||
List<String> singleElement = Collections.singletonList("Hello");
|
||||
NonEmptyList<String> nonEmpty = new NonEmptyList<>(singleElement);
|
||||
assertEquals("Hello", nonEmpty.get());
|
||||
assertTrue(nonEmpty.getOthers().isEmpty());
|
||||
assertEquals(1, nonEmpty.getAll().size());
|
||||
assertTrue(nonEmpty.getAll().contains("Hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingletonElement() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new NonEmptyList<>((String) null));
|
||||
NonEmptyList<String> nonEmpty = new NonEmptyList<>("Foo");
|
||||
assertEquals("Foo", nonEmpty.get());
|
||||
assertTrue(nonEmpty.getOthers().isEmpty());
|
||||
assertEquals(Collections.singletonList("Foo"), nonEmpty.getAll());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleElements() {
|
||||
List<String> multipleElements = Arrays.asList("Foo", "Bar", "Baz");
|
||||
NonEmptyList<String> nonEmpty = new NonEmptyList<>(multipleElements);
|
||||
assertEquals("Foo", nonEmpty.get());
|
||||
assertEquals(Arrays.asList("Bar", "Baz"), nonEmpty.getOthers());
|
||||
assertEquals(multipleElements, nonEmpty.getAll());
|
||||
}
|
||||
}
|
|
@ -22,28 +22,22 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
||||
import org.pgpainless.encryption_signing.EncryptionOptions;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
||||
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import picocli.CommandLine;
|
||||
|
||||
|
@ -90,96 +84,57 @@ public class Encrypt implements Runnable {
|
|||
System.exit(19);
|
||||
}
|
||||
|
||||
PGPPublicKeyRing[] pubKeysArray = new PGPPublicKeyRing[certs.length];
|
||||
EncryptionOptions encOpt = new EncryptionOptions();
|
||||
SigningOptions signOpt = new SigningOptions();
|
||||
|
||||
for (int i = 0 ; i < certs.length; i++) {
|
||||
try (InputStream fileIn = new FileInputStream(certs[i])) {
|
||||
PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(fileIn);
|
||||
pubKeysArray[i] = publicKey;
|
||||
encOpt.addRecipient(publicKey);
|
||||
} catch (IOException e) {
|
||||
err_ln("Cannot read certificate " + certs[i].getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
PGPPublicKeyRingCollection publicKeys;
|
||||
try {
|
||||
publicKeys = new PGPPublicKeyRingCollection(Arrays.asList(pubKeysArray));
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot construct public key collection.");
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
return;
|
||||
|
||||
for (int i = 0; i < withPassword.length; i++) {
|
||||
Passphrase passphrase = Passphrase.fromPassword(withPassword[i]);
|
||||
encOpt.addPassphrase(passphrase);
|
||||
}
|
||||
PGPSecretKeyRing[] secretKeys = new PGPSecretKeyRing[signWith.length];
|
||||
|
||||
final Scanner scanner = new Scanner(System.in);
|
||||
for (int i = 0; i < signWith.length; i++) {
|
||||
try (FileInputStream fileIn = new FileInputStream(signWith[i])) {
|
||||
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(fileIn);
|
||||
secretKeys[i] = secretKey;
|
||||
SecretKeyRingProtector protector = SecretKeyRingProtector.defaultSecretKeyRingProtector(
|
||||
new SecretKeyPassphraseProvider() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Passphrase getPassphraseFor(Long keyId) {
|
||||
print_ln("Please provide the passphrase for key " + new OpenPgpV4Fingerprint(secretKey));
|
||||
String password = scanner.nextLine();
|
||||
Passphrase passphrase = Passphrase.fromPassword(password.trim());
|
||||
return passphrase;
|
||||
}
|
||||
}
|
||||
);
|
||||
signOpt.addInlineSignature(protector, secretKey,
|
||||
type == Type.text || type == Type.mime ?
|
||||
DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT);
|
||||
} catch (IOException | PGPException e) {
|
||||
err_ln("Cannot read secret key from file " + signWith[i].getName());
|
||||
err_ln(e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
Passphrase[] passphraseArray = new Passphrase[withPassword.length];
|
||||
for (int i = 0; i < withPassword.length; i++) {
|
||||
String password = withPassword[i];
|
||||
passphraseArray[i] = Passphrase.fromPassword(password);
|
||||
}
|
||||
|
||||
Map<Long, Passphrase> passphraseMap = new HashMap<>();
|
||||
Scanner scanner = null;
|
||||
for (PGPSecretKeyRing ring : secretKeys) {
|
||||
for (PGPSecretKey key : ring) {
|
||||
// Skip non-signing keys
|
||||
PGPSignature signature = (PGPSignature) key.getPublicKey().getSignatures().next();
|
||||
int flags = signature.getHashedSubPackets().getKeyFlags();
|
||||
if (!key.isSigningKey() || !KeyFlag.hasKeyFlag(flags, KeyFlag.SIGN_DATA)) {
|
||||
// Key cannot sign
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.getKeyEncryptionAlgorithm() == SymmetricKeyAlgorithm.NULL.getAlgorithmId()) {
|
||||
passphraseMap.put(key.getKeyID(), Passphrase.emptyPassphrase());
|
||||
} else {
|
||||
print_ln("Please provide the passphrase for key " + new OpenPgpV4Fingerprint(key));
|
||||
if (scanner == null) {
|
||||
scanner = new Scanner(System.in);
|
||||
}
|
||||
String password = scanner.nextLine();
|
||||
Passphrase passphrase = Passphrase.fromPassword(password.trim());
|
||||
passphraseMap.put(key.getKeyID(), passphrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionBuilderInterface.ToRecipientsOrSign builder = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.toRecipients(publicKeys)
|
||||
.and();
|
||||
for (Passphrase passphrase : passphraseArray) {
|
||||
builder = builder.forPassphrase(passphrase).and();
|
||||
}
|
||||
EncryptionBuilderInterface.Armor builder_armor = null;
|
||||
EncryptionBuilderInterface.SignWith builder1 = builder;
|
||||
try {
|
||||
if (signWith.length != 0) {
|
||||
for (int i = 0; i < signWith.length; i++) {
|
||||
PGPSecretKeyRing secretKeyRing = secretKeys[i];
|
||||
EncryptionBuilderInterface.AdditionalSignWith additionalSignWith = builder1.signInlineWith(
|
||||
SecretKeyRingProtector.unlockAllKeysWith(
|
||||
passphraseMap.get(secretKeyRing.getPublicKey().getKeyID()),
|
||||
secretKeyRing),
|
||||
secretKeyRing, null,
|
||||
type == Type.text || type == Type.mime ?
|
||||
DocumentSignatureType.CANONICAL_TEXT_DOCUMENT : DocumentSignatureType.BINARY_DOCUMENT);
|
||||
builder_armor = additionalSignWith;
|
||||
builder1 = additionalSignWith.and();
|
||||
}
|
||||
} else {
|
||||
builder_armor = builder.doNotSign();
|
||||
}
|
||||
EncryptionStream encryptionStream = !armor ? builder_armor.noArmor() : builder_armor.asciiArmor();
|
||||
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(System.out)
|
||||
.withOptions(ProducerOptions
|
||||
.signAndEncrypt(encOpt, signOpt)
|
||||
.setAsciiArmor(armor));
|
||||
|
||||
Streams.pipeAll(System.in, encryptionStream);
|
||||
|
||||
|
|
Loading…
Reference in a new issue