// SPDX-FileCopyrightText: 2023 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 package org.bouncycastle.extensions import openpgp.filterNotNullValues import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers import org.bouncycastle.bcpg.ECDHPublicBCPGKey import org.bouncycastle.bcpg.ECDSAPublicBCPGKey import org.bouncycastle.bcpg.EdDSAPublicBCPGKey import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.PGPainless import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.SignatureType import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.generation.type.eddsa.EdDSACurve import org.pgpainless.signature.consumer.SignatureVerifier import java.util.* import kotlin.reflect.KProperty /** * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA], * this method returns the name of the underlying elliptic curve. * * For other key types or unknown curves, this method throws an [IllegalArgumentException]. * * @return curve name */ fun PGPPublicKey.getCurveName(): String { PublicKeyAlgorithm.requireFromId(algorithm) .let { when (it) { PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") } } .let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID} .let { it to ECUtil.getCurveName(it) } .let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } } /** * Return the [PublicKeyAlgorithm] of this key. */ val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm get() = PublicKeyAlgorithm.requireFromId(algorithm) /** * Return the [OpenPgpFingerprint] of this key. */ val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint by LazyPGPPublicKey { OpenPgpFingerprint.of(it) } val PGPPublicKey.goodDirectKeySignatures: List by LazyPGPPublicKey { key -> key.getSignaturesOfType(SignatureType.DIRECT_KEY.code) .asSequence() .filter { it.keyID == key.keyID } .filter { runCatching { SignatureVerifier.verifyDirectKeySignature(it, key, PGPainless.getPolicy(), Date()) }.getOrElse { false } } .toList() } val PGPPublicKey.goodDirectKeySignature: PGPSignature? by LazyPGPPublicKey { it.goodDirectKeySignatures .sortedBy { sig -> sig.creationTime } .lastOrNull() } val PGPPublicKey.goodKeyRevocations: List by LazyPGPPublicKey { key -> key.getSignaturesOfType(SignatureType.KEY_REVOCATION.code) .asSequence() .filter { it.keyID == key.keyID } .filter { runCatching { SignatureVerifier.verifyKeyRevocationSignature(it, key, PGPainless.getPolicy(), Date()) }.getOrElse { false } } .toList() } val PGPPublicKey.goodKeyRevocation: PGPSignature? by LazyPGPPublicKey { it.goodKeyRevocations .sortedBy { sig -> sig.creationTime } .lastOrNull() } val PGPPublicKey.goodUserIds: Map by LazyPGPPublicKey { it.getGoodUserIds(Date()) } fun PGPPublicKey.getGoodUserIds(referenceTime: Date): Map { return userIDs.asSequence().associateWith { userId -> getSignaturesForID(userId).asSequence() .filter { it.wasIssuedBy(this) } .filter { it.isCertification } .filter { it.isEffective(referenceTime)} .sortedBy { it.creationTime } .lastOrNull() }.filterNotNullValues() } val PGPPublicKey.primaryUserId: String? by LazyPGPPublicKey { it.getPrimaryUserId(Date()) } fun PGPPublicKey.getPrimaryUserId(referenceTime: Date): String? = getGoodUserIds(referenceTime).entries .sortedBy { it.value.creationTime } .lastOrNull { it.value.hashedSubPackets.isPrimaryUserID }?.key // latest primary User ID ?: getGoodUserIds(referenceTime).keys.firstOrNull() // else first User ID internal class LazyPGPPublicKey(val function: (PGPPublicKey) -> T) { private var value: Result? = null operator fun getValue(pgpPublicKey: PGPPublicKey, property: KProperty<*>): T { if (value == null) { value = try { Result.success(function(pgpPublicKey)) } catch (e : Throwable) { Result.failure(e) } } return value!!.getOrThrow() } }