pgpainless/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java

396 lines
17 KiB
Java

// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.signature.consumer;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.policy.Policy;
import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.util.CollectionUtils;
/**
* Pick signatures from keys.
*
* The format of a V4 OpenPGP key is:
*
* Primary-Key
* [Revocation Self Signature]
* [Direct Key Signature...]
* User ID [Signature ...]
* [User ID [Signature ...] ...]
* [User Attribute [Signature ...] ...]
* [[Subkey [Binding-Signature-Revocation] Primary-Key-Binding-Signature] ...]
*/
public final class SignaturePicker {
private SignaturePicker() {
}
/**
* 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
* @param policy policy
* @param validationDate date of signature validation
* @return most recent, valid key revocation signature
*/
public static PGPSignature pickCurrentRevocationSelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
PGPPublicKey primaryKey = keyRing.getPublicKey();
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION);
PGPSignature mostCurrentValidSig = null;
for (PGPSignature signature : signatures) {
try {
SignatureVerifier.verifyKeyRevocationSignature(signature, primaryKey, policy, validationDate);
} catch (SignatureValidationException e) {
// Signature is not valid
continue;
}
mostCurrentValidSig = signature;
}
return mostCurrentValidSig;
}
/**
* 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 policy policy
* @param validationDate validation date
* @return direct-key self-signature
*/
public static PGPSignature pickCurrentDirectKeySelfSignature(PGPKeyRing keyRing, Policy policy, Date validationDate) {
PGPPublicKey primaryKey = keyRing.getPublicKey();
return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, validationDate);
}
/**
* 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 policy policy
* @param validationDate validation date
* @return direct key sig
*/
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) {
try {
SignatureVerifier.verifyDirectKeySignature(signature, signingKey, signedKey, policy, validationDate);
} catch (SignatureValidationException e) {
// Direct key sig is not valid
continue;
}
mostRecentDirectKeySigBySigningKey = signature;
}
return mostRecentDirectKeySigBySigningKey;
}
/**
* 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 policy policy
* @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 policy policy
* @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 policy policy
* @param validationDate validation date
* @return revocation signature
*/
public static PGPSignature pickCurrentUserIdRevocationSignature(PGPKeyRing keyRing, String userId, Policy policy, Date validationDate) {
PGPPublicKey primaryKey = keyRing.getPublicKey();
List<PGPSignature> signatures = getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION);
PGPSignature latestUserIdRevocation = null;
for (PGPSignature signature : signatures) {
PGPPublicKey signer = keyRing.getPublicKey(signature.getKeyID());
if (signer == null) {
// Signature made by external key. Skip.
continue;
}
try {
SignatureVerifier.verifyUserIdRevocation(userId, signature, primaryKey, policy, validationDate);
} catch (SignatureValidationException e) {
// User-id revocation is not valid
continue;
}
latestUserIdRevocation = signature;
}
return latestUserIdRevocation;
}
/**
* 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 policy policy
* @param validationDate validation date
* @return user-id certification
*/
public static PGPSignature pickCurrentUserIdCertificationSignature(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 mostRecentUserIdCertification = null;
for (PGPSignature signature : signatures) {
if (primaryKey.getKeyID() != signature.getKeyID()) {
// Signature not made by primary key
continue;
}
try {
SignatureVerifier.verifyUserIdCertification(userId, signature, primaryKey, policy, validationDate);
} catch (SignatureValidationException e) {
// User-id certification is not valid
continue;
}
mostRecentUserIdCertification = signature;
}
return mostRecentUserIdCertification;
}
/**
* 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 policy policy
* @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.wasPossiblyMadeByKey(primaryKey).verify(signature);
SignatureValidator.signatureIsCertification().verify(signature);
SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(signature);
SignatureValidator.signatureIsAlreadyEffective(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 policy policy
* @param validationDate validation date
* @return subkey revocation signature
*/
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> signatures = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION);
PGPSignature latestSubkeyRevocation = null;
for (PGPSignature signature : signatures) {
try {
SignatureVerifier.verifySubkeyBindingRevocation(signature, primaryKey, subkey, policy, validationDate);
} catch (SignatureValidationException e) {
// subkey binding revocation is not valid
continue;
}
latestSubkeyRevocation = signature;
}
return latestSubkeyRevocation;
}
/**
* 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 policy policy
* @param validationDate date of validation
* @return most recent valid subkey binding signature
*/
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.");
}
List<PGPSignature> subkeyBindingSigs = getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING);
PGPSignature mostCurrentValidSig = null;
for (PGPSignature signature : subkeyBindingSigs) {
try {
SignatureVerifier.verifySubkeyBindingSignature(signature, primaryKey, subkey, policy, validationDate);
} catch (SignatureValidationException validationException) {
// Subkey binding sig is not valid
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 policy policy
* @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.signatureDoesNotPredateSignee(subkey).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);
Collections.sort(signatureList, new SignatureCreationDateComparator());
return signatureList;
}
}