pgpainless/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignaturePicker.kt

384 lines
16 KiB
Kotlin

// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.signature.consumer
import java.util.Date
import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSignature
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.bouncycastle.extensions.getPublicKeyFor
import org.pgpainless.bouncycastle.extensions.isExpired
import org.pgpainless.bouncycastle.extensions.wasIssuedBy
import org.pgpainless.exception.SignatureValidationException
import org.pgpainless.policy.Policy
/**
* 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<PGPSignature> =
key.getSignaturesOfType(type.code)
.asSequence()
.sortedWith(SignatureCreationDateComparator())
.toList()
}
}