From 6b397a0d568488a5935c7516bb676c6b7b01747b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 15:38:30 +0200 Subject: [PATCH] Kotlin conversion: SignaturePicker --- .../signature/consumer/SignaturePicker.java | 395 ------------------ .../extensions/PGPSignatureExtensions.kt | 4 + .../signature/consumer/SignaturePicker.kt | 310 ++++++++++++++ 3 files changed, 314 insertions(+), 395 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java b/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java deleted file mode 100644 index e6f2f755..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/consumer/SignaturePicker.java +++ /dev/null @@ -1,395 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// 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 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 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 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 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 userIdSigIterator = primaryKey.getSignaturesForID(userId); - List 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 userIdSigIterator = primaryKey.getSignaturesForID(userId); - List 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 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 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 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 getSortedSignaturesOfType(PGPPublicKey key, SignatureType type) { - Iterator signaturesOfType = key.getSignaturesOfType(type.getCode()); - List signatureList = CollectionUtils.iteratorToList(signaturesOfType); - Collections.sort(signatureList, new SignatureCreationDateComparator()); - return signatureList; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt index a27e68e0..4fe97bc7 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -5,6 +5,7 @@ package org.bouncycastle.extensions import openpgp.plusSeconds +import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.RevocationState import org.pgpainless.algorithm.SignatureType @@ -69,6 +70,9 @@ fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean = false } +fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = + wasIssuedBy(OpenPgpFingerprint.of(key)) + /** * Return true, if this signature is a hard revocation. */ diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt new file mode 100644 index 00000000..9c5b9a8d --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt @@ -0,0 +1,310 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.signature.consumer + +import org.bouncycastle.extensions.getPublicKeyFor +import org.bouncycastle.extensions.hasPublicKey +import org.bouncycastle.extensions.isExpired +import org.bouncycastle.extensions.wasIssuedBy +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 java.util.Date +import kotlin.math.sign + +/** + * 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] ...] + */ +class SignaturePicker { + + companion object { + + /** + * 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 referenceTime date of signature validation + * @return most recent, valid key revocation signature + */ + @JvmStatic + fun pickCurrentRevocationSelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return getSortedSignaturesOfType(primaryKey, SignatureType.KEY_REVOCATION).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifyKeyRevocationSignature(it, primaryKey, policy, referenceTime) + true // valid + } catch (e : SignatureValidationException) { + false // not valid + } + } + } + + /** + * 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 referenceTime validation date + * @return direct-key self-signature + */ + @JvmStatic + fun pickCurrentDirectKeySelfSignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return pickCurrentDirectKeySignature(primaryKey, primaryKey, policy, referenceTime) + } + + @JvmStatic + fun pickCurrentDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifyDirectKeySignature(it, signingKey, signedKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime validation date + * @return latest direct key signature + */ + @JvmStatic + fun pickLatestDirectKeySignature(keyRing: PGPKeyRing, policy: Policy, referenceTime: Date): PGPSignature? { + return pickLatestDirectKeySignature(keyRing.publicKey, keyRing.publicKey, policy, referenceTime) + } + + /** + * 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 referenceTime date of validation + * @return latest direct key sig + */ + @JvmStatic + fun pickLatestDirectKeySignature(signingKey: PGPPublicKey, signedKey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + var latest: PGPSignature? = null + return getSortedSignaturesOfType(signedKey, SignatureType.DIRECT_KEY).lastOrNull { + try { + SignatureValidator.signatureIsOfType(SignatureType.DIRECT_KEY).verify(it) + SignatureValidator.signatureStructureIsAcceptable(signingKey, policy).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + if (latest != null && !latest!!.isExpired(referenceTime)) { + SignatureValidator.signatureIsNotYetExpired(referenceTime).verify(it) + } + SignatureValidator.correctSignatureOverKey(signingKey, signedKey).verify(it) + latest = it + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime validation date + * @return revocation signature + */ + @JvmStatic + fun pickCurrentUserIdRevocationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return getSortedSignaturesOfType(primaryKey, SignatureType.CERTIFICATION_REVOCATION).lastOrNull { + keyRing.getPublicKeyFor(it) ?: return@lastOrNull false // signature made by external key. skip. + return@lastOrNull try { + SignatureVerifier.verifyUserIdRevocation(userId.toString(), it, primaryKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false // signature not valid + } + } + } + + /** + * 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 referenceTime validation date + * @return user-id certification + */ + @JvmStatic + fun pickCurrentUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return primaryKey.getSignaturesForID(userId.toString()).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull it.wasIssuedBy(primaryKey) && try { + SignatureVerifier.verifyUserIdCertification(userId.toString(), it, primaryKey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime validation date + * @return user-id certification + */ + @JvmStatic + fun pickLatestUserIdCertificationSignature(keyRing: PGPKeyRing, userId: CharSequence, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + return primaryKey.getSignaturesForID(userId.toString()).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .lastOrNull { + return@lastOrNull try { + SignatureValidator.wasPossiblyMadeByKey(primaryKey).verify(it) + SignatureValidator.signatureIsCertification().verify(it) + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + SignatureValidator.correctSignatureOverUserId(userId.toString(), primaryKey, primaryKey).verify(it) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime validation date + * @return subkey revocation signature + */ + @JvmStatic + fun pickCurrentSubkeyBindingRevocationSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding revocations." } + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_REVOCATION).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifySubkeyBindingRevocation(it, primaryKey, subkey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime date of validation + * @return most recent valid subkey binding signature + */ + @JvmStatic + fun pickCurrentSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { + return@lastOrNull try { + SignatureVerifier.verifySubkeyBindingSignature(it, primaryKey, subkey, policy, referenceTime) + true + } catch (e : SignatureValidationException) { + false + } + } + } + + /** + * 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 referenceTime validationDate + * @return subkey binding signature + */ + @JvmStatic + fun pickLatestSubkeyBindingSignature(keyRing: PGPKeyRing, subkey: PGPPublicKey, policy: Policy, referenceTime: Date): PGPSignature? { + val primaryKey = keyRing.publicKey + require(primaryKey.keyID != subkey.keyID) { "Primary key cannot have subkey binding signatures." } + var latest: PGPSignature? = null + return getSortedSignaturesOfType(subkey, SignatureType.SUBKEY_BINDING).lastOrNull { + return@lastOrNull try { + SignatureValidator.signatureIsOfType(SignatureType.SUBKEY_BINDING).verify(it) + SignatureValidator.signatureStructureIsAcceptable(primaryKey, policy).verify(it) + SignatureValidator.signatureDoesNotPredateSignee(subkey).verify(it) + SignatureValidator.signatureIsAlreadyEffective(referenceTime).verify(it) + // if the currently latest signature is not yet expired, check if the next candidate is not yet expired + if (latest != null && !latest!!.isExpired(referenceTime)) { + SignatureValidator.signatureIsNotYetExpired(referenceTime).verify(it) + } + SignatureValidator.correctSubkeyBindingSignature(primaryKey, subkey).verify(it) + latest = it + true + } catch (e : SignatureValidationException) { + false + } + } + } + + @JvmStatic + private fun getSortedSignaturesOfType(key: PGPPublicKey, type: SignatureType): List = + key.getSignaturesOfType(type.code).asSequence() + .sortedWith(SignatureCreationDateComparator()) + .toList() + } + +} \ No newline at end of file