Introduce more extension methods

This commit is contained in:
Paul Schaub 2023-09-12 21:55:39 +02:00
parent bb796143ff
commit 68af0a4f0e
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
9 changed files with 168 additions and 71 deletions

View File

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package openpgp
import java.util.*
/**
* Return a new date which represents this date plus the given amount of seconds added.
*
* Since '0' is a special date value in the OpenPGP specification
* (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0.
*
* @param date date
* @param seconds number of seconds to be added
* @return date plus seconds or null if seconds is '0'
*/
fun Date.plusSeconds(seconds: Long): Date? {
require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." }
return if (seconds == 0L) null
else Date(this.time + 1000 * seconds)
}

View File

@ -8,6 +8,7 @@ import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSignature
import org.pgpainless.PGPainless
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.SubkeyIdentifier
@ -51,11 +52,22 @@ fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
* identify the [PGPPublicKey] via its key-ID.
*/
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
signature.getFingerprint()?.let { this.getPublicKey(it) } ?:
signature.fingerprint?.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)
this.getPublicKey(onePassSignature.keyID)
/**
* Return the [OpenPgpFingerprint] of this OpenPGP key.
*/
val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this)
/**
* Return this OpenPGP key as an ASCII armored String.
*/
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)

View File

@ -11,6 +11,7 @@ import org.bouncycastle.bcpg.EdDSAPublicBCPGKey
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil
import org.bouncycastle.openpgp.PGPPublicKey
import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
/**
@ -35,3 +36,15 @@ fun PGPPublicKey.getCurveName(): String {
.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
get() = OpenPgpFingerprint.of(this)

View File

@ -7,10 +7,13 @@ package org.bouncycastle.extensions
import org.bouncycastle.bcpg.S2K
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPrivateKey
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.exception.KeyIntegrityException
import org.pgpainless.exception.WrongPassphraseException
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.protection.SecretKeyRingProtector
import org.pgpainless.key.protection.UnlockSecretKey
import org.pgpainless.util.Passphrase
@ -70,3 +73,15 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0)
* @return true if secret key has S2K of type GNU_DUMMY_S2K, false otherwise.
*/
fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K)
/**
* Return the [PublicKeyAlgorithm] of this key.
*/
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
get() = publicKey.publicKeyAlgorithm
/**
* Return the [OpenPgpFingerprint] of this key.
*/
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this)

View File

@ -4,6 +4,7 @@
package org.bouncycastle.extensions
import openpgp.openPgpKeyId
import org.bouncycastle.openpgp.*
import org.pgpainless.key.OpenPgpFingerprint
@ -40,13 +41,29 @@ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
this.getSecretKey(fingerprint.bytes)
/**
* Return the [PGPSecretKey] with the given key-ID.
*
* @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given key-ID
*/
fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey =
getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
/**
* Return the [PGPSecretKey] with the given fingerprint.
*
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given fingerprint
*/
fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey =
getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.")
/**
* 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) } ?:
signature.fingerprint?.let { this.getSecretKey(it) } ?:
this.getSecretKey(signature.keyID)
/**

View File

@ -4,10 +4,12 @@
package org.bouncycastle.extensions
import openpgp.plusSeconds
import org.bouncycastle.openpgp.PGPSignature
import org.pgpainless.algorithm.RevocationState
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.signature.SignatureUtils
import org.pgpainless.key.util.RevocationAttributes.Reason
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import java.util.*
@ -16,42 +18,74 @@ import java.util.*
* such a subpacket.
*/
fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? =
SignatureUtils.getKeyExpirationDate(keyCreationDate, this)
SignatureSubpacketsUtil.getKeyExpirationTime(this)
?.let { keyCreationDate.plusSeconds(it.time) }
/**
* Return the value of the signature ExpirationTime subpacket, or null, if the signature
* does not carry such a subpacket.
*/
fun PGPSignature.getSignatureExpirationDate(): Date? =
SignatureUtils.getSignatureExpirationDate(this)
val PGPSignature.signatureExpirationDate: Date?
get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this)
?.let { this.creationTime.plusSeconds(it.time) }
/**
* Return true, if the signature is expired at the given reference time.
*/
fun PGPSignature.isExpired(referenceTime: Date = Date()) =
SignatureUtils.isSignatureExpired(this, referenceTime)
signatureExpirationDate?.let { referenceTime >= it } ?: false
/**
* Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint
* subpackets of the signature.
*/
fun PGPSignature.getIssuerKeyId() = SignatureUtils.determineIssuerKeyId(this)
val PGPSignature.issuerKeyId: Long
get() = when (version) {
2, 3 -> keyID
else -> {
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)
?.let { if (it != 0L) it else null }
?: fingerprint?.keyId
?: 0L
}
}
/**
* Return true, if the signature was likely issued by the key with the given fingerprint.
* Return true, if the signature was likely issued by a key with the given fingerprint.
*/
fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint) = SignatureUtils.wasIssuedBy(fingerprint, this)
fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean =
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId)
/**
* Return true, if the signature was likely issued by a key with the given fingerprint.
* @param fingerprint fingerprint bytes
*/
@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.")
fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean =
try {
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
} catch (e : IllegalArgumentException) {
// Unknown fingerprint length / format
false
}
/**
* Return true, if this signature is a hard revocation.
*/
fun PGPSignature.isHardRevocation() = SignatureUtils.isHardRevocation(this)
val PGPSignature.isHardRevocation
get() = when (SignatureType.requireFromCode(signatureType)) {
SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION, SignatureType.CERTIFICATION_REVOCATION -> {
SignatureSubpacketsUtil.getRevocationReason(this)
?.let { Reason.isHardRevocation(it.revocationReason) }
?: true // no reason -> hard revocation
}
else -> false // Not a revocation
}
fun PGPSignature?.toRevocationState() =
if (this == null) RevocationState.notRevoked()
else
if (isHardRevocation()) RevocationState.hardRevoked()
else RevocationState.softRevoked(creationTime)
else if (isHardRevocation) RevocationState.hardRevoked()
else RevocationState.softRevoked(creationTime)
fun PGPSignature.getFingerprint(): OpenPgpFingerprint? =
SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
val PGPSignature.fingerprint: OpenPgpFingerprint?
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)

View File

@ -121,7 +121,7 @@ class KeyRingInfo(
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
return@filter !revocation.isHardRevocation && certification.creationTime > revocation.creationTime
}
/**
@ -272,7 +272,7 @@ class KeyRingInfo(
* @return true, if the given user-ID is hard-revoked.
*/
fun isHardRevoked(userId: CharSequence): Boolean {
return signatures.userIdRevocations[userId]?.isHardRevocation() ?: false
return signatures.userIdRevocations[userId]?.isHardRevocation ?: false
}
/**
@ -632,7 +632,7 @@ class KeyRingInfo(
}
}
signatures.userIdRevocations[userId]?.let { rev ->
if (rev.isHardRevocation()) {
if (rev.isHardRevocation) {
return false // hard revoked -> invalid
}
sig.creationTime > rev.creationTime// re-certification after soft revocation?

View File

@ -8,6 +8,7 @@ import openpgp.openPgpKeyId
import org.bouncycastle.bcpg.S2K
import org.bouncycastle.bcpg.SecretKeyPacket
import org.bouncycastle.extensions.certificate
import org.bouncycastle.extensions.requireSecretKey
import org.bouncycastle.openpgp.*
import org.bouncycastle.util.Strings
import org.pgpainless.exception.MissingPassphraseException
@ -34,9 +35,10 @@ class KeyRingUtils {
* @return primary secret key
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSecretKeyRing extension function.",
ReplaceWith("secretKeys.requireSecretKey(keyId)"))
fun requirePrimarySecretKeyFrom(secretKeys: PGPSecretKeyRing): PGPSecretKey {
return getPrimarySecretKeyFrom(secretKeys)
?: throw NoSuchElementException("Provided PGPSecretKeyRing has no primary secret key.")
return secretKeys.requireSecretKey(secretKeys.publicKey.keyID)
}
/**

View File

@ -4,7 +4,9 @@
package org.pgpainless.signature
import openpgp.plusSeconds
import org.bouncycastle.bcpg.sig.KeyExpirationTime
import org.bouncycastle.extensions.*
import org.bouncycastle.openpgp.*
import org.bouncycastle.util.encoders.Hex
import org.bouncycastle.util.io.Streams
@ -17,6 +19,7 @@ import org.pgpainless.util.ArmorUtils
import java.io.IOException
import java.io.InputStream
import java.util.*
import kotlin.math.sign
const val MAX_ITERATIONS = 10000
@ -32,10 +35,10 @@ class SignatureUtils {
* @return key expiration date as given by the signature
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method.",
ReplaceWith("signature.getKeyExpirationDate(keyCreationDate)"))
fun getKeyExpirationDate(keyCreationDate: Date, signature: PGPSignature): Date? {
val expirationPacket: KeyExpirationTime = SignatureSubpacketsUtil.getKeyExpirationTime(signature) ?: return null
val expiresInSeconds = expirationPacket.time
return datePlusSeconds(keyCreationDate, expiresInSeconds)
return signature.getKeyExpirationDate(keyCreationDate)
}
/**
@ -46,12 +49,9 @@ class SignatureUtils {
* @return expiration date of the signature, or null if it does not expire.
*/
@JvmStatic
fun getSignatureExpirationDate(signature: PGPSignature): Date? {
val expirationTime = SignatureSubpacketsUtil.getSignatureExpirationTime(signature) ?: return null
val expiresInSeconds = expirationTime.time
return datePlusSeconds(signature.creationTime, expiresInSeconds)
}
@Deprecated("Deprecated in favor of PGPSignature extension method.",
ReplaceWith("signature.signatureExpirationDate"))
fun getSignatureExpirationDate(signature: PGPSignature): Date? = signature.signatureExpirationDate
/**
* Return a new date which represents the given date plus the given amount of seconds added.
@ -64,11 +64,10 @@ class SignatureUtils {
* @return date plus seconds or null if seconds is '0'
*/
@JvmStatic
@Deprecated("Deprecated in favor of Date extension method.",
ReplaceWith("date.plusSeconds(seconds)"))
fun datePlusSeconds(date: Date, seconds: Long): Date? {
if (seconds == 0L) {
return null
}
return Date(date.time + 1000 * seconds)
return date.plusSeconds(seconds)
}
/**
@ -79,8 +78,10 @@ class SignatureUtils {
* @return true if expired, false otherwise
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method.",
ReplaceWith("signature.isExpired()"))
fun isSignatureExpired(signature: PGPSignature): Boolean {
return isSignatureExpired(signature, Date())
return signature.isExpired()
}
/**
@ -92,9 +93,10 @@ class SignatureUtils {
* @return true if sig is expired at reference date, false otherwise
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method.",
ReplaceWith("signature.isExpired(referenceTime)"))
fun isSignatureExpired(signature: PGPSignature, referenceTime: Date): Boolean {
val expirationDate = getSignatureExpirationDate(signature) ?: return false
return referenceTime >= expirationDate
return signature.isExpired(referenceTime)
}
/**
@ -106,15 +108,10 @@ class SignatureUtils {
* @return true if signature is a hard revocation
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension function.",
ReplaceWith("signature.isHardRevocation()"))
fun isHardRevocation(signature: PGPSignature): Boolean {
val type = SignatureType.requireFromCode(signature.signatureType)
if (type != SignatureType.KEY_REVOCATION && type != SignatureType.SUBKEY_REVOCATION && type != SignatureType.CERTIFICATION_REVOCATION) {
// Not a revocation
return false
}
val reason = SignatureSubpacketsUtil.getRevocationReason(signature) ?: return true // no reason -> hard revocation
return Reason.isHardRevocation(reason.revocationReason)
return signature.isHardRevocation
}
@JvmStatic
@ -181,22 +178,10 @@ class SignatureUtils {
* @return signatures issuing key id
*/
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method.",
ReplaceWith("signature.issuerKeyId"))
fun determineIssuerKeyId(signature: PGPSignature): Long {
if (signature.version == 3) {
// V3 sigs do not contain subpackets
return signature.keyID
}
val issuerKeyId = SignatureSubpacketsUtil.getIssuerKeyId(signature)
val issuerFingerprint = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature)
if (issuerKeyId != null && issuerKeyId.keyID != 0L) {
return issuerKeyId.keyID
}
if (issuerKeyId == null && issuerFingerprint != null) {
return issuerFingerprint.keyId
}
return 0
return signature.issuerKeyId
}
/**
@ -211,21 +196,17 @@ class SignatureUtils {
}
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method",
ReplaceWith("signature.wasIssuedBy(fingerprint)"))
fun wasIssuedBy(fingerprint: ByteArray, signature: PGPSignature): Boolean {
return try {
val pgpFingerprint = OpenPgpFingerprint.parseFromBinary(fingerprint)
wasIssuedBy(pgpFingerprint, signature)
} catch (e : IllegalArgumentException) {
// Unknown fingerprint length
false
}
return signature.wasIssuedBy(fingerprint)
}
@JvmStatic
@Deprecated("Deprecated in favor of PGPSignature extension method",
ReplaceWith("signature.wasIssuedBy(fingerprint)"))
fun wasIssuedBy(fingerprint: OpenPgpFingerprint, signature: PGPSignature): Boolean {
val issuerFp = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(signature)
?: return fingerprint.keyId == signature.keyID
return fingerprint == issuerFp
return signature.wasIssuedBy(fingerprint)
}
/**