From 68b9496381fbe51f4815304021a00f6f5ad32e0f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 5 Oct 2023 16:41:36 +0200 Subject: [PATCH] Expand the experiment --- .../src/main/kotlin/openpgp/MapExtensions.kt | 11 +++++ .../extensions/PGPKeyRingExtensions.kt | 46 ++++++++++++++++++- .../extensions/PGPPublicKeyExtensions.kt | 37 +++++++++++---- .../extensions/PGPSignatureExtensions.kt | 3 ++ 4 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/openpgp/MapExtensions.kt diff --git a/pgpainless-core/src/main/kotlin/openpgp/MapExtensions.kt b/pgpainless-core/src/main/kotlin/openpgp/MapExtensions.kt new file mode 100644 index 00000000..72d7ade0 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/openpgp/MapExtensions.kt @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package openpgp + +/** + * Filter `null` values from a [Map], turning it into a [Map]. + */ +@Suppress("UNCHECKED_CAST") +fun Map.filterNotNullValues(): Map = filterValues { it != null } as Map \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt index afa38aa6..ee0708b4 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -12,6 +12,13 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.PGPainless import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier +import java.util.* +import kotlin.NoSuchElementException +import kotlin.math.max +import kotlin.reflect.KProperty + +val PGPKeyRing.primaryPublicKey: PGPPublicKey + get() = requireNotNull(publicKey).also { require(it.isMasterKey) } /** * Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. @@ -72,9 +79,44 @@ fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublic * Return the [OpenPgpFingerprint] of this OpenPGP key. */ val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint - get() = OpenPgpFingerprint.of(this) + get() = OpenPgpFingerprint.of(primaryPublicKey) /** * Return this OpenPGP key as an ASCII armored String. */ -fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) \ No newline at end of file +fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) + +val PGPKeyRing.goodDirectKeySignatures: List by LazyPGPKeyRing { + it.primaryPublicKey.goodDirectKeySignatures +} + +val PGPKeyRing.goodDirectKeySignature: PGPSignature? by LazyPGPKeyRing { + it.primaryPublicKey.goodDirectKeySignature +} + +val PGPKeyRing.expirationDate: Date? by LazyPGPKeyRing { + val dkExp = it.goodDirectKeySignature?.getKeyExpirationDate(it.primaryPublicKey.creationTime) + val puExp = it.primaryPublicKey.goodUserIds[it.primaryPublicKey.primaryUserId] + ?.getKeyExpirationDate(it.primaryPublicKey.creationTime) + + dkExp ?: return@LazyPGPKeyRing puExp // direct-key exp null ? -> userId exp + puExp ?: return@LazyPGPKeyRing dkExp // userId exp null ? -> direct-key exp + + return@LazyPGPKeyRing if (dkExp < puExp) dkExp else puExp // max direct-key exp, userId exp +} + + +internal class LazyPGPKeyRing(val function: (PGPKeyRing) -> T) { + private var value: Result? = null + + operator fun getValue(keys: PGPKeyRing, property: KProperty<*>): T { + if (value == null) { + value = try { + Result.success(function(keys)) + } catch (e : Throwable) { + Result.failure(e) + } + } + return value!!.getOrThrow() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt index 49586f04..87b19c2b 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPPublicKeyExtensions.kt @@ -4,6 +4,7 @@ package org.bouncycastle.extensions +import openpgp.filterNotNullValues import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers import org.bouncycastle.bcpg.ECDHPublicBCPGKey import org.bouncycastle.bcpg.ECDSAPublicBCPGKey @@ -12,11 +13,8 @@ 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.KeyFlag import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.SignatureType -import org.pgpainless.decryption_verification.SignatureVerification -import org.pgpainless.exception.SignatureValidationException import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.generation.type.eddsa.EdDSACurve import org.pgpainless.signature.consumer.SignatureVerifier @@ -55,9 +53,9 @@ val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm /** * Return the [OpenPgpFingerprint] of this key. */ -val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint by Lazy { OpenPgpFingerprint.of(it) } +val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint by LazyPGPPublicKey { OpenPgpFingerprint.of(it) } -val PGPPublicKey.goodDirectKeySignatures: List by Lazy { key -> +val PGPPublicKey.goodDirectKeySignatures: List by LazyPGPPublicKey { key -> key.getSignaturesOfType(SignatureType.DIRECT_KEY.code) .asSequence() .filter { it.keyID == key.keyID } @@ -69,13 +67,13 @@ val PGPPublicKey.goodDirectKeySignatures: List by Lazy { key -> .toList() } -val PGPPublicKey.goodDirectKeySignature: PGPSignature? by Lazy { +val PGPPublicKey.goodDirectKeySignature: PGPSignature? by LazyPGPPublicKey { it.goodDirectKeySignatures .sortedBy { sig -> sig.creationTime } .lastOrNull() } -val PGPPublicKey.goodKeyRevocations: List by Lazy { key -> +val PGPPublicKey.goodKeyRevocations: List by LazyPGPPublicKey { key -> key.getSignaturesOfType(SignatureType.KEY_REVOCATION.code) .asSequence() .filter { it.keyID == key.keyID } @@ -87,13 +85,34 @@ val PGPPublicKey.goodKeyRevocations: List by Lazy { key -> .toList() } -val PGPPublicKey.goodKeyRevocation: PGPSignature? by Lazy { +val PGPPublicKey.goodKeyRevocation: PGPSignature? by LazyPGPPublicKey { it.goodKeyRevocations .sortedBy { sig -> sig.creationTime } .lastOrNull() } -internal class Lazy(val function: (PGPPublicKey) -> T) { +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 { 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 4fe97bc7..1d727f02 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -36,6 +36,9 @@ val PGPSignature.signatureExpirationDate: Date? fun PGPSignature.isExpired(referenceTime: Date = Date()) = signatureExpirationDate?.let { referenceTime >= it } ?: false +fun PGPSignature.isEffective(referenceTime: Date = Date()) = + !isExpired(referenceTime) && creationTime < referenceTime + /** * Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint * subpackets of the signature.