From 8bd53dd95b0647354801b954a06734bdfa82f52f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 12 Sep 2023 15:01:26 +0200 Subject: [PATCH] Add extension methods to PGPKeyRing, PGPSecretKeyRing and PGPSignature --- .../extensions/PGPKeyRingExtensions.kt | 48 ++++++++++++++++- .../extensions/PGPSecretKeyRingExtensions.kt | 51 +++++++++++++++++-- .../extensions/PGPSignatureExtensions.kt | 4 ++ .../org/pgpainless/key/OpenPgpFingerprint.kt | 2 + 4 files changed, 101 insertions(+), 4 deletions(-) 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 43669bd1..2a3c85f5 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPKeyRingExtensions.kt @@ -5,6 +5,10 @@ package org.bouncycastle.extensions import org.bouncycastle.openpgp.PGPKeyRing +import org.bouncycastle.openpgp.PGPOnePassSignature +import org.bouncycastle.openpgp.PGPPublicKey +import org.bouncycastle.openpgp.PGPSignature +import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.SubkeyIdentifier /** @@ -12,4 +16,46 @@ import org.pgpainless.key.SubkeyIdentifier */ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = this.publicKey.keyID == subkeyIdentifier.primaryKeyId && - this.getPublicKey(subkeyIdentifier.subkeyId) != null \ No newline at end of file + this.getPublicKey(subkeyIdentifier.subkeyId) != null + +/** + * Return true, if the [PGPKeyRing] contains a public key with the given key-ID. + * + * @param keyId keyId + * @return true if key with the given key-ID is present, false otherwise + */ +fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = + this.getPublicKey(keyId) != null + +/** + * Return true, if the [PGPKeyRing] contains a public key with the given fingerprint. + * + * @param fingerprint fingerprint + * @return true if key with the given fingerprint is present, false otherwise + */ +fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean = + this.getPublicKey(fingerprint) != null + +/** + * Return the [PGPPublicKey] with the given [OpenPgpFingerprint] or null, if no such key is present. + * + * @param fingerprint fingerprint + * @return public key + */ +fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = + this.getPublicKey(fingerprint.bytes) + +/** + * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. + * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to + * identify the [PGPPublicKey] via its key-ID. + */ +fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? = + signature.getFingerprint()?.let { this.getPublicKey(it) } ?: + this.getPublicKey(signature.keyID) + +/** + * Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. + */ +fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? = + this.getPublicKey(onePassSignature.keyID) \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index 3a9c2918..5be7e128 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -4,8 +4,53 @@ package org.bouncycastle.extensions -import org.bouncycastle.openpgp.PGPPublicKeyRing -import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.* +import org.pgpainless.key.OpenPgpFingerprint +/** + * OpenPGP certificate containing the public keys of this OpenPGP key. + */ val PGPSecretKeyRing.certificate: PGPPublicKeyRing - get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) \ No newline at end of file + get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) + +/** + * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given key-ID. + * + * @param keyId keyId of the secret key + * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise + */ +fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = + this.getSecretKey(keyId) != null + +/** + * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint. + * + * @param fingerprint fingerprint + * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise + */ +fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean = + this.getSecretKey(fingerprint) != null + +/** + * Return the [PGPSecretKey] with the given [OpenPgpFingerprint]. + * + * @param fingerprint fingerprint of the secret key + * @return the secret key or null + */ +fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = + this.getSecretKey(fingerprint.bytes) + +/** + * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. + * If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to + * identify the [PGPSecretKey] via its key-ID. + */ +fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = + signature.getFingerprint()?.let { this.getSecretKey(it) } ?: + this.getSecretKey(signature.keyID) + +/** + * Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. + */ +fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = + this.getSecretKey(onePassSignature.keyID) \ No newline at end of file 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 16f61304..80200aa6 100644 --- a/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/bouncycastle/extensions/PGPSignatureExtensions.kt @@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPSignature import org.pgpainless.algorithm.RevocationState import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.signature.SignatureUtils +import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import java.util.* /** @@ -51,3 +52,6 @@ fun PGPSignature?.toRevocationState() = else if (isHardRevocation()) RevocationState.hardRevoked() else RevocationState.softRevoked(creationTime) + +fun PGPSignature.getFingerprint(): OpenPgpFingerprint? = + SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt index c5a2a24f..fdcad306 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/OpenPgpFingerprint.kt @@ -16,6 +16,7 @@ import java.nio.charset.Charset */ abstract class OpenPgpFingerprint : CharSequence, Comparable { val fingerprint: String + val bytes: ByteArray /** * Return the version of the fingerprint. @@ -41,6 +42,7 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") } this.fingerprint = prep + this.bytes = Hex.decode(prep) } constructor(bytes: ByteArray): this(Hex.toHexString(bytes))