mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 03:17:58 +01:00
Kotlin conversion: KeyRingInfo
This commit is contained in:
parent
85e2fe956a
commit
6f9e692474
4 changed files with 772 additions and 1326 deletions
File diff suppressed because it is too large
Load diff
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract information from PGPKeyRings.
|
|
||||||
*/
|
|
||||||
package org.pgpainless.key.info;
|
|
|
@ -5,6 +5,7 @@
|
||||||
package org.bouncycastle.extensions
|
package org.bouncycastle.extensions
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
import org.bouncycastle.openpgp.PGPSignature
|
||||||
|
import org.pgpainless.algorithm.RevocationState
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.signature.SignatureUtils
|
import org.pgpainless.signature.SignatureUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -44,3 +45,9 @@ fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.w
|
||||||
* Return true, if this signature is a hard revocation.
|
* Return true, if this signature is a hard revocation.
|
||||||
*/
|
*/
|
||||||
fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this)
|
fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this)
|
||||||
|
|
||||||
|
fun PGPSignature?.toRevocationState() =
|
||||||
|
if (this == null) RevocationState.notRevoked()
|
||||||
|
else
|
||||||
|
if (isHardRevocation()) RevocationState.hardRevoked()
|
||||||
|
else RevocationState.softRevoked(creationTime)
|
||||||
|
|
|
@ -0,0 +1,765 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.key.info
|
||||||
|
|
||||||
|
import openpgp.openPgpKeyId
|
||||||
|
import org.bouncycastle.extensions.*
|
||||||
|
import org.bouncycastle.openpgp.*
|
||||||
|
import org.pgpainless.PGPainless
|
||||||
|
import org.pgpainless.algorithm.*
|
||||||
|
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.SignatureUtils
|
||||||
|
import org.pgpainless.signature.SignatureUtils.Companion.isHardRevocation
|
||||||
|
import org.pgpainless.signature.SignatureUtils.Companion.isSignatureExpired
|
||||||
|
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
|
||||||
|
import java.security.Key
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.NoSuchElementException
|
||||||
|
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
if (directKeyExpirationDate != null && userIdExpirationDate == null) {
|
||||||
|
return directKeyExpirationDate
|
||||||
|
}
|
||||||
|
if (directKeyExpirationDate == null) {
|
||||||
|
return userIdExpirationDate
|
||||||
|
}
|
||||||
|
return if (directKeyExpirationDate < userIdExpirationDate)
|
||||||
|
directKeyExpirationDate
|
||||||
|
else userIdExpirationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 SignatureUtils.getKeyExpirationDate(subkey.creationTime, bindingSig)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 && isHardRevocation(signatures.primaryKeyRevocation)) {
|
||||||
|
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 || isSignatureExpired(binding)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revocation
|
||||||
|
return if (revocation != null) {
|
||||||
|
if (isHardRevocation(revocation)) {
|
||||||
|
// Subkey is hard revoked
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
// Key is soft-revoked, not yet re-bound
|
||||||
|
(isSignatureExpired(revocation) || !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) {
|
||||||
|
SignatureSubpacketsUtil.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 primary user-ID or if absent, of the primary key.
|
||||||
|
*/
|
||||||
|
val preferredHashAlgorithms: Set<HashAlgorithm>
|
||||||
|
get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [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 primary user-ID or if absent of the primary key.
|
||||||
|
*/
|
||||||
|
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
|
||||||
|
get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [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 primary user-ID or if absent, the primary key.
|
||||||
|
*/
|
||||||
|
val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
|
||||||
|
get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue