mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-10 18:59:39 +02: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.
|
||||
try {
|
||||
SignatureValidator.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// User-id revocation is not valid
|
||||
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
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidCertificationRevocationSignature(primaryKey, userId)
|
||||
.accept(signature, primaryKey, keyRing)) {
|
||||
// sig does not check out for userid
|
||||
continue;
|
||||
}
|
||||
|
||||
mostRecentUserIdRevocation = signature;
|
||||
latestUserIdRevocation = 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
|
||||
for (PGPSignature signature : signatures) {
|
||||
try {
|
||||
SignatureValidator.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate);
|
||||
} catch (SignatureValidationException e) {
|
||||
// subkey binding revocation is not valid
|
||||
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
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SelectSignatureFromKey.isValidSubkeyRevocationSignature().accept(signature, subkey, keyRing)) {
|
||||
// Signature does not check out
|
||||
continue;
|
||||
}
|
||||
mostRecentSubkeyRevocation = signature;
|
||||
latestSubkeyRevocation = 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,22 +274,34 @@ public abstract class SignatureValidator {
|
|||
@Override
|
||||
public void verify(PGPSignature signature) throws SignatureValidationException {
|
||||
HashAlgorithm hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
|
||||
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = null;
|
||||
SignatureType type = SignatureType.valueOf(signature.getSignatureType());
|
||||
if (type == SignatureType.CERTIFICATION_REVOCATION || type == SignatureType.KEY_REVOCATION || type == SignatureType.SUBKEY_REVOCATION) {
|
||||
hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy();
|
||||
} else {
|
||||
hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||
}
|
||||
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy);
|
||||
|
||||
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
|
||||
throw new SignatureValidationException("Signature uses inacceptable hash algorithm " + hashAlgorithm);
|
||||
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) {
|
||||
hashAlgorithmPolicy = policy.getRevocationSignatureHashAlgorithmPolicy();
|
||||
} else {
|
||||
hashAlgorithmPolicy = policy.getSignatureHashAlgorithmPolicy();
|
||||
}
|
||||
return hashAlgorithmPolicy;
|
||||
}
|
||||
|
||||
public static SignatureValidator signatureDoesNotHaveCriticalUnknownNotations(NotationRegistry registry) {
|
||||
return new SignatureValidator() {
|
||||
@Override
|
||||
|
@ -324,15 +336,39 @@ 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)) {
|
||||
if (signatureCreationTime.after(validationDate)) {
|
||||
throw new SignatureValidationException("Signature was created at " + signatureCreationTime + " and is therefore not yet valid at " + validationDate);
|
||||
}
|
||||
// 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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue