pgpainless/pgpainless-core/src/main/kotlin/org/pgpainless/key/info/KeyRingInfo.kt

770 lines
29 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.info
import java.util.*
import openpgp.openPgpKeyId
import org.bouncycastle.openpgp.*
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.*
import org.pgpainless.bouncycastle.extensions.*
import org.pgpainless.exception.KeyException.UnboundUserIdException
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.policy.Policy
import org.pgpainless.signature.consumer.SignaturePicker
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate
import org.pgpainless.util.DateUtil
import org.slf4j.LoggerFactory
class KeyRingInfo(
val keys: PGPKeyRing,
val policy: Policy = PGPainless.getPolicy(),
val referenceDate: Date = Date()
) {
@JvmOverloads
constructor(
keys: PGPKeyRing,
referenceDate: Date = Date()
) : this(keys, PGPainless.getPolicy(), referenceDate)
private val signatures: Signatures = Signatures(keys, referenceDate, policy)
/** Primary {@link PGPPublicKey}.´ */
val publicKey: PGPPublicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys)
/** Primary key ID. */
val keyId: Long = publicKey.keyID
/** Primary key fingerprint. */
val fingerprint: OpenPgpFingerprint = OpenPgpFingerprint.of(keys)
/** All User-IDs (valid, expired, revoked). */
val userIds: List<String> = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey)
/** Primary User-ID. */
val primaryUserId = findPrimaryUserId()
/** Revocation State. */
val revocationState = signatures.primaryKeyRevocation.toRevocationState()
/**
* Return the date on which the primary key was revoked, or null if it has not yet been revoked.
*
* @return revocation date or null
*/
val revocationDate: Date? =
if (revocationState.isSoftRevocation()) revocationState.date else null
/**
* Primary [PGPSecretKey] of this key ring or null if the key ring is not a [PGPSecretKeyRing].
*/
val secretKey: PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.secretKey!!
else -> null
}
/** OpenPGP key version. */
val version: Int = publicKey.version
/**
* Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. The first key in the list
* being the primary key. Note that the list is unmodifiable.
*
* @return list of public keys
*/
val publicKeys: List<PGPPublicKey> = keys.publicKeys.asSequence().toList()
/** All secret keys. If the key ring is a [PGPPublicKeyRing], then return an empty list. */
val secretKeys: List<PGPSecretKey> =
when (keys) {
is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList()
else -> listOf()
}
/** List of valid public subkeys. */
val validSubkeys: List<PGPPublicKey> =
keys.publicKeys.asSequence().filter { isKeyValidlyBound(it.keyID) }.toList()
/** List of valid user-IDs. */
val validUserIds: List<String> = userIds.filter { isUserIdBound(it) }
/** List of valid and expired user-IDs. */
val validAndExpiredUserIds: List<String> =
userIds.filter {
val certification = signatures.userIdCertifications[it] ?: return@filter false
val revocation = signatures.userIdRevocations[it] ?: return@filter true
return@filter !revocation.isHardRevocation &&
certification.creationTime > revocation.creationTime
}
/** List of email addresses that can be extracted from the user-IDs. */
val emailAddresses: List<String> =
userIds.mapNotNull {
PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 ->
if (m1.find()) m1.group(1)
else
PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 ->
if (m2.find()) m2.group(1) else null
}
}
}
/** Newest direct-key self-signature on the primary key. */
val latestDirectKeySelfSignature: PGPSignature? = signatures.primaryKeySelfSignature
/** Newest primary-key revocation self-signature. */
val revocationSelfSignature: PGPSignature? = signatures.primaryKeyRevocation
/** Public-key encryption-algorithm of the primary key. */
val algorithm: PublicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.algorithm)
/** Creation date of the primary key. */
val creationDate: Date = publicKey.creationTime!!
/** Latest date at which the key was modified (either by adding a subkey or self-signature). */
val lastModified: Date = getMostRecentSignature()?.creationTime ?: getLatestKeyCreationDate()
/** True, if the underlying keyring is a [PGPSecretKeyRing]. */
val isSecretKey: Boolean = keys is PGPSecretKeyRing
/** True, if there are no encrypted secret keys. */
val isFullyDecrypted: Boolean =
!isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() }
/** True, if there are only encrypted secret keys. */
val isFullyEncrypted: Boolean =
isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() }
/** List of public keys, whose secret key counterparts can be used to decrypt messages. */
val decryptionSubkeys: List<PGPPublicKey> =
keys.publicKeys
.asSequence()
.filter {
if (it.keyID != keyId) {
if (signatures.subkeyBindings[it.keyID] == null) {
LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.")
return@filter false
}
}
if (!it.isEncryptionKey) {
LOGGER.debug("(Sub-?)Key ${it.keyID.openPgpKeyId()} is not encryption-capable.")
return@filter false
}
return@filter true
}
.toList()
/** Expiration date of the primary key. */
val primaryKeyExpirationDate: Date?
get() {
val directKeyExpirationDate: Date? =
latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) }
val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId()
val primaryUserIdCertification =
possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) }
val userIdExpirationDate: Date? =
primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) }
if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) {
/*
throw NoSuchElementException(
"No direct-key signature and no user-id signature found.")
*/
return null
}
if (directKeyExpirationDate != null && userIdExpirationDate == null) {
return directKeyExpirationDate
}
if (directKeyExpirationDate == null) {
return userIdExpirationDate
}
return if (directKeyExpirationDate < userIdExpirationDate) directKeyExpirationDate
else userIdExpirationDate
}
/** List of all subkeys that can be used to sign a message. */
val signingSubkeys: List<PGPPublicKey> =
validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) }
/** Whether the key is usable for encryption. */
val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY)
/**
* Whether the key is capable of signing messages. This field is also true, if the key contains
* a subkey that is capable of signing messages, but where the secret key is unavailable, e.g.
* because it was moved to a smart-card.
*
* To check for keys that are actually usable to sign messages, use [isUsableForSigning].
*/
val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty()
/** Whether the key is actually usable to sign messages. */
val isUsableForSigning: Boolean =
isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) }
/** [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. */
val preferredHashAlgorithms: Set<HashAlgorithm>
get() =
primaryUserId?.let { getPreferredHashAlgorithms(it) }
?: getPreferredHashAlgorithms(keyId)
/**
* [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key.
*/
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
get() =
primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) }
?: getPreferredSymmetricKeyAlgorithms(keyId)
/** [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. */
val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
get() =
primaryUserId?.let { getPreferredCompressionAlgorithms(it) }
?: getPreferredCompressionAlgorithms(keyId)
/**
* Return the expiration date of the subkey with the provided fingerprint.
*
* @param fingerprint subkey fingerprint
* @return expiration date or null
*/
fun getSubkeyExpirationDate(fingerprint: OpenPgpFingerprint): Date? {
return getSubkeyExpirationDate(fingerprint.keyId)
}
/**
* Return the expiration date of the subkey with the provided keyId.
*
* @param keyId subkey keyId
* @return expiration date
*/
fun getSubkeyExpirationDate(keyId: Long): Date? {
if (publicKey.keyID == keyId) return primaryKeyExpirationDate
val subkey =
getPublicKey(keyId)
?: throw NoSuchElementException(
"No subkey with key-ID ${keyId.openPgpKeyId()} found.")
val bindingSig =
getCurrentSubkeyBindingSignature(keyId)
?: throw AssertionError("Subkey has no valid binding signature.")
return bindingSig.getKeyExpirationDate(subkey.creationTime)
}
/**
* Return the date after which the key can no longer be used to perform the given use-case,
* caused by expiration.
*
* @return expiration date for the given use-case
*/
fun getExpirationDateForUse(use: KeyFlag): Date? {
require(use != KeyFlag.SPLIT && use != KeyFlag.SHARED) {
"SPLIT and SHARED are not uses, but properties."
}
val primaryKeyExpiration = primaryKeyExpirationDate
val keysWithFlag: List<PGPPublicKey> = getKeysWithKeyFlag(use)
if (keysWithFlag.isEmpty())
throw NoSuchElementException("No key with the required key flag found.")
var nonExpiring = false
val latestSubkeyExpiration =
keysWithFlag
.map { key ->
getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true }
}
.filterNotNull()
.maxByOrNull { it }
if (nonExpiring) return primaryKeyExpiration
return if (primaryKeyExpiration == null) latestSubkeyExpiration
else if (latestSubkeyExpiration == null) primaryKeyExpiration
else minOf(primaryKeyExpiration, latestSubkeyExpiration)
}
/**
* Return true, if the given user-ID is hard-revoked.
*
* @return true, if the given user-ID is hard-revoked.
*/
fun isHardRevoked(userId: CharSequence): Boolean {
return signatures.userIdRevocations[userId]?.isHardRevocation ?: false
}
/**
* Return a list of all keys which carry the provided key flag in their signature.
*
* @param flag flag
* @return keys with flag
*/
fun getKeysWithKeyFlag(flag: KeyFlag): List<PGPPublicKey> =
publicKeys.filter { getKeyFlagsOf(it.keyID).contains(flag) }
/**
* Return a list of all subkeys which can be used to encrypt a message for the given user-ID.
*
* @return encryption subkeys
*/
fun getEncryptionSubkeys(
userId: CharSequence?,
purpose: EncryptionPurpose
): List<PGPPublicKey> {
if (userId != null && !isUserIdValid(userId)) {
throw UnboundUserIdException(
OpenPgpFingerprint.of(keys),
userId.toString(),
getLatestUserIdCertification(userId),
getUserIdRevocation(userId))
}
return getEncryptionSubkeys(purpose)
}
/**
* Return a list of all subkeys which can be used to encrypt a message, given the purpose.
*
* @return subkeys which can be used for encryption
*/
fun getEncryptionSubkeys(purpose: EncryptionPurpose): List<PGPPublicKey> {
primaryKeyExpirationDate?.let {
if (it < referenceDate) {
LOGGER.debug(
"Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}")
return listOf()
}
}
return keys.publicKeys
.asSequence()
.filter {
if (!isKeyValidlyBound(it.keyID)) {
LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.")
return@filter false
}
getSubkeyExpirationDate(it.keyID)?.let { exp ->
if (exp < referenceDate) {
LOGGER.debug(
"(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.")
return@filter false
}
}
if (!it.isEncryptionKey) {
LOGGER.debug(
"(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.")
return@filter false
}
val keyFlags = getKeyFlagsOf(it.keyID)
when (purpose) {
EncryptionPurpose.COMMUNICATIONS ->
return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS)
EncryptionPurpose.STORAGE ->
return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)
EncryptionPurpose.ANY ->
return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) ||
keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)
}
}
.toList()
}
/**
* Return, whether the key is usable for encryption, given the purpose.
*
* @return true, if the key can be used to encrypt a message according to the
* encryption-purpose.
*/
fun isUsableForEncryption(purpose: EncryptionPurpose): Boolean {
return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty()
}
/**
* Return the primary user-ID, even if it is possibly expired.
*
* @return possibly expired primary user-ID
*/
fun getPossiblyExpiredPrimaryUserId(): String? =
primaryUserId
?: userIds
.mapNotNull { userId -> getLatestUserIdCertification(userId)?.let { userId to it } }
.sortedByDescending { it.second.creationTime }
.maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID }
?.first
/** Return the most-recently created self-signature on the key. */
private fun getMostRecentSignature(): PGPSignature? =
setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature)
.asSequence()
.plus(signatures.userIdCertifications.values)
.plus(signatures.userIdRevocations.values)
.plus(signatures.subkeyBindings.values)
.plus(signatures.subkeyRevocations.values)
.maxByOrNull { creationDate }
/**
* Return the creation time of the latest added subkey.
*
* @return latest key creation time
*/
fun getLatestKeyCreationDate(): Date =
validSubkeys.maxByOrNull { creationDate }?.creationTime
?: throw AssertionError("Apparently there is no validly bound key in this key ring.")
/**
* Return the latest certification self-signature for the given user-ID.
*
* @return latest self-certification for the given user-ID.
*/
fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? =
signatures.userIdCertifications[userId]
/**
* Return the latest revocation self-signature for the given user-ID
*
* @return latest user-ID revocation for the given user-ID
*/
fun getUserIdRevocation(userId: CharSequence): PGPSignature? =
signatures.userIdRevocations[userId]
/**
* Return the current binding signature for the subkey with the given key-ID.
*
* @return current subkey binding signature
*/
fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? =
signatures.subkeyBindings[keyId]
/**
* Return the current revocation signature for the subkey with the given key-ID.
*
* @return current subkey revocation signature
*/
fun getSubkeyRevocationSignature(keyId: Long): PGPSignature? =
signatures.subkeyRevocations[keyId]
/**
* Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id.
*
* @param keyId key-id
* @return list of key flags
*/
fun getKeyFlagsOf(keyId: Long): List<KeyFlag> =
if (keyId == publicKey.keyID) {
latestDirectKeySelfSignature?.let { sig ->
SignatureSubpacketsUtil.parseKeyFlags(sig)?.let { flags ->
return flags
}
}
primaryUserId?.let {
SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags
->
return flags
}
}
listOf()
} else {
getCurrentSubkeyBindingSignature(keyId)?.let {
SignatureSubpacketsUtil.parseKeyFlags(it)?.let { flags ->
return flags
}
}
listOf()
}
/**
* Return a list of {@link KeyFlag KeyFlags} that apply to the given user-id.
*
* @param userId user-id
* @return key flags
*/
fun getKeyFlagsOf(userId: CharSequence): List<KeyFlag> =
if (!isUserIdValid(userId)) {
listOf()
} else {
getLatestUserIdCertification(userId)?.let {
SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf()
}
?: throw AssertionError(
"While user-id '$userId' was reported as valid, there appears to be no certification for it.")
}
/**
* Return the public key with the given key id from the provided key ring.
*
* @param keyId key id
* @return public key or null
*/
fun getPublicKey(keyId: Long): PGPPublicKey? = keys.getPublicKey(keyId)
/**
* Return the secret key with the given key id.
*
* @param keyId key id
* @return secret key or null
*/
fun getSecretKey(keyId: Long): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.getSecretKey(keyId)
else -> null
}
/**
* Return true, if the secret-key with the given key-ID is available (i.e. not moved to a
* smart-card).
*
* @return availability of the secret key
*/
fun isSecretKeyAvailable(keyId: Long): Boolean {
return getSecretKey(keyId)?.let {
return if (it.s2K == null) true // Unencrypted key
else it.s2K.type !in 100..110 // Secret key on smart-card
}
?: false // Missing secret key
}
/**
* Return the public key with the given fingerprint.
*
* @param fingerprint fingerprint
* @return public key or null
*/
fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
keys.getPublicKey(fingerprint.keyId)
/**
* Return the secret key with the given fingerprint.
*
* @param fingerprint fingerprint
* @return secret key or null
*/
fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId)
else -> null
}
/**
* Return the public key matching the given [SubkeyIdentifier].
*
* @return public key
* @throws IllegalArgumentException if the identifier's primary key does not match the primary
* key of the key.
*/
fun getPublicKey(identifier: SubkeyIdentifier): PGPPublicKey? {
require(identifier.primaryKeyId == publicKey.keyID) { "Mismatching primary key ID." }
return getPublicKey(identifier.subkeyId)
}
/**
* Return the secret key matching the given [SubkeyIdentifier].
*
* @return secret key
* @throws IllegalArgumentException if the identifier's primary key does not match the primary
* key of the key.
*/
fun getSecretKey(identifier: SubkeyIdentifier): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> {
require(identifier.primaryKeyId == publicKey.keyID) {
"Mismatching primary key ID."
}
keys.getSecretKey(identifier.subkeyId)
}
else -> null
}
/**
* Return true if the public key with the given key id is bound to the key ring properly.
*
* @param keyId key id
* @return true if key is bound validly
*/
fun isKeyValidlyBound(keyId: Long): Boolean {
val publicKey = keys.getPublicKey(keyId) ?: return false
// Primary key -> Check Primary Key Revocation
if (publicKey.keyID == this.publicKey.keyID) {
return if (signatures.primaryKeyRevocation != null &&
signatures.primaryKeyRevocation.isHardRevocation) {
false
} else signatures.primaryKeyRevocation == null
}
// Else Subkey -> Check Subkey Revocation
val binding = signatures.subkeyBindings[keyId]
val revocation = signatures.subkeyRevocations[keyId]
// No valid binding
if (binding == null || binding.isExpired(referenceDate)) {
return false
}
// Revocation
return if (revocation != null) {
if (revocation.isHardRevocation) {
// Subkey is hard revoked
false
} else {
// Key is soft-revoked, not yet re-bound
(revocation.isExpired(referenceDate) ||
!revocation.creationTime.after(binding.creationTime))
}
} else true
}
/**
* Return the current primary user-id of the key ring.
*
* <p>
* Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, this
* method returns the first user-id on the key, otherwise null.
*
* @return primary user-id or null
*/
private fun findPrimaryUserId(): String? {
if (userIds.isEmpty()) {
return null
}
return signatures.userIdCertifications
.filter { (_, certification) -> certification.hashedSubPackets.isPrimaryUserID }
.entries
.maxByOrNull { (_, certification) -> certification.creationTime }
?.key
?: signatures.userIdCertifications.keys.firstOrNull()
}
/** Return true, if the primary user-ID, as well as the given user-ID are valid and bound. */
fun isUserIdValid(userId: CharSequence) =
if (primaryUserId == null) {
false
} else {
isUserIdBound(primaryUserId) &&
(if (userId == primaryUserId) true else isUserIdBound(userId))
}
/** Return true, if the given user-ID is validly bound. */
fun isUserIdBound(userId: CharSequence) =
signatures.userIdCertifications[userId]?.let { sig ->
if (sig.isExpired(referenceDate)) {
// certification expired
return false
}
if (sig.hashedSubPackets.isPrimaryUserID) {
getKeyExpirationTimeAsDate(sig, publicKey)?.let { expirationDate ->
// key expired?
if (expirationDate < referenceDate) return false
}
}
signatures.userIdRevocations[userId]?.let { rev ->
if (rev.isHardRevocation) {
return false // hard revoked -> invalid
}
sig.creationTime > rev.creationTime // re-certification after soft revocation?
}
?: true // certification, but no revocation
}
?: false // no certification
/** [HashAlgorithm] preferences of the given user-ID. */
fun getPreferredHashAlgorithms(userId: CharSequence): Set<HashAlgorithm> {
return getKeyAccessor(userId, keyId).preferredHashAlgorithms
}
/** [HashAlgorithm] preferences of the given key. */
fun getPreferredHashAlgorithms(keyId: Long): Set<HashAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms
}
/** [SymmetricKeyAlgorithm] preferences of the given user-ID. */
fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set<SymmetricKeyAlgorithm> {
return getKeyAccessor(userId, keyId).preferredSymmetricKeyAlgorithms
}
/** [SymmetricKeyAlgorithm] preferences of the given key. */
fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set<SymmetricKeyAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId))
.preferredSymmetricKeyAlgorithms
}
/** [CompressionAlgorithm] preferences of the given user-ID. */
fun getPreferredCompressionAlgorithms(userId: CharSequence): Set<CompressionAlgorithm> {
return getKeyAccessor(userId, keyId).preferredCompressionAlgorithms
}
/** [CompressionAlgorithm] preferences of the given key. */
fun getPreferredCompressionAlgorithms(keyId: Long): Set<CompressionAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId))
.preferredCompressionAlgorithms
}
val isUsableForThirdPartyCertification: Boolean =
isKeyValidlyBound(keyId) && getKeyFlagsOf(keyId).contains(KeyFlag.CERTIFY_OTHER)
private fun getKeyAccessor(userId: CharSequence?, keyId: Long): KeyAccessor {
if (getPublicKey(keyId) == null) {
throw NoSuchElementException(
"No subkey with key-id ${keyId.openPgpKeyId()} found on this key.")
}
if (userId != null && !userIds.contains(userId)) {
throw NoSuchElementException("No user-id '$userId' found on this key.")
}
return if (userId != null) {
KeyAccessor.ViaUserId(this, SubkeyIdentifier(keys, keyId), userId)
} else {
KeyAccessor.ViaKeyId(this, SubkeyIdentifier(keys, keyId))
}
}
companion object {
/** Evaluate the key for the given signature. */
@JvmStatic
fun evaluateForSignature(keys: PGPKeyRing, signature: PGPSignature) =
KeyRingInfo(keys, signature.creationTime!!)
private val PATTERN_EMAIL_FROM_USERID =
"<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>".toPattern()
private val PATTERN_EMAIL_EXPLICIT =
"^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern()
@JvmStatic private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java)
}
private class Signatures(val keys: PGPKeyRing, val referenceDate: Date, val policy: Policy) {
val primaryKeyRevocation: PGPSignature? =
SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate)
val primaryKeySelfSignature: PGPSignature? =
SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate)
val userIdRevocations = mutableMapOf<String, PGPSignature>()
val userIdCertifications = mutableMapOf<String, PGPSignature>()
val subkeyRevocations = mutableMapOf<Long, PGPSignature>()
val subkeyBindings = mutableMapOf<Long, PGPSignature>()
init {
KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.publicKey).forEach { userId ->
SignaturePicker.pickCurrentUserIdRevocationSignature(
keys, userId, policy, referenceDate)
?.let { userIdRevocations[userId] = it }
SignaturePicker.pickLatestUserIdCertificationSignature(
keys, userId, policy, referenceDate)
?.let { userIdCertifications[userId] = it }
}
keys.publicKeys.asSequence().drop(1).forEach { subkey ->
SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(
keys, subkey, policy, referenceDate)
?.let { subkeyRevocations[subkey.keyID] = it }
SignaturePicker.pickLatestSubkeyBindingSignature(
keys, subkey, policy, referenceDate)
?.let { subkeyBindings[subkey.keyID] = it }
}
}
}
}