diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 6671dcb9..aaa6a590 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -15,6 +15,9 @@ */ package org.pgpainless.key.info; +import static org.pgpainless.key.util.SignatureUtils.getLatestValidSignature; +import static org.pgpainless.key.util.SignatureUtils.sortByCreationTimeAscending; + import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -23,6 +26,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -33,6 +37,8 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.key.util.SignatureUtils; /** * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. @@ -56,6 +62,18 @@ public class KeyRingInfo { return keys.getPublicKey(); } + public PGPPublicKey getPublicKey(OpenPgpV4Fingerprint fingerprint) { + return getPublicKey(fingerprint.getKeyId()); + } + + public PGPPublicKey getPublicKey(long keyId) { + return keys.getPublicKey(keyId); + } + + public static PGPPublicKey getPublicKey(PGPKeyRing keyRing, long keyId) { + return keyRing.getPublicKey(keyId); + } + /** * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. * The first key in the list being the primary key. @@ -85,6 +103,17 @@ public class KeyRingInfo { return null; } + public PGPSecretKey getSecretKey(OpenPgpV4Fingerprint fingerprint) { + return getSecretKey(fingerprint.getKeyId()); + } + + public PGPSecretKey getSecretKey(long keyId) { + if (keys instanceof PGPSecretKeyRing) { + return ((PGPSecretKeyRing) keys).getSecretKey(keyId); + } + return null; + } + /** * Return all secret keys of the key ring. * If the key ring is a {@link PGPPublicKeyRing}, then return an empty list. @@ -136,6 +165,25 @@ public class KeyRingInfo { return userIds; } + public List getValidUserIds() { + List valid = new ArrayList<>(); + List userIds = getUserIds(); + for (String userId : userIds) { + if (isUserIdValid(userId)) { + valid.add(userId); + } + } + return valid; + } + + public boolean isUserIdValid(String userId) { + try { + return SignatureUtils.isUserIdValid(getPublicKey(), userId); + } catch (PGPException e) { + return false; + } + } + /** * Return a list of all user-ids of the primary key that appear to be email-addresses. * @@ -259,4 +307,77 @@ public class KeyRingInfo { } return true; } + + public List getSelfSignatures() { + return getSelfSignatures(new OpenPgpV4Fingerprint(getPublicKey())); + } + + public List getSelfSignatures(OpenPgpV4Fingerprint subkeyFingerprint) { + return getSelfSignatures(subkeyFingerprint.getKeyId()); + } + + public List getSelfSignatures(long subkeyId) { + PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); + Iterator it = publicKey.getSignaturesForKeyID(subkeyId); + List signatures = new ArrayList<>(); + while (it.hasNext()) { + signatures.add(it.next()); + } + sortByCreationTimeAscending(signatures); + return signatures; + } + + public PGPSignature getLatestValidSelfSignature() throws PGPException { + return getLatestValidSelfSignature(new OpenPgpV4Fingerprint(getPublicKey())); + } + + public PGPSignature getLatestValidSelfSignature(OpenPgpV4Fingerprint fingerprint) throws PGPException { + return getLatestValidSelfSignature(fingerprint.getKeyId()); + } + + public PGPSignature getLatestValidSelfSignature(long subkeyId) throws PGPException { + PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); + List signatures = getSelfSignatures(subkeyId); + return getLatestValidSignature(publicKey, signatures, keys); + } + + public List getBindingSignatures(OpenPgpV4Fingerprint fingerprint) { + return getBindingSignatures(fingerprint.getKeyId()); + } + + public List getBindingSignatures(long subkeyId) { + if (subkeyId == getKeyId()) { + return Collections.emptyList(); + } + PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); + return SignatureUtils.getBindingSignatures(publicKey, getKeyId()); + } + + public PGPSignature getLatestValidBindingSignature(long subKeyID) throws PGPException { + PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subKeyID); + List signatures = getBindingSignatures(subKeyID); + return getLatestValidSignature(publicKey, signatures, keys); + } + + public PGPSignature getLatestValidSelfOrBindingSignature() throws PGPException { + return getLatestValidSelfOrBindingSignature(new OpenPgpV4Fingerprint(getPublicKey())); + } + + public PGPSignature getLatestValidSelfOrBindingSignature(OpenPgpV4Fingerprint fingerprint) throws PGPException { + return getLatestValidSelfOrBindingSignature(fingerprint.getKeyId()); + } + + public PGPSignature getLatestValidSelfOrBindingSignature(long subKeyId) throws PGPException { + PGPSignature self = getLatestValidSelfSignature(subKeyId); + PGPSignature binding = getLatestValidBindingSignature(subKeyId); + if (self == null) { + return binding; + } + if (binding == null) { + return self; + } + return self.getCreationTime().after(binding.getCreationTime()) ? self : binding; + } + + } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java index 40b8004e..88830bc7 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java @@ -90,6 +90,26 @@ public class KeyRingUtils { return null; } + public static PGPPublicKey getPublicKeyFrom(PGPKeyRing keyRing, long subKeyId) { + return keyRing.getPublicKey(subKeyId); + } + + public static PGPPublicKey requirePublicKeyFrom(PGPKeyRing keyRing, long subKeyId) { + PGPPublicKey publicKey = getPublicKeyFrom(keyRing, subKeyId); + if (publicKey == null) { + throw new IllegalArgumentException("KeyRing does not contain public key with keyID " + Long.toHexString(subKeyId)); + } + return publicKey; + } + + public static PGPSecretKey requireSecretKeyFrom(PGPSecretKeyRing keyRing, long subKeyId) { + PGPSecretKey secretKey = keyRing.getSecretKey(subKeyId); + if (secretKey == null) { + throw new IllegalArgumentException("KeyRing does not contain secret key with keyID " + Long.toHexString(subKeyId)); + } + return secretKey; + } + /** * Extract a {@link PGPPublicKeyRing} containing all public keys from the provided {@link PGPSecretKeyRing}. * diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java index 5d36e4fc..89ef141f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/SignatureUtils.java @@ -15,13 +15,23 @@ */ package org.pgpainless.key.util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; import java.util.List; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.implementation.ImplementationFactory; public class SignatureUtils { @@ -51,4 +61,155 @@ public class SignatureUtils { } return preferredHashAlgorithms.get(0); } + + public static PGPSignature getLatestValidSignature(PGPPublicKey publicKey, List signatures, PGPKeyRing keyRing) throws PGPException { + List valid = new ArrayList<>(); + for (PGPSignature signature : signatures) { + long issuerID = signature.getKeyID(); + PGPPublicKey issuer = KeyRingUtils.getPublicKeyFrom(keyRing, issuerID); + if (issuer == null) { + continue; + } + + if (!isSignatureValid(signature, issuer, publicKey)) { + continue; + } + + if (isSignatureExpired(signature)) { + continue; + } + valid.add(signature); + } + sortByCreationTimeAscending(valid); + + return valid.isEmpty() ? null : valid.get(valid.size() - 1); + } + + public static boolean isSignatureValid(PGPSignature signature, PGPPublicKey issuer, PGPPublicKey target) throws PGPException { + SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType()); + switch (signatureType) { + case BINARY_DOCUMENT: + case CANONICAL_TEXT_DOCUMENT: + case STANDALONE: + case TIMESTAMP: + case THIRD_PARTY_CONFIRMATION: + throw new IllegalArgumentException("Signature is not a key signature."); + case GENERIC_CERTIFICATION: + case NO_CERTIFICATION: + case CASUAL_CERTIFICATION: + case POSITIVE_CERTIFICATION: + case DIRECT_KEY: + case KEY_REVOCATION: + case CERTIFICATION_REVOCATION: + return isSelfSignatureValid(signature, issuer); + case SUBKEY_BINDING: + case PRIMARYKEY_BINDING: + case SUBKEY_REVOCATION: + return isKeyOnKeySignatureValid(signature, issuer, target); + } + return false; + } + + public static boolean isKeyOnKeySignatureValid(PGPSignature signature, PGPPublicKey issuer, PGPPublicKey target) throws PGPException { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), issuer); + return signature.verifyCertification(issuer, target); + } + + public static boolean isSelfSignatureValid(PGPSignature signature, PGPPublicKey publicKey) throws PGPException { + switch (signature.getSignatureType()) { + case PGPSignature.POSITIVE_CERTIFICATION: + case PGPSignature.DEFAULT_CERTIFICATION: + for (Iterator it = publicKey.getUserIDs(); it.hasNext(); ) { + String userId = it.next(); + boolean valid = isSelfSignatureOnUserIdValid(signature, userId, publicKey); + if (valid) { + return true; + } + } + } + return false; + } + + public static boolean isSelfSignatureOnUserIdValid(PGPSignature signature, String userId, PGPPublicKey publicKey) throws PGPException { + signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), publicKey); + return signature.verifyCertification(userId, publicKey); + } + + public static boolean isSignatureExpired(PGPSignature signature) { + long expiration = signature.getHashedSubPackets().getSignatureExpirationTime(); + if (expiration == 0) { + return false; + } + Date now = new Date(); + Date creation = signature.getCreationTime(); + return now.after(new Date(creation.getTime() + 1000 * expiration)); + } + + public static void sortByCreationTimeAscending(List signatures) { + Collections.sort(signatures, new Comparator() { + @Override + public int compare(PGPSignature s1, PGPSignature s2) { + return s1.getCreationTime().compareTo(s2.getCreationTime()); + } + }); + } + + public static List getBindingSignatures(PGPPublicKey subKey, long primaryKeyId) { + List signatures = new ArrayList<>(); + List bindingSigs = getSignaturesOfTypes(subKey, SignatureType.SUBKEY_BINDING); + for (PGPSignature signature : bindingSigs) { + if (signature.getKeyID() != primaryKeyId) { + continue; + } + signatures.add(signature); + } + return signatures; + } + + public static List getSignaturesOfTypes(PGPPublicKey publicKey, SignatureType... types) { + List signatures = new ArrayList<>(); + for (SignatureType type : types) { + Iterator it = publicKey.getSignaturesOfType(type.getCode()); + while (it.hasNext()) { + Object o = it.next(); + if (o instanceof PGPSignature) { + signatures.add((PGPSignature) o); + } + } + } + sortByCreationTimeAscending(signatures); + return signatures; + } + + public static List getSignaturesForUserId(PGPPublicKey publicKey, String userId) { + List signatures = new ArrayList<>(); + Iterator it = publicKey.getSignaturesForID(userId); + while (it != null && it.hasNext()) { + Object o = it.next(); + if (o instanceof PGPSignature) { + signatures.add((PGPSignature) o); + } + } + sortByCreationTimeAscending(signatures); + return signatures; + } + + public static PGPSignature getLatestSelfSignatureForUserId(PGPPublicKey publicKey, String userId) throws PGPException { + List valid = new ArrayList<>(); + List signatures = getSignaturesForUserId(publicKey, userId); + for (PGPSignature signature : signatures) { + if (isSelfSignatureOnUserIdValid(signature, userId, publicKey)) { + valid.add(signature); + } + } + return valid.isEmpty() ? null : valid.get(valid.size() - 1); + } + + public static boolean isUserIdValid(PGPPublicKey publicKey, String userId) throws PGPException { + PGPSignature latestSelfSig = getLatestSelfSignatureForUserId(publicKey, userId); + if (latestSelfSig == null) { + return false; + } + return latestSelfSig.getSignatureType() != SignatureType.CERTIFICATION_REVOCATION.getCode(); + } }