Apply new formatting from 'gradle spotlessApply'

This commit is contained in:
Paul Schaub 2023-10-23 14:24:31 +02:00
parent 633048fd2a
commit 51e9bfc67f
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
147 changed files with 6186 additions and 5198 deletions

View File

@ -11,40 +11,38 @@ import java.util.*
/** /**
* Return a new date which represents this date plus the given amount of seconds added. * 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 * Since '0' is a special date value in the OpenPGP specification (e.g. '0' means no expiration for
* (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0. * expiration dates), this method will return 'null' if seconds is 0.
* *
* @param date date * @param date date
* @param seconds number of seconds to be added * @param seconds number of seconds to be added
* @return date plus seconds or null if seconds is '0' * @return date plus seconds or null if seconds is '0'
*/ */
fun Date.plusSeconds(seconds: Long): Date? { fun Date.plusSeconds(seconds: Long): Date? {
require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." } require(Long.MAX_VALUE - time > seconds) {
return if (seconds == 0L) null "Adding $seconds seconds to this date would cause time to overflow."
else Date(this.time + 1000 * seconds) }
return if (seconds == 0L) null else Date(this.time + 1000 * seconds)
} }
val Date.asSeconds: Long val Date.asSeconds: Long
get() = time / 1000 get() = time / 1000
fun Date.secondsTill(later: Date): Long { fun Date.secondsTill(later: Date): Long {
require(this <= later) { require(this <= later) { "Timestamp MUST be before the later timestamp." }
"Timestamp MUST be before the later timestamp."
}
return later.asSeconds - this.asSeconds return later.asSeconds - this.asSeconds
} }
/** /** Return a new [Date] instance with this instance's time floored down to seconds precision. */
* Return a new [Date] instance with this instance's time floored down to seconds precision.
*/
fun Date.toSecondsPrecision(): Date { fun Date.toSecondsPrecision(): Date {
return Date(asSeconds * 1000) return Date(asSeconds * 1000)
} }
internal val parser: SimpleDateFormat internal val parser: SimpleDateFormat
// Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every
get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z") // invocation.
.apply { timeZone = TimeZone.getTimeZone("UTC") } get() =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").apply { timeZone = TimeZone.getTimeZone("UTC") }
/** /**
* Format a date as UTC timestamp. * Format a date as UTC timestamp.
@ -55,12 +53,13 @@ fun Date.formatUTC(): String = parser.format(this)
/** /**
* Parse a UTC timestamp into a date. * Parse a UTC timestamp into a date.
*
* @return date * @return date
*/ */
fun String.parseUTC(): Date { fun String.parseUTC(): Date {
return try { return try {
parser.parse(this) parser.parse(this)
} catch (e : ParseException) { } catch (e: ParseException) {
throw IllegalArgumentException("Malformed UTC timestamp: $this", e) throw IllegalArgumentException("Malformed UTC timestamp: $this", e)
} }
} }

View File

@ -4,16 +4,12 @@
package openpgp package openpgp
/** /** Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). */
* Format this Long as an OpenPGP key-ID (16 digit uppercase hex number).
*/
fun Long.openPgpKeyId(): String { fun Long.openPgpKeyId(): String {
return String.format("%016X", this).uppercase() return String.format("%016X", this).uppercase()
} }
/** /** Parse a Long form a 16 digit hex encoded OpenPgp key-ID. */
* Parse a Long form a 16 digit hex encoded OpenPgp key-ID.
*/
fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long { fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long {
require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) { require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) {
"Provided long key-id does not match expected format. " + "Provided long key-id does not match expected format. " +

View File

@ -11,13 +11,13 @@ import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactor
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
/** /**
* Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. * Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. That
* That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted. * way, if a message needs to be decrypted multiple times, expensive private key operations can be
* omitted.
* *
* This implementation changes the behavior or [recoverSessionData] to first return any * This implementation changes the behavior or [recoverSessionData] to first return any cache hits.
* cache hits. * If no hit is found, the method call is delegated to the underlying
* If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory]. * [PublicKeyDataDecryptorFactory]. The result of that is then placed in the cache and returned.
* The result of that is then placed in the cache and returned.
*/ */
class CachingBcPublicKeyDataDecryptorFactory( class CachingBcPublicKeyDataDecryptorFactory(
privateKey: PGPPrivateKey, privateKey: PGPPrivateKey,
@ -26,16 +26,22 @@ class CachingBcPublicKeyDataDecryptorFactory(
private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf() private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf()
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray = override fun recoverSessionData(
lookupSessionKeyData(secKeyData) ?: keyAlgorithm: Int,
costlyRecoverSessionData(keyAlgorithm, secKeyData) secKeyData: Array<out ByteArray>
.also { cacheSessionKeyData(secKeyData, it) } ): ByteArray =
lookupSessionKeyData(secKeyData)
?: costlyRecoverSessionData(keyAlgorithm, secKeyData).also {
cacheSessionKeyData(secKeyData, it)
}
private fun lookupSessionKeyData(secKeyData: Array<out ByteArray>): ByteArray? = private fun lookupSessionKeyData(secKeyData: Array<out ByteArray>): ByteArray? =
cachedSessions[toKey(secKeyData)]?.clone() cachedSessions[toKey(secKeyData)]?.clone()
private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray = private fun costlyRecoverSessionData(
super.recoverSessionData(keyAlgorithm, secKeyData) keyAlgorithm: Int,
secKeyData: Array<out ByteArray>
): ByteArray = super.recoverSessionData(keyAlgorithm, secKeyData)
private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) { private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) {
cachedSessions[toKey(secKeyData)] = sessionKey.clone() cachedSessions[toKey(secKeyData)] = sessionKey.clone()

View File

@ -13,9 +13,7 @@ import org.pgpainless.PGPainless
import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
/** /** Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. */
* Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier].
*/
fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean = fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
this.publicKey.keyID == subkeyIdentifier.primaryKeyId && this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
this.getPublicKey(subkeyIdentifier.subkeyId) != null this.getPublicKey(subkeyIdentifier.subkeyId) != null
@ -26,8 +24,7 @@ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
* @param keyId keyId * @param keyId keyId
* @return true if key with the given key-ID is present, false otherwise * @return true if key with the given key-ID is present, false otherwise
*/ */
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = this.getPublicKey(keyId) != null
this.getPublicKey(keyId) != null
/** /**
* Return true, if the [PGPKeyRing] contains a public key with the given fingerprint. * Return true, if the [PGPKeyRing] contains a public key with the given fingerprint.
@ -48,33 +45,30 @@ fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
this.getPublicKey(fingerprint.bytes) this.getPublicKey(fingerprint.bytes)
fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey = fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey =
getPublicKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") getPublicKey(keyId)
?: throw NoSuchElementException(
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey = fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey =
getPublicKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") getPublicKey(fingerprint)
?: throw NoSuchElementException(
"OpenPGP key does not contain key with fingerprint $fingerprint.")
/** /**
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. * Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to * the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
* identify the [PGPPublicKey] via its key-ID. * subpacket to identify the [PGPPublicKey] via its key-ID.
*/ */
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? = fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
signature.fingerprint?.let { this.getPublicKey(it) } ?: signature.fingerprint?.let { this.getPublicKey(it) } ?: this.getPublicKey(signature.keyID)
this.getPublicKey(signature.keyID)
/** /** Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
* Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
*/
fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? = fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? =
this.getPublicKey(onePassSignature.keyID) this.getPublicKey(onePassSignature.keyID)
/** /** Return the [OpenPgpFingerprint] of this OpenPGP key. */
* Return the [OpenPgpFingerprint] of this OpenPGP key.
*/
val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this) get() = OpenPgpFingerprint.of(this)
/** /** Return this OpenPGP key as an ASCII armored String. */
* Return this OpenPGP key as an ASCII armored String.
*/
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this) fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)

View File

@ -15,8 +15,8 @@ import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.generation.type.eddsa.EdDSACurve import org.pgpainless.key.generation.type.eddsa.EdDSACurve
/** /**
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA], * For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and
* this method returns the name of the underlying elliptic curve. * [PublicKeyAlgorithm.EDDSA], this method returns the name of the underlying elliptic curve.
* *
* For other key types or unknown curves, this method throws an [IllegalArgumentException]. * For other key types or unknown curves, this method throws an [IllegalArgumentException].
* *
@ -32,19 +32,21 @@ fun PGPPublicKey.getCurveName(): String {
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).") else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
} }
} }
.let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID} .let {
if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName
else it.curveOID
}
.let { it to ECUtil.getCurveName(it) } .let { it to ECUtil.getCurveName(it) }
.let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") } .let {
if (it.second != null) return it.second
else throw IllegalArgumentException("Unknown curve: ${it.first}")
}
} }
/** /** Return the [PublicKeyAlgorithm] of this key. */
* Return the [PublicKeyAlgorithm] of this key.
*/
val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm
get() = PublicKeyAlgorithm.requireFromId(algorithm) get() = PublicKeyAlgorithm.requireFromId(algorithm)
/** /** Return the [OpenPgpFingerprint] of this key. */
* Return the [OpenPgpFingerprint] of this key.
*/
val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this) get() = OpenPgpFingerprint.of(this)

View File

@ -7,7 +7,6 @@ package org.bouncycastle.extensions
import org.bouncycastle.bcpg.S2K import org.bouncycastle.bcpg.S2K
import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPPrivateKey
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
@ -39,8 +38,9 @@ fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
*/ */
@Throws(PGPException::class, KeyIntegrityException::class) @Throws(PGPException::class, KeyIntegrityException::class)
@JvmOverloads @JvmOverloads
fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey = fun PGPSecretKey.unlock(
UnlockSecretKey.unlockSecretKey(this, protector) protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
): PGPPrivateKey = UnlockSecretKey.unlockSecretKey(this, protector)
/** /**
* Unlock the secret key to get its [PGPPrivateKey]. * Unlock the secret key to get its [PGPPrivateKey].
@ -74,14 +74,10 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0)
*/ */
fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K) fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K)
/** /** Return the [PublicKeyAlgorithm] of this key. */
* Return the [PublicKeyAlgorithm] of this key.
*/
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
get() = publicKey.publicKeyAlgorithm get() = publicKey.publicKeyAlgorithm
/** /** Return the [OpenPgpFingerprint] of this key. */
* Return the [OpenPgpFingerprint] of this key.
*/
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this) get() = OpenPgpFingerprint.of(this)

View File

@ -8,9 +8,7 @@ import openpgp.openPgpKeyId
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.OpenPgpFingerprint
/** /** OpenPGP certificate containing the public keys of this OpenPGP key. */
* OpenPGP certificate containing the public keys of this OpenPGP key.
*/
val PGPSecretKeyRing.certificate: PGPPublicKeyRing val PGPSecretKeyRing.certificate: PGPPublicKeyRing
get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList()) get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList())
@ -20,8 +18,7 @@ val PGPSecretKeyRing.certificate: PGPPublicKeyRing
* @param keyId keyId of the secret key * @param keyId keyId of the secret key
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise * @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
*/ */
fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = this.getSecretKey(keyId) != null
this.getSecretKey(keyId) != null
/** /**
* Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint. * Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint.
@ -44,36 +41,39 @@ fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey
/** /**
* Return the [PGPSecretKey] with the given key-ID. * 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 * @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given
* key-ID
*/ */
fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey = fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey =
getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.") getSecretKey(keyId)
?: throw NoSuchElementException(
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
/** /**
* Return the [PGPSecretKey] with the given fingerprint. * Return the [PGPSecretKey] with the given fingerprint.
* *
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key 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 = fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey =
getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.") getSecretKey(fingerprint)
?: throw NoSuchElementException(
"OpenPGP key does not contain key with fingerprint $fingerprint.")
/** /**
* Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. * Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to * the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
* identify the [PGPSecretKey] via its key-ID. * subpacket to identify the [PGPSecretKey] via its key-ID.
*/ */
fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? = fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? =
signature.fingerprint?.let { this.getSecretKey(it) } ?: signature.fingerprint?.let { this.getSecretKey(it) } ?: this.getSecretKey(signature.keyID)
this.getSecretKey(signature.keyID)
/** /** Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
* Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
*/
fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? = fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? =
this.getSecretKey(onePassSignature.keyID) this.getSecretKey(onePassSignature.keyID)
fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? = fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? =
when(pkesk.version) { when (pkesk.version) {
3 -> this.getSecretKey(pkesk.keyID) 3 -> this.getSecretKey(pkesk.keyID)
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.") else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
} }

View File

@ -4,6 +4,7 @@
package org.bouncycastle.extensions package org.bouncycastle.extensions
import java.util.*
import openpgp.plusSeconds import openpgp.plusSeconds
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignature
@ -12,27 +13,25 @@ import org.pgpainless.algorithm.SignatureType
import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.util.RevocationAttributes.Reason import org.pgpainless.key.util.RevocationAttributes.Reason
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import java.util.*
/** /**
* Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry * Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry
* such a subpacket. * such a subpacket.
*/ */
fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? = fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? =
SignatureSubpacketsUtil.getKeyExpirationTime(this) SignatureSubpacketsUtil.getKeyExpirationTime(this)?.let { keyCreationDate.plusSeconds(it.time) }
?.let { keyCreationDate.plusSeconds(it.time) }
/** /**
* Return the value of the signature ExpirationTime subpacket, or null, if the signature * Return the value of the signature ExpirationTime subpacket, or null, if the signature does not
* does not carry such a subpacket. * carry such a subpacket.
*/ */
val PGPSignature.signatureExpirationDate: Date? val PGPSignature.signatureExpirationDate: Date?
get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this) get() =
?.let { this.creationTime.plusSeconds(it.time) } SignatureSubpacketsUtil.getSignatureExpirationTime(this)?.let {
this.creationTime.plusSeconds(it.time)
}
/** /** Return true, if the signature is expired at the given reference time. */
* Return true, if the signature is expired at the given reference time.
*/
fun PGPSignature.isExpired(referenceTime: Date = Date()) = fun PGPSignature.isExpired(referenceTime: Date = Date()) =
signatureExpirationDate?.let { referenceTime >= it } ?: false signatureExpirationDate?.let { referenceTime >= it } ?: false
@ -41,46 +40,48 @@ fun PGPSignature.isExpired(referenceTime: Date = Date()) =
* subpackets of the signature. * subpackets of the signature.
*/ */
val PGPSignature.issuerKeyId: Long val PGPSignature.issuerKeyId: Long
get() = when (version) { get() =
2, 3 -> keyID when (version) {
2,
3 -> keyID
else -> { else -> {
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this) SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)?.let {
?.let { if (it != 0L) it else null } if (it != 0L) it else null
?: fingerprint?.keyId }
?: 0L ?: fingerprint?.keyId ?: 0L
} }
} }
/** /** Return true, if the signature was likely issued by a 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): Boolean = fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean =
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId) 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. * Return true, if the signature was likely issued by a key with the given fingerprint.
*
* @param fingerprint fingerprint bytes * @param fingerprint fingerprint bytes
*/ */
@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.") @Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.")
fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean = fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean =
try { try {
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint)) wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
} catch (e : IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// Unknown fingerprint length / format // Unknown fingerprint length / format
false false
} }
fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = wasIssuedBy(OpenPgpFingerprint.of(key))
wasIssuedBy(OpenPgpFingerprint.of(key))
/** /** Return true, if this signature is a hard revocation. */
* Return true, if this signature is a hard revocation.
*/
val PGPSignature.isHardRevocation val PGPSignature.isHardRevocation
get() = when (SignatureType.requireFromCode(signatureType)) { get() =
SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION, SignatureType.CERTIFICATION_REVOCATION -> { when (SignatureType.requireFromCode(signatureType)) {
SignatureSubpacketsUtil.getRevocationReason(this) SignatureType.KEY_REVOCATION,
?.let { Reason.isHardRevocation(it.revocationReason) } SignatureType.SUBKEY_REVOCATION,
SignatureType.CERTIFICATION_REVOCATION -> {
SignatureSubpacketsUtil.getRevocationReason(this)?.let {
Reason.isHardRevocation(it.revocationReason)
}
?: true // no reason -> hard revocation ?: true // no reason -> hard revocation
} }
else -> false // Not a revocation else -> false // Not a revocation

View File

@ -4,6 +4,8 @@
package org.pgpainless package org.pgpainless
import java.io.OutputStream
import java.util.*
import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing
@ -19,8 +21,6 @@ import org.pgpainless.key.parsing.KeyRingReader
import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.policy.Policy import org.pgpainless.policy.Policy
import org.pgpainless.util.ArmorUtils import org.pgpainless.util.ArmorUtils
import java.io.OutputStream
import java.util.*
class PGPainless private constructor() { class PGPainless private constructor() {
@ -28,25 +28,24 @@ class PGPainless private constructor() {
/** /**
* Generate a fresh OpenPGP key ring from predefined templates. * Generate a fresh OpenPGP key ring from predefined templates.
*
* @return templates * @return templates
*/ */
@JvmStatic @JvmStatic fun generateKeyRing() = KeyRingTemplates()
fun generateKeyRing() = KeyRingTemplates()
/** /**
* Build a custom OpenPGP key ring. * Build a custom OpenPGP key ring.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun buildKeyRing() = KeyRingBuilder()
fun buildKeyRing() = KeyRingBuilder()
/** /**
* Read an existing OpenPGP key ring. * Read an existing OpenPGP key ring.
*
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun readKeyRing() = KeyRingReader()
fun readKeyRing() = KeyRingReader()
/** /**
* Extract a public key certificate from a secret key. * Extract a public key certificate from a secret key.
@ -59,7 +58,8 @@ class PGPainless private constructor() {
KeyRingUtils.publicKeyRingFrom(secretKey) KeyRingUtils.publicKeyRingFrom(secretKey)
/** /**
* Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together. * Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key
* server) together.
* *
* @param originalCopy local, older copy of the cert * @param originalCopy local, older copy of the cert
* @param updatedCopy updated, newer copy of the cert * @param updatedCopy updated, newer copy of the cert
@ -67,8 +67,7 @@ class PGPainless private constructor() {
* @throws PGPException in case of an error * @throws PGPException in case of an error
*/ */
@JvmStatic @JvmStatic
fun mergeCertificate(originalCopy: PGPPublicKeyRing, fun mergeCertificate(originalCopy: PGPPublicKeyRing, updatedCopy: PGPPublicKeyRing) =
updatedCopy: PGPPublicKeyRing) =
PGPPublicKeyRing.join(originalCopy, updatedCopy) PGPPublicKeyRing.join(originalCopy, updatedCopy)
/** /**
@ -76,22 +75,19 @@ class PGPainless private constructor() {
* *
* @param key key or certificate * @param key key or certificate
* @return ascii armored string * @return ascii armored string
*
* @throws IOException in case of an error during the armoring process * @throws IOException in case of an error during the armoring process
*/ */
@JvmStatic @JvmStatic
fun asciiArmor(key: PGPKeyRing) = fun asciiArmor(key: PGPKeyRing) =
if (key is PGPSecretKeyRing) if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key)
ArmorUtils.toAsciiArmoredString(key) else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
else
ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
/** /**
* Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream]. * Wrap a key of certificate in ASCII armor and write the result into the given
* [OutputStream].
* *
* @param key key or certificate * @param key key or certificate
* @param outputStream output stream * @param outputStream output stream
*
* @throws IOException in case of an error during the armoring process * @throws IOException in case of an error during the armoring process
*/ */
@JvmStatic @JvmStatic
@ -106,33 +102,34 @@ class PGPainless private constructor() {
* *
* @param signature detached signature * @param signature detached signature
* @return ascii armored string * @return ascii armored string
*
* @throws IOException in case of an error during the armoring process * @throws IOException in case of an error during the armoring process
*/ */
@JvmStatic @JvmStatic
fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature) fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature)
/** /**
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP. * Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using
* OpenPGP.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun encryptAndOrSign() = EncryptionBuilder()
fun encryptAndOrSign() = EncryptionBuilder()
/** /**
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP. * Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using
* OpenPGP.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun decryptAndOrVerify() = DecryptionBuilder()
fun decryptAndOrVerify() = DecryptionBuilder()
/** /**
* Make changes to a secret key at the given reference time. * Make changes to a secret key at the given reference time. This method can be used to
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys. * change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
*
* <p> * <p>
* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}. * After making the desired changes in the builder, the modified key can be extracted using
* {@link SecretKeyRingEditorInterface#done()}.
* *
* @param secretKeys secret key ring * @param secretKeys secret key ring
* @param referenceTime reference time used as signature creation date * @param referenceTime reference time used as signature creation date
@ -144,8 +141,9 @@ class PGPainless private constructor() {
SecretKeyRingEditor(secretKey, referenceTime) SecretKeyRingEditor(secretKey, referenceTime)
/** /**
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing]. * Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] /
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time. * [PGPSecretKeyRing]. This method can be used to determine expiration dates, key flags and
* other information about a key at a specific time.
* *
* @param keyRing key ring * @param keyRing key ring
* @param referenceTime date of inspection * @param referenceTime date of inspection
@ -161,15 +159,13 @@ class PGPainless private constructor() {
* *
* @return policy * @return policy
*/ */
@JvmStatic @JvmStatic fun getPolicy() = Policy.getInstance()
fun getPolicy() = Policy.getInstance()
/** /**
* Create different kinds of signatures on other keys. * Create different kinds of signatures on other keys.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun certify() = CertifyCertificate()
fun certify() = CertifyCertificate()
} }
} }

View File

@ -4,10 +4,7 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
enum class AEADAlgorithm( enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: Int) {
val algorithmId: Int,
val ivLength: Int,
val tagLength: Int) {
EAX(1, 16, 16), EAX(1, 16, 16),
OCB(2, 15, 16), OCB(2, 15, 16),
GCM(3, 12, 16), GCM(3, 12, 16),
@ -16,15 +13,12 @@ enum class AEADAlgorithm(
companion object { companion object {
@JvmStatic @JvmStatic
fun fromId(id: Int): AEADAlgorithm? { fun fromId(id: Int): AEADAlgorithm? {
return values().firstOrNull { return values().firstOrNull { algorithm -> algorithm.algorithmId == id }
algorithm -> algorithm.algorithmId == id
}
} }
@JvmStatic @JvmStatic
fun requireFromId(id: Int): AEADAlgorithm { fun requireFromId(id: Int): AEADAlgorithm {
return fromId(id) ?: return fromId(id) ?: throw NoSuchElementException("No AEADAlgorithm found for id $id")
throw NoSuchElementException("No AEADAlgorithm found for id $id")
} }
} }
} }

View File

@ -7,7 +7,8 @@ package org.pgpainless.algorithm
class AlgorithmSuite( class AlgorithmSuite(
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>, symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
hashAlgorithms: List<HashAlgorithm>, hashAlgorithms: List<HashAlgorithm>,
compressionAlgorithms: List<CompressionAlgorithm>) { compressionAlgorithms: List<CompressionAlgorithm>
) {
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet() val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet() val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
@ -16,30 +17,31 @@ class AlgorithmSuite(
companion object { companion object {
@JvmStatic @JvmStatic
val defaultSymmetricKeyAlgorithms = listOf( val defaultSymmetricKeyAlgorithms =
listOf(
SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_256,
SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_192,
SymmetricKeyAlgorithm.AES_128) SymmetricKeyAlgorithm.AES_128)
@JvmStatic @JvmStatic
val defaultHashAlgorithms = listOf( val defaultHashAlgorithms =
listOf(
HashAlgorithm.SHA512, HashAlgorithm.SHA512,
HashAlgorithm.SHA384, HashAlgorithm.SHA384,
HashAlgorithm.SHA256, HashAlgorithm.SHA256,
HashAlgorithm.SHA224) HashAlgorithm.SHA224)
@JvmStatic @JvmStatic
val defaultCompressionAlgorithms = listOf( val defaultCompressionAlgorithms =
listOf(
CompressionAlgorithm.ZLIB, CompressionAlgorithm.ZLIB,
CompressionAlgorithm.BZIP2, CompressionAlgorithm.BZIP2,
CompressionAlgorithm.ZIP, CompressionAlgorithm.ZIP,
CompressionAlgorithm.UNCOMPRESSED) CompressionAlgorithm.UNCOMPRESSED)
@JvmStatic @JvmStatic
val defaultAlgorithmSuite = AlgorithmSuite( val defaultAlgorithmSuite =
defaultSymmetricKeyAlgorithms, AlgorithmSuite(
defaultHashAlgorithms, defaultSymmetricKeyAlgorithms, defaultHashAlgorithms, defaultCompressionAlgorithms)
defaultCompressionAlgorithms)
} }
} }

View File

@ -4,18 +4,17 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
enum class CertificationType( enum class CertificationType(val signatureType: SignatureType) {
val signatureType: SignatureType
) {
/** /**
* The issuer of this certification does not make any particular assertion as to how well the certifier has * The issuer of this certification does not make any particular assertion as to how well the
* checked that the owner of the key is in fact the person described by the User ID. * certifier has checked that the owner of the key is in fact the person described by the User
* ID.
*/ */
GENERIC(SignatureType.GENERIC_CERTIFICATION), GENERIC(SignatureType.GENERIC_CERTIFICATION),
/** /**
* The issuer of this certification has not done any verification of the claim that the owner of this key is * The issuer of this certification has not done any verification of the claim that the owner of
* the User ID specified. * this key is the User ID specified.
*/ */
NONE(SignatureType.NO_CERTIFICATION), NONE(SignatureType.NO_CERTIFICATION),

View File

@ -20,22 +20,20 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
companion object { companion object {
/** /**
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
* If an invalid id is provided, null is returned. * an invalid id is provided, null is returned.
* *
* @param id id * @param id id
* @return compression algorithm * @return compression algorithm
*/ */
@JvmStatic @JvmStatic
fun fromId(id: Int): CompressionAlgorithm? { fun fromId(id: Int): CompressionAlgorithm? {
return values().firstOrNull { return values().firstOrNull { c -> c.algorithmId == id }
c -> c.algorithmId == id
}
} }
/** /**
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. * Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
* If an invalid id is provided, throw an [NoSuchElementException]. * an invalid id is provided, throw an [NoSuchElementException].
* *
* @param id id * @param id id
* @return compression algorithm * @return compression algorithm
@ -43,8 +41,8 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
*/ */
@JvmStatic @JvmStatic
fun requireFromId(id: Int): CompressionAlgorithm { fun requireFromId(id: Int): CompressionAlgorithm {
return fromId(id) ?: return fromId(id)
throw NoSuchElementException("No CompressionAlgorithm found for id $id") ?: throw NoSuchElementException("No CompressionAlgorithm found for id $id")
} }
} }
} }

View File

@ -6,15 +6,11 @@ package org.pgpainless.algorithm
enum class DocumentSignatureType(val signatureType: SignatureType) { enum class DocumentSignatureType(val signatureType: SignatureType) {
/** /** Signature is calculated over the unchanged binary data. */
* Signature is calculated over the unchanged binary data.
*/
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT), BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
/** /**
* The signature is calculated over the text data with its line endings * The signature is calculated over the text data with its line endings converted to `<CR><LF>`.
* converted to `<CR><LF>`.
*/ */
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT), CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
;
} }

View File

@ -5,18 +5,10 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
enum class EncryptionPurpose { enum class EncryptionPurpose {
/** /** The stream will encrypt communication that goes over the wire. E.g. EMail, Chat... */
* The stream will encrypt communication that goes over the wire.
* E.g. EMail, Chat...
*/
COMMUNICATIONS, COMMUNICATIONS,
/** /** The stream will encrypt data at rest. E.g. Encrypted backup... */
* The stream will encrypt data at rest.
* E.g. Encrypted backup...
*/
STORAGE, STORAGE,
/** /** The stream will use keys with either flags to encrypt the data. */
* The stream will use keys with either flags to encrypt the data.
*/
ANY ANY
} }

View File

@ -12,42 +12,44 @@ package org.pgpainless.algorithm
enum class Feature(val featureId: Byte) { enum class Feature(val featureId: Byte) {
/** /**
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification * Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using
* Detection Code Packets. * Modification Detection Code Packets.
* *
* See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14) * See
* [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
*/ */
MODIFICATION_DETECTION(0x01), MODIFICATION_DETECTION(0x01),
/** /**
* Support for Authenticated Encryption with Additional Data (AEAD). * Support for Authenticated Encryption with Additional Data (AEAD). If a key announces this
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets. * feature, it signals support for consuming AEAD Encrypted Data Packets.
* *
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
* NOTE: This value is currently RESERVED.
* *
* See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-) * See
* [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
*/ */
GNUPG_AEAD_ENCRYPTED_DATA(0x02), GNUPG_AEAD_ENCRYPTED_DATA(0x02),
/** /**
* If a key announces this feature, it is a version 5 public key. * If a key announces this feature, it is a version 5 public key. The version 5 format is
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material. * similar to the version 4 format except for the addition of a count for the key material. This
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case * count helps to parse secret key packets (which are an extension of the public key packet
* of an unknown algorithm. * format) in the case of an unknown algorithm. In addition, fingerprints of version 5 keys are
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys. * calculated differently from version 4 keys.
* *
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! * NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
* NOTE: This value is currently RESERVED.
* *
* See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats) * See
* [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
*/ */
GNUPG_VERSION_5_PUBLIC_KEY(0x04), GNUPG_VERSION_5_PUBLIC_KEY(0x04),
/** /**
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2. * Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
* *
* See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd) * See
* [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
*/ */
MODIFICATION_DETECTION_2(0x08), MODIFICATION_DETECTION_2(0x08),
; ;
@ -55,28 +57,25 @@ enum class Feature(val featureId: Byte) {
companion object { companion object {
@JvmStatic @JvmStatic
fun fromId(id: Byte): Feature? { fun fromId(id: Byte): Feature? {
return values().firstOrNull { return values().firstOrNull { f -> f.featureId == id }
f -> f.featureId == id
}
} }
@JvmStatic @JvmStatic
fun requireFromId(id: Byte): Feature { fun requireFromId(id: Byte): Feature {
return fromId(id) ?: return fromId(id) ?: throw NoSuchElementException("Unknown feature id encountered: $id")
throw NoSuchElementException("Unknown feature id encountered: $id")
} }
@JvmStatic @JvmStatic
fun fromBitmask(bitmask: Int): List<Feature> { fun fromBitmask(bitmask: Int): List<Feature> {
return values().filter { return values().filter { it.featureId.toInt() and bitmask != 0 }
it.featureId.toInt() and bitmask != 0
}
} }
@JvmStatic @JvmStatic
fun toBitmask(vararg features: Feature): Byte { fun toBitmask(vararg features: Feature): Byte {
return features.map { it.featureId.toInt() } return features
.reduceOrNull { mask, f -> mask or f }?.toByte() .map { it.featureId.toInt() }
.reduceOrNull { mask, f -> mask or f }
?.toByte()
?: 0 ?: 0
} }
} }

View File

@ -11,36 +11,33 @@ package org.pgpainless.algorithm
*/ */
enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) { enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
@Deprecated("MD5 is deprecated") @Deprecated("MD5 is deprecated") MD5(1, "MD5"),
MD5 (1, "MD5"), SHA1(2, "SHA1"),
SHA1 (2, "SHA1"), RIPEMD160(3, "RIPEMD160"),
RIPEMD160 (3, "RIPEMD160"), SHA256(8, "SHA256"),
SHA256 (8, "SHA256"), SHA384(9, "SHA384"),
SHA384 (9, "SHA384"), SHA512(10, "SHA512"),
SHA512 (10, "SHA512"), SHA224(11, "SHA224"),
SHA224 (11, "SHA224"), SHA3_256(12, "SHA3-256"),
SHA3_256 (12, "SHA3-256"), SHA3_512(14, "SHA3-512"),
SHA3_512 (14, "SHA3-512"),
; ;
companion object { companion object {
/** /**
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
* If an invalid algorithm id was provided, null is returned. * invalid algorithm id was provided, null is returned.
* *
* @param id numeric id * @param id numeric id
* @return enum value * @return enum value
*/ */
@JvmStatic @JvmStatic
fun fromId(id: Int): HashAlgorithm? { fun fromId(id: Int): HashAlgorithm? {
return values().firstOrNull { return values().firstOrNull { h -> h.algorithmId == id }
h -> h.algorithmId == id
}
} }
/** /**
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. * Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
* If an invalid algorithm id was provided, throw a [NoSuchElementException]. * invalid algorithm id was provided, throw a [NoSuchElementException].
* *
* @param id algorithm id * @param id algorithm id
* @return enum value * @return enum value
@ -48,15 +45,15 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
*/ */
@JvmStatic @JvmStatic
fun requireFromId(id: Int): HashAlgorithm { fun requireFromId(id: Int): HashAlgorithm {
return fromId(id) ?: return fromId(id) ?: throw NoSuchElementException("No HashAlgorithm found for id $id")
throw NoSuchElementException("No HashAlgorithm found for id $id")
} }
/** /**
* Return the [HashAlgorithm] value that corresponds to the provided name. * Return the [HashAlgorithm] value that corresponds to the provided name. If an invalid
* If an invalid algorithm name was provided, null is returned. * algorithm name was provided, null is returned.
* *
* See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4) * See
* [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
* for a list of algorithms and names. * for a list of algorithms and names.
* *
* @param name text name * @param name text name
@ -65,11 +62,8 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
@JvmStatic @JvmStatic
fun fromName(name: String): HashAlgorithm? { fun fromName(name: String): HashAlgorithm? {
return name.uppercase().let { algoName -> return name.uppercase().let { algoName ->
values().firstOrNull { values().firstOrNull { it.algorithmName == algoName }
it.algorithmName == algoName ?: values().firstOrNull { it.algorithmName == algoName.replace("-", "") }
} ?: values().firstOrNull {
it.algorithmName == algoName.replace("-", "")
}
} }
} }
} }

View File

@ -6,40 +6,26 @@ package org.pgpainless.algorithm
enum class KeyFlag(val flag: Int) { enum class KeyFlag(val flag: Int) {
/** /** This key may be used to certify third-party keys. */
* This key may be used to certify third-party keys. CERTIFY_OTHER(1),
*/
CERTIFY_OTHER (1),
/** /** This key may be used to sign data. */
* This key may be used to sign data. SIGN_DATA(2),
*/
SIGN_DATA (2),
/** /** This key may be used to encrypt communications. */
* This key may be used to encrypt communications. ENCRYPT_COMMS(4),
*/
ENCRYPT_COMMS (4),
/** /** This key may be used to encrypt storage. */
* This key may be used to encrypt storage.
*/
ENCRYPT_STORAGE(8), ENCRYPT_STORAGE(8),
/** /** The private component of this key may have been split by a secret-sharing mechanism. */
* The private component of this key may have been split by a secret-sharing mechanism. SPLIT(16),
*/
SPLIT (16),
/** /** This key may be used for authentication. */
* This key may be used for authentication. AUTHENTICATION(32),
*/
AUTHENTICATION (32),
/** /** The private component of this key may be in the possession of more than one person. */
* The private component of this key may be in the possession of more than one person. SHARED(128),
*/
SHARED (128),
; ;
companion object { companion object {
@ -52,9 +38,7 @@ enum class KeyFlag(val flag: Int) {
*/ */
@JvmStatic @JvmStatic
fun fromBitmask(bitmask: Int): List<KeyFlag> { fun fromBitmask(bitmask: Int): List<KeyFlag> {
return values().filter { return values().filter { it.flag and bitmask != 0 }
it.flag and bitmask != 0
}
} }
/** /**
@ -65,13 +49,12 @@ enum class KeyFlag(val flag: Int) {
*/ */
@JvmStatic @JvmStatic
fun toBitmask(vararg flags: KeyFlag): Int { fun toBitmask(vararg flags: KeyFlag): Int {
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } ?: 0
?: 0
} }
/** /**
* Return true if the provided bitmask has the bit for the provided flag set. * Return true if the provided bitmask has the bit for the provided flag set. Return false
* Return false if the mask does not contain the flag. * if the mask does not contain the flag.
* *
* @param mask bitmask * @param mask bitmask
* @param flag flag to be tested for * @param flag flag to be tested for
@ -84,9 +67,7 @@ enum class KeyFlag(val flag: Int) {
@JvmStatic @JvmStatic
fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean { fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean {
return flags.any { return flags.any { hasKeyFlag(mask, it) }
hasKeyFlag(mask, it)
}
} }
} }
} }

View File

@ -22,7 +22,6 @@ enum class OpenPgpPacket(val tag: Int) {
UATTR(17), UATTR(17),
SEIPD(18), SEIPD(18),
MDC(19), MDC(19),
EXP_1(60), EXP_1(60),
EXP_2(61), EXP_2(61),
EXP_3(62), EXP_3(62),
@ -32,15 +31,13 @@ enum class OpenPgpPacket(val tag: Int) {
companion object { companion object {
@JvmStatic @JvmStatic
fun fromTag(tag: Int): OpenPgpPacket? { fun fromTag(tag: Int): OpenPgpPacket? {
return values().firstOrNull { return values().firstOrNull { it.tag == tag }
it.tag == tag
}
} }
@JvmStatic @JvmStatic
fun requireFromTag(tag: Int): OpenPgpPacket { fun requireFromTag(tag: Int): OpenPgpPacket {
return fromTag(tag) ?: return fromTag(tag)
throw NoSuchElementException("No OpenPGP packet known for tag $tag") ?: throw NoSuchElementException("No OpenPGP packet known for tag $tag")
} }
} }
} }

View File

@ -12,85 +12,71 @@ package org.pgpainless.algorithm
enum class PublicKeyAlgorithm( enum class PublicKeyAlgorithm(
val algorithmId: Int, val algorithmId: Int,
val signingCapable: Boolean, val signingCapable: Boolean,
val encryptionCapable: Boolean) { val encryptionCapable: Boolean
) {
/** /** RSA capable of encryption and signatures. */
* RSA capable of encryption and signatures. RSA_GENERAL(1, true, true),
*/
RSA_GENERAL (1, true, true),
/** /**
* RSA with usage encryption. * RSA with usage encryption.
* *
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a> * @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
* notice</a>
*/ */
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", @Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
ReplaceWith("RSA_GENERAL")) RSA_ENCRYPT(2, false, true),
RSA_ENCRYPT (2, false, true),
/** /**
* RSA with usage of creating signatures. * RSA with usage of creating signatures.
* *
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a> * @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
* notice</a>
*/ */
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", @Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
ReplaceWith("RSA_GENERAL")) RSA_SIGN(3, true, false),
RSA_SIGN (3, true, false),
/** /** ElGamal with usage encryption. */
* ElGamal with usage encryption. ELGAMAL_ENCRYPT(16, false, true),
*/
ELGAMAL_ENCRYPT (16, false, true),
/** /** Digital Signature Algorithm. */
* Digital Signature Algorithm. DSA(17, true, false),
*/
DSA (17, true, false),
/** /** Elliptic Curve Diffie-Hellman. */
* Elliptic Curve Diffie-Hellman. ECDH(18, false, true),
*/
ECDH (18, false, true),
/** /** Elliptic Curve Digital Signature Algorithm. */
* Elliptic Curve Digital Signature Algorithm. ECDSA(19, true, false),
*/
ECDSA (19, true, false),
/** /**
* ElGamal General. * ElGamal General.
* *
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a> * @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation
* notice</a>
*/ */
@Deprecated("ElGamal is deprecated") @Deprecated("ElGamal is deprecated") ELGAMAL_GENERAL(20, true, true),
ELGAMAL_GENERAL (20, true, true),
/** /** Diffie-Hellman key exchange algorithm. */
* Diffie-Hellman key exchange algorithm. DIFFIE_HELLMAN(21, false, true),
*/
DIFFIE_HELLMAN (21, false, true),
/** /** Digital Signature Algorithm based on twisted Edwards Curves. */
* Digital Signature Algorithm based on twisted Edwards Curves. EDDSA(22, true, false),
*/
EDDSA (22, true, false),
; ;
fun isSigningCapable(): Boolean = signingCapable fun isSigningCapable(): Boolean = signingCapable
fun isEncryptionCapable(): Boolean = encryptionCapable fun isEncryptionCapable(): Boolean = encryptionCapable
companion object { companion object {
@JvmStatic @JvmStatic
fun fromId(id: Int): PublicKeyAlgorithm? { fun fromId(id: Int): PublicKeyAlgorithm? {
return values().firstOrNull { return values().firstOrNull { it.algorithmId == id }
it.algorithmId == id
}
} }
@JvmStatic @JvmStatic
fun requireFromId(id: Int): PublicKeyAlgorithm { fun requireFromId(id: Int): PublicKeyAlgorithm {
return fromId(id) ?: return fromId(id)
throw NoSuchElementException("No PublicKeyAlgorithm found for id $id") ?: throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
} }
} }
} }

View File

@ -4,53 +4,48 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
import org.pgpainless.util.DateUtil
import java.lang.AssertionError import java.lang.AssertionError
import java.util.* import java.util.*
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import org.pgpainless.util.DateUtil
class RevocationState private constructor( class RevocationState private constructor(val type: RevocationStateType, private val _date: Date?) :
val type: RevocationStateType, Comparable<RevocationState> {
private val _date: Date?): Comparable<RevocationState> {
val date: Date val date: Date
get() { get() {
if (!isSoftRevocation()) { if (!isSoftRevocation()) {
throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.") throw NoSuchElementException(
"RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
} }
return _date!! return _date!!
} }
private constructor(type: RevocationStateType): this(type, null) private constructor(type: RevocationStateType) : this(type, null)
fun isSoftRevocation() = type == RevocationStateType.softRevoked fun isSoftRevocation() = type == RevocationStateType.softRevoked
fun isHardRevocation() = type == RevocationStateType.hardRevoked fun isHardRevocation() = type == RevocationStateType.hardRevoked
fun isNotRevoked() = type == RevocationStateType.notRevoked fun isNotRevoked() = type == RevocationStateType.notRevoked
companion object { companion object {
@JvmStatic @JvmStatic fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
@JvmStatic @JvmStatic
fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date) fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date)
@JvmStatic @JvmStatic fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
} }
override fun compareTo(other: RevocationState): Int { override fun compareTo(other: RevocationState): Int {
return when(type) { return when (type) {
RevocationStateType.notRevoked -> RevocationStateType.notRevoked -> if (other.isNotRevoked()) 0 else -1
if (other.isNotRevoked()) 0
else -1
RevocationStateType.softRevoked -> RevocationStateType.softRevoked ->
if (other.isNotRevoked()) 1 if (other.isNotRevoked()) 1
// Compare soft dates in reverse // Compare soft dates in reverse
else if (other.isSoftRevocation()) other.date.compareTo(date) else if (other.isSoftRevocation()) other.date.compareTo(date) else -1
else -1 RevocationStateType.hardRevoked -> if (other.isHardRevocation()) 0 else 1
RevocationStateType.hardRevoked ->
if (other.isHardRevocation()) 0
else 1
else -> throw AssertionError("Unknown type: $type") else -> throw AssertionError("Unknown type: $type")
} }
} }
@ -80,7 +75,8 @@ class RevocationState private constructor(
return false return false
} }
if (isSoftRevocation()) { if (isSoftRevocation()) {
return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time return DateUtil.toSecondsPrecision(date).time ==
DateUtil.toSecondsPrecision(other.date).time
} }
return true return true
} }

View File

@ -5,18 +5,12 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
enum class RevocationStateType { enum class RevocationStateType {
/** /** Certificate is not revoked. */
* Certificate is not revoked.
*/
notRevoked, notRevoked,
/** /** Certificate is revoked with a soft revocation. */
* Certificate is revoked with a soft revocation.
*/
softRevoked, softRevoked,
/** /** Certificate is revoked with a hard revocation. */
* Certificate is revoked with a hard revocation.
*/
hardRevoked hardRevoked
} }

View File

@ -7,23 +7,24 @@ package org.pgpainless.algorithm
import org.bouncycastle.bcpg.SignatureSubpacketTags.* import org.bouncycastle.bcpg.SignatureSubpacketTags.*
/** /**
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature. * Enumeration of possible subpackets that might be found in the hashed and unhashed area of an
* OpenPGP signature.
* *
* See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1) * See
* [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
*/ */
enum class SignatureSubpacket(val code: Int) { enum class SignatureSubpacket(val code: Int) {
/** /**
* The time the signature was made. * The time the signature was made. MUST be present in the hashed area of the signature.
* MUST be present in the hashed area of the signature.
* *
* See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4) * See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4)
*/ */
signatureCreationTime(2), signatureCreationTime(2),
/** /**
* The validity period of the signature. This is the number of seconds * The validity period of the signature. This is the number of seconds after the signature
* after the signature creation time that the signature expires. If * creation time that the signature expires. If this is not present or has a value of zero, it
* this is not present or has a value of zero, it never expires. * never expires.
* *
* See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10) * See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10)
*/ */
@ -37,90 +38,76 @@ enum class SignatureSubpacket(val code: Int) {
exportableCertification(4), exportableCertification(4),
/** /**
* Signer asserts that the key is not only valid but also trustworthy at * Signer asserts that the key is not only valid but also trustworthy at the specified level.
* the specified level. Level 0 has the same meaning as an ordinary * Level 0 has the same meaning as an ordinary validity signature. Level 1 means that the signed
* validity signature. Level 1 means that the signed key is asserted to * key is asserted to be a valid, trusted introducer, with the 2nd octet of the body specifying
* be a valid, trusted introducer, with the 2nd octet of the body * the degree of trust. Level 2 means that the signed key is asserted to be trusted to issue
* specifying the degree of trust. Level 2 means that the signed key is * level 1 trust signatures, i.e., that it is a "meta introducer". Generally, a level n trust
* asserted to be trusted to issue level 1 trust signatures, i.e., that * signature asserts that a key is trusted to issue level n-1 trust signatures. The trust amount
* it is a "meta introducer". Generally, a level n trust signature * is in a range from 0-255, interpreted such that values less than 120 indicate partial trust
* asserts that a key is trusted to issue level n-1 trust signatures. * and values of 120 or greater indicate complete trust. Implementations SHOULD emit values of
* The trust amount is in a range from 0-255, interpreted such that * 60 for partial trust and 120 for complete trust.
* values less than 120 indicate partial trust and values of 120 or
* greater indicate complete trust. Implementations SHOULD emit values
* of 60 for partial trust and 120 for complete trust.
* *
* See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13) * See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13)
*/ */
trustSignature(5), trustSignature(5),
/** /**
* Used in conjunction with trust Signature packets (of level greater 0) to * Used in conjunction with trust Signature packets (of level greater 0) to limit the scope of
* limit the scope of trust that is extended. Only signatures by the * trust that is extended. Only signatures by the target key on User IDs that match the regular
* target key on User IDs that match the regular expression in the body * expression in the body of this packet have trust extended by the trust Signature subpacket.
* of this packet have trust extended by the trust Signature subpacket. * The regular expression uses the same syntax as the Henry Spencer's "almost public domain"
* The regular expression uses the same syntax as the Henry Spencer's * regular expression [REGEX] package. A description of the syntax is found in Section 8 below.
* "almost public domain" regular expression [REGEX] package. A
* description of the syntax is found in Section 8 below.
* *
* See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14) * See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14)
*/ */
regularExpression(6), regularExpression(6),
/** /**
* Signature's revocability status. The packet body contains a Boolean * Signature's revocability status. The packet body contains a Boolean flag indicating whether
* flag indicating whether the signature is revocable. Signatures that * the signature is revocable. Signatures that are not revocable have any later revocation
* are not revocable have any later revocation signatures ignored. They * signatures ignored. They represent a commitment by the signer that he cannot revoke his
* represent a commitment by the signer that he cannot revoke his * signature for the life of his key. If this packet is not present, the signature is revocable.
* signature for the life of his key. If this packet is not present,
* the signature is revocable.
* *
* See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12) * See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12)
*/ */
revocable(7), revocable(7),
/** /**
* The validity period of the key. This is the number of seconds after * The validity period of the key. This is the number of seconds after the key creation time
* the key creation time that the key expires. If this is not present * that the key expires. If this is not present or has a value of zero, the key never expires.
* or has a value of zero, the key never expires. This is found only on * This is found only on a self-signature.
* a self-signature.
* *
* See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6) * See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6)
*/ */
keyExpirationTime(9), keyExpirationTime(9),
/** /** Placeholder for backwards compatibility. */
* Placeholder for backwards compatibility.
*/
placeholder(10), placeholder(10),
/** /**
* Symmetric algorithm numbers that indicate which algorithms the keyholder * Symmetric algorithm numbers that indicate which algorithms the keyholder prefers to use. The
* prefers to use. The subpackets body is an ordered list of * subpackets body is an ordered list of octets with the most preferred listed first. It is
* octets with the most preferred listed first. It is assumed that only * assumed that only algorithms listed are supported by the recipient's software. This is only
* algorithms listed are supported by the recipient's software. * found on a self-signature.
* This is only found on a self-signature.
* *
* See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7) * See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7)
*/ */
preferredSymmetricAlgorithms(11), preferredSymmetricAlgorithms(11),
/** /**
* Authorizes the specified key to issue revocation signatures for this * Authorizes the specified key to issue revocation signatures for this key. Class octet must
* key. Class octet must have bit 0x80 set. If the bit 0x40 is set, * have bit 0x80 set. If the bit 0x40 is set, then this means that the revocation information is
* then this means that the revocation information is sensitive. Other * sensitive. Other bits are for future expansion to other kinds of authorizations. This is
* bits are for future expansion to other kinds of authorizations. This * found on a self-signature.
* is found on a self-signature.
* *
* If the "sensitive" flag is set, the keyholder feels this subpacket * If the "sensitive" flag is set, the keyholder feels this subpacket contains private trust
* contains private trust information that describes a real-world * information that describes a real-world sensitive relationship. If this flag is set,
* sensitive relationship. If this flag is set, implementations SHOULD * implementations SHOULD NOT export this signature to other users except in cases where the
* NOT export this signature to other users except in cases where the * data needs to be available: when the signature is being sent to the designated revoker, or
* data needs to be available: when the signature is being sent to the * when it is accompanied by a revocation signature from that revoker. Note that it may be
* designated revoker, or when it is accompanied by a revocation * appropriate to isolate this subpacket within a separate signature so that it is not combined
* signature from that revoker. Note that it may be appropriate to * with other subpackets that need to be exported.
* isolate this subpacket within a separate signature so that it is not
* combined with other subpackets that need to be exported.
* *
* See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15) * See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15)
*/ */
@ -134,54 +121,48 @@ enum class SignatureSubpacket(val code: Int) {
issuerKeyId(16), issuerKeyId(16),
/** /**
* This subpacket describes a "notation" on the signature that the * This subpacket describes a "notation" on the signature that the issuer wishes to make. The
* issuer wishes to make. The notation has a name and a value, each of * notation has a name and a value, each of which are strings of octets. There may be more than
* which are strings of octets. There may be more than one notation in * one notation in a signature. Notations can be used for any extension the issuer of the
* a signature. Notations can be used for any extension the issuer of * signature cares to make. The "flags" field holds four octets of flags.
* the signature cares to make. The "flags" field holds four octets of
* flags.
* *
* See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16) * See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16)
*/ */
notationData(20), notationData(20),
/** /**
* Message digest algorithm numbers that indicate which algorithms the * Message digest algorithm numbers that indicate which algorithms the keyholder prefers to
* keyholder prefers to receive. Like the preferred symmetric * receive. Like the preferred symmetric algorithms, the list is ordered. This is only found on
* algorithms, the list is ordered. * a self-signature.
* This is only found on a self-signature.
* *
* See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8) * See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8)
*/ */
preferredHashAlgorithms(21), preferredHashAlgorithms(21),
/** /**
* Compression algorithm numbers that indicate which algorithms the * Compression algorithm numbers that indicate which algorithms the keyholder prefers to use.
* keyholder prefers to use. Like the preferred symmetric algorithms, the * Like the preferred symmetric algorithms, the list is ordered. If this subpacket is not
* list is ordered. If this subpacket is not included, ZIP is preferred. * included, ZIP is preferred. A zero denotes that uncompressed data is preferred; the
* A zero denotes that uncompressed data is preferred; the keyholder's * keyholder's software might have no compression software in that implementation. This is only
* software might have no compression software in that implementation. * found on a self-signature.
* This is only found on a self-signature.
* *
* See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9) * See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9)
*/ */
preferredCompressionAlgorithms(22), preferredCompressionAlgorithms(22),
/** /**
* This is a list of one-bit flags that indicate preferences that the * This is a list of one-bit flags that indicate preferences that the keyholder has about how
* keyholder has about how the key is handled on a key server. All * the key is handled on a key server. All undefined flags MUST be zero. This is found only on a
* undefined flags MUST be zero. * self-signature.
* This is found only on a self-signature.
* *
* See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17) * See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17)
*/ */
keyServerPreferences(23), keyServerPreferences(23),
/** /**
* This is a URI of a key server that the keyholder prefers be used for * This is a URI of a key server that the keyholder prefers be used for updates. Note that keys
* updates. Note that keys with multiple User IDs can have a preferred * with multiple User IDs can have a preferred key server for each User ID. Note also that since
* key server for each User ID. Note also that since this is a URI, the * this is a URI, the key server can actually be a copy of the key retrieved by ftp, http,
* key server can actually be a copy of the key retrieved by ftp, http,
* finger, etc. * finger, etc.
* *
* See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18) * See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18)
@ -189,63 +170,55 @@ enum class SignatureSubpacket(val code: Int) {
preferredKeyServers(24), preferredKeyServers(24),
/** /**
* This is a flag in a User ID's self-signature that states whether this * This is a flag in a User ID's self-signature that states whether this User ID is the main
* User ID is the main User ID for this key. It is reasonable for an * User ID for this key. It is reasonable for an implementation to resolve ambiguities in
* implementation to resolve ambiguities in preferences, etc. by * preferences, etc. by referring to the primary User ID. If this flag is absent, its value is
* referring to the primary User ID. If this flag is absent, its value * zero. If more than one User ID in a key is marked as primary, the implementation may resolve
* is zero. If more than one User ID in a key is marked as primary, the * the ambiguity in any way it sees fit, but it is RECOMMENDED that priority be given to the
* implementation may resolve the ambiguity in any way it sees fit, but * User ID with the most recent self-signature.
* it is RECOMMENDED that priority be given to the User ID with the most
* recent self-signature.
* *
* When appearing on a self-signature on a User ID packet, this * When appearing on a self-signature on a User ID packet, this subpacket applies only to User
* subpacket applies only to User ID packets. When appearing on a * ID packets. When appearing on a self-signature on a User Attribute packet, this subpacket
* self-signature on a User Attribute packet, this subpacket applies * applies only to User Attribute packets. That is to say, there are two different and
* only to User Attribute packets. That is to say, there are two * independent "primaries" -- one for User IDs, and one for User Attributes.
* different and independent "primaries" -- one for User IDs, and one
* for User Attributes.
* *
* See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19) * See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19)
*/ */
primaryUserId(25), primaryUserId(25),
/** /**
* This subpacket contains a URI of a document that describes the policy * This subpacket contains a URI of a document that describes the policy under which the
* under which the signature was issued. * signature was issued.
* *
* See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20) * See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20)
*/ */
policyUrl(26), policyUrl(26),
/** /**
* This subpacket contains a list of binary flags that hold information * This subpacket contains a list of binary flags that hold information about a key. It is a
* about a key. It is a string of octets, and an implementation MUST * string of octets, and an implementation MUST NOT assume a fixed size. This is so it can grow
* NOT assume a fixed size. This is so it can grow over time. If a * over time. If a list is shorter than an implementation expects, the unstated flags are
* list is shorter than an implementation expects, the unstated flags * considered to be zero.
* are considered to be zero.
* *
* See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21) * See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21)
*/ */
keyFlags(27), keyFlags(27),
/** /**
* This subpacket allows a keyholder to state which User ID is * This subpacket allows a keyholder to state which User ID is responsible for the signing. Many
* responsible for the signing. Many keyholders use a single key for * keyholders use a single key for different purposes, such as business communications as well
* different purposes, such as business communications as well as * as personal communications. This subpacket allows such a keyholder to state which of their
* personal communications. This subpacket allows such a keyholder to * roles is making a signature.
* state which of their roles is making a signature.
* *
* See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22) * See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22)
*/ */
signerUserId(28), signerUserId(28),
/** /**
* This subpacket is used only in key revocation and certification * This subpacket is used only in key revocation and certification revocation signatures. It
* revocation signatures. It describes the reason why the key or * describes the reason why the key or certificate was revoked.
* certificate was revoked.
* *
* The first octet contains a machine-readable code that denotes the * The first octet contains a machine-readable code that denotes the reason for the revocation:
* reason for the revocation:
* *
* 0 - No reason specified (key revocations or cert revocations) * 0 - No reason specified (key revocations or cert revocations)
* 1 - Key is superseded (key revocations) * 1 - Key is superseded (key revocations)
@ -259,117 +232,105 @@ enum class SignatureSubpacket(val code: Int) {
revocationReason(29), revocationReason(29),
/** /**
* The Features subpacket denotes which advanced OpenPGP features a * The Features subpacket denotes which advanced OpenPGP features a user's implementation
* user's implementation supports. This is so that as features are * supports. This is so that as features are added to OpenPGP that cannot be
* added to OpenPGP that cannot be backwards-compatible, a user can * backwards-compatible, a user can state that they can use that feature. The flags are single
* state that they can use that feature. The flags are single bits that * bits that indicate that a given feature is supported.
* indicate that a given feature is supported.
* *
* This subpacket is similar to a preferences subpacket, and only * This subpacket is similar to a preferences subpacket, and only appears in a self-signature.
* appears in a self-signature.
* *
* See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24) * See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
*/ */
features(30), features(30),
/** /**
* This subpacket identifies a specific target signature to which a * This subpacket identifies a specific target signature to which a signature refers. For
* signature refers. For revocation signatures, this subpacket * revocation signatures, this subpacket provides explicit designation of which signature is
* provides explicit designation of which signature is being revoked. * being revoked. For a third-party or timestamp signature, this designates what signature is
* For a third-party or timestamp signature, this designates what * signed. All arguments are an identifier of that target signature.
* signature is signed. All arguments are an identifier of that target
* signature.
* *
* The N octets of hash data MUST be the size of the hash of the * The N octets of hash data MUST be the size of the hash of the signature. For example, a
* signature. For example, a target signature with a SHA-1 hash MUST * target signature with a SHA-1 hash MUST have 20 octets of hash data.
* have 20 octets of hash data.
* *
* See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25) * See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25)
*/ */
signatureTarget(31), signatureTarget(31),
/** /**
* This subpacket contains a complete Signature packet body as * This subpacket contains a complete Signature packet body as specified in Section 5.2 above.
* specified in Section 5.2 above. It is useful when one signature * It is useful when one signature needs to refer to, or be incorporated in, another signature.
* needs to refer to, or be incorporated in, another signature.
* *
* See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26) * See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26)
*/ */
embeddedSignature(32), embeddedSignature(32),
/** /**
* The OpenPGP Key fingerprint of the key issuing the signature. This * The OpenPGP Key fingerprint of the key issuing the signature. This subpacket SHOULD be
* subpacket SHOULD be included in all signatures. If the version of * included in all signatures. If the version of the issuing key is 4 and an Issuer subpacket is
* the issuing key is 4 and an Issuer subpacket is also included in the * also included in the signature, the key ID of the Issuer subpacket MUST match the low 64 bits
* signature, the key ID of the Issuer subpacket MUST match the low 64 * of the fingerprint.
* bits of the fingerprint.
* *
* Note that the length N of the fingerprint for a version 4 key is 20 * Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
* octets; for a version 5 key N is 32. * key N is 32.
* *
* See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28) * See
* [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
*/ */
issuerFingerprint(33), issuerFingerprint(33),
/** /**
* AEAD algorithm numbers that indicate which AEAD algorithms the * AEAD algorithm numbers that indicate which AEAD algorithms the keyholder prefers to use. The
* keyholder prefers to use. The subpackets body is an ordered list of * subpackets body is an ordered list of octets with the most preferred listed first. It is
* octets with the most preferred listed first. It is assumed that only * assumed that only algorithms listed are supported by the recipient's software. This is only
* algorithms listed are supported by the recipient's software. * found on a self-signature. Note that support for the AEAD Encrypted Data packet in the
* This is only found on a self-signature. * general is indicated by a Feature Flag.
* Note that support for the AEAD Encrypted Data packet in the general
* is indicated by a Feature Flag.
* *
* See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8) * See
* [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
*/ */
preferredAEADAlgorithms(39), preferredAEADAlgorithms(39),
/** /**
* The OpenPGP Key fingerprint of the intended recipient primary key. * The OpenPGP Key fingerprint of the intended recipient primary key. If one or more subpackets
* If one or more subpackets of this type are included in a signature, * of this type are included in a signature, it SHOULD be considered valid only in an encrypted
* it SHOULD be considered valid only in an encrypted context, where the * context, where the key it was encrypted to is one of the indicated primary keys, or one of
* key it was encrypted to is one of the indicated primary keys, or one * their subkeys. This can be used to prevent forwarding a signature outside its intended,
* of their subkeys. This can be used to prevent forwarding a signature * encrypted context.
* outside its intended, encrypted context.
* *
* Note that the length N of the fingerprint for a version 4 key is 20 * Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
* octets; for a version 5 key N is 32. * key N is 32.
* *
* See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29) * See
* [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
*/ */
intendedRecipientFingerprint(35), intendedRecipientFingerprint(35),
/** /**
* This subpacket MUST only appear as a hashed subpacket of an * This subpacket MUST only appear as a hashed subpacket of an Attestation Key Signature. It has
* Attestation Key Signature. It has no meaning in any other signature * no meaning in any other signature type. It is used by the primary key to attest to a set of
* type. It is used by the primary key to attest to a set of third- * third- party certifications over the associated User ID or User Attribute. This enables the
* party certifications over the associated User ID or User Attribute. * holder of an OpenPGP primary key to mark specific third-party certifications as
* This enables the holder of an OpenPGP primary key to mark specific * re-distributable with the rest of the Transferable Public Key (see the "No-modify" flag in
* third-party certifications as re-distributable with the rest of the * "Key Server Preferences", above). Implementations MUST include exactly one Attested
* Transferable Public Key (see the "No-modify" flag in "Key Server * Certification subpacket in any generated Attestation Key Signature.
* Preferences", above). Implementations MUST include exactly one
* Attested Certification subpacket in any generated Attestation Key
* Signature.
* *
* See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30) * See
* [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
*/ */
attestedCertification(37) attestedCertification(37);
;
companion object { companion object {
/** /**
* Return the [SignatureSubpacket] that corresponds to the provided id. * Return the [SignatureSubpacket] that corresponds to the provided id. If an unmatched code
* If an unmatched code is presented, return null. * is presented, return null.
* *
* @param code id * @param code id
* @return signature subpacket * @return signature subpacket
*/ */
@JvmStatic @JvmStatic
fun fromCode(code: Int): SignatureSubpacket? { fun fromCode(code: Int): SignatureSubpacket? {
return values().firstOrNull { return values().firstOrNull { it.code == code }
it.code == code
}
} }
/** /**
@ -381,21 +342,20 @@ enum class SignatureSubpacket(val code: Int) {
*/ */
@JvmStatic @JvmStatic
fun requireFromCode(code: Int): SignatureSubpacket { fun requireFromCode(code: Int): SignatureSubpacket {
return fromCode(code) ?: return fromCode(code)
throw NoSuchElementException("No SignatureSubpacket tag found with code $code") ?: throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
} }
/** /**
* Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets]. * Convert an array of signature subpacket tags into a list of
* [SignatureSubpacket SignatureSubpackets].
* *
* @param codes array of codes * @param codes array of codes
* @return list of subpackets * @return list of subpackets
*/ */
@JvmStatic @JvmStatic
fun fromCodes(vararg codes: Int): List<SignatureSubpacket> { fun fromCodes(vararg codes: Int): List<SignatureSubpacket> {
return codes.toList().mapNotNull { return codes.toList().mapNotNull { fromCode(it) }
fromCode(it)
}
} }
} }
} }

View File

@ -7,150 +7,120 @@ package org.pgpainless.algorithm
import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignature
/** /**
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 * Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 See [PGPSignature] for
* See [PGPSignature] for comparison. * comparison.
* *
* See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11) * See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11)
*/ */
enum class SignatureType(val code: Int) { enum class SignatureType(val code: Int) {
/** /**
* Signature of a binary document. * Signature of a binary document. This means the signer owns it, created it, or certifies that
* This means the signer owns it, created it, or certifies that it * it has not been modified.
* has not been modified.
*/ */
BINARY_DOCUMENT(0x00), BINARY_DOCUMENT(0x00),
/** /**
* Signature of a canonical text document. * Signature of a canonical text document. This means the signer owns it, created it, or
* This means the signer owns it, created it, or certifies that it * certifies that it has not been modified. The signature is calculated over the text data with
* has not been modified. The signature is calculated over the text * its line endings converted to {@code <CR><LF>}.
* data with its line endings converted to {@code <CR><LF>}.
*/ */
CANONICAL_TEXT_DOCUMENT(0x01), CANONICAL_TEXT_DOCUMENT(0x01),
/** /**
* Standalone signature. * Standalone signature. This signature is a signature of only its own subpacket contents. It is
* This signature is a signature of only its own subpacket contents. * calculated identically to a signature over a zero-length binary document. Note that it
* It is calculated identically to a signature over a zero-length * doesn't make sense to have a V3 standalone signature.
* binary document. Note that it doesn't make sense to have a V3
* standalone signature.
*/ */
STANDALONE(0x02), STANDALONE(0x02),
/** /**
* Generic certification of a User ID and Public-Key packet. * Generic certification of a User ID and Public-Key packet. The issuer of this certification
* The issuer of this certification does not make any particular * does not make any particular assertion as to how well the certifier has checked that the
* assertion as to how well the certifier has checked that the owner * owner of the key is in fact the person described by the User ID.
* of the key is in fact the person described by the User ID.
*/ */
GENERIC_CERTIFICATION(0x10), GENERIC_CERTIFICATION(0x10),
/** /**
* Persona certification of a User ID and Public-Key packet. * Persona certification of a User ID and Public-Key packet. The issuer of this certification
* The issuer of this certification has not done any verification of * has not done any verification of the claim that the owner of this key is the User ID
* the claim that the owner of this key is the User ID specified. * specified.
*/ */
NO_CERTIFICATION(0x11), NO_CERTIFICATION(0x11),
/** /**
* Casual certification of a User ID and Public-Key packet. * Casual certification of a User ID and Public-Key packet. The issuer of this certification has
* The issuer of this certification has done some casual * done some casual verification of the claim of identity.
* verification of the claim of identity.
*/ */
CASUAL_CERTIFICATION(0x12), CASUAL_CERTIFICATION(0x12),
/** /**
* Positive certification of a User ID and Public-Key packet. * Positive certification of a User ID and Public-Key packet. The issuer of this certification
* The issuer of this certification has done substantial * has done substantial verification of the claim of identity.
* verification of the claim of identity.
*/ */
POSITIVE_CERTIFICATION(0x13), POSITIVE_CERTIFICATION(0x13),
/** /**
* Subkey Binding Signature. * Subkey Binding Signature. This signature is a statement by the top-level signing key that
* This signature is a statement by the top-level signing key that * indicates that it owns the subkey. This signature is calculated directly on the primary key
* indicates that it owns the subkey. This signature is calculated * and subkey, and not on any User ID or other packets. A signature that binds a signing subkey
* directly on the primary key and subkey, and not on any User ID or * MUST have an Embedded Signature subpacket in this binding signature that contains a
* other packets. A signature that binds a signing subkey MUST have * [#PRIMARYKEY_BINDING] signature made by the signing subkey on the primary key and subkey.
* an Embedded Signature subpacket in this binding signature that
* contains a [#PRIMARYKEY_BINDING] signature made by the
* signing subkey on the primary key and subkey.
*/ */
SUBKEY_BINDING(0x18), SUBKEY_BINDING(0x18),
/** /**
* Primary Key Binding Signature * Primary Key Binding Signature This signature is a statement by a signing subkey, indicating
* This signature is a statement by a signing subkey, indicating * that it is owned by the primary key and subkey. This signature is calculated the same way as
* that it is owned by the primary key and subkey. This signature * a [#SUBKEY_BINDING] signature: directly on the primary key and subkey, and not on any User ID
* is calculated the same way as a [#SUBKEY_BINDING] signature: * or other packets.
* directly on the primary key and subkey, and not on any User ID or
* other packets.
*/ */
PRIMARYKEY_BINDING(0x19), PRIMARYKEY_BINDING(0x19),
/** /**
* Signature directly on a key * Signature directly on a key This signature is calculated directly on a key. It binds the
* This signature is calculated directly on a key. It binds the * information in the Signature subpackets to the key, and is appropriate to be used for
* information in the Signature subpackets to the key, and is * subpackets that provide information about the key, such as the Revocation Key subpacket. It
* appropriate to be used for subpackets that provide information * is also appropriate for statements that non-self certifiers want to make about the key
* about the key, such as the Revocation Key subpacket. It is also * itself, rather than the binding between a key and a name.
* appropriate for statements that non-self certifiers want to make
* about the key itself, rather than the binding between a key and a
* name.
*/ */
DIRECT_KEY(0x1f), DIRECT_KEY(0x1f),
/** /**
* Key revocation signature * Key revocation signature The signature is calculated directly on the key being revoked. A
* The signature is calculated directly on the key being revoked. A * revoked key is not to be used. Only revocation signatures by the key being revoked, or by an
* revoked key is not to be used. Only revocation signatures by the * authorized revocation key, should be considered valid revocation signatures.
* key being revoked, or by an authorized revocation key, should be
* considered valid revocation signatures.
*/ */
KEY_REVOCATION(0x20), KEY_REVOCATION(0x20),
/** /**
* Subkey revocation signature * Subkey revocation signature The signature is calculated directly on the subkey being revoked.
* The signature is calculated directly on the subkey being revoked. * A revoked subkey is not to be used. Only revocation signatures by the top-level signature key
* A revoked subkey is not to be used. Only revocation signatures * that is bound to this subkey, or by an authorized revocation key, should be considered valid
* by the top-level signature key that is bound to this subkey, or
* by an authorized revocation key, should be considered valid
* revocation signatures. * revocation signatures.
*/ */
SUBKEY_REVOCATION(0x28), SUBKEY_REVOCATION(0x28),
/** /**
* Certification revocation signature * Certification revocation signature This signature revokes an earlier User ID certification
* This signature revokes an earlier User ID certification signature * signature (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. It should be issued
* (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. * by the same key that issued the revoked signature or an authorized revocation key. The
* It should be issued by the same key that issued the * signature is computed over the same data as the certificate that it revokes, and should have
* revoked signature or an authorized revocation key. The signature * a later creation date than that certificate.
* is computed over the same data as the certificate that it
* revokes, and should have a later creation date than that
* certificate.
*/ */
CERTIFICATION_REVOCATION(0x30), CERTIFICATION_REVOCATION(0x30),
/** /** Timestamp signature. This signature is only meaningful for the timestamp contained in it. */
* Timestamp signature.
* This signature is only meaningful for the timestamp contained in
* it.
*/
TIMESTAMP(0x40), TIMESTAMP(0x40),
/** /**
* Third-Party Confirmation signature. * Third-Party Confirmation signature. This signature is a signature over some other OpenPGP
* This signature is a signature over some other OpenPGP Signature * Signature packet(s). It is analogous to a notary seal on the signed data. A third-party
* packet(s). It is analogous to a notary seal on the signed data. * signature SHOULD include Signature Target subpacket(s) to give easy identification. Note that
* A third-party signature SHOULD include Signature Target * we really do mean SHOULD. There are plausible uses for this (such as a blind party that only
* subpacket(s) to give easy identification. Note that we really do * sees the signature, not the key or source document) that cannot include a target subpacket.
* mean SHOULD. There are plausible uses for this (such as a blind
* party that only sees the signature, not the key or source
* document) that cannot include a target subpacket.
*/ */
THIRD_PARTY_CONFIRMATION(0x50) THIRD_PARTY_CONFIRMATION(0x50);
;
companion object { companion object {
@ -162,9 +132,7 @@ enum class SignatureType(val code: Int) {
*/ */
@JvmStatic @JvmStatic
fun fromCode(code: Int): SignatureType? { fun fromCode(code: Int): SignatureType? {
return values().firstOrNull { return values().firstOrNull { it.code == code }
it.code == code
}
} }
/** /**
@ -176,8 +144,9 @@ enum class SignatureType(val code: Int) {
*/ */
@JvmStatic @JvmStatic
fun requireFromCode(code: Int): SignatureType { fun requireFromCode(code: Int): SignatureType {
return fromCode(code) ?: return fromCode(code)
throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.") ?: throw NoSuchElementException(
"Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
} }
/** /**
@ -188,8 +157,7 @@ enum class SignatureType(val code: Int) {
* @throws IllegalArgumentException in case of an unmatched signature type code * @throws IllegalArgumentException in case of an unmatched signature type code
*/ */
@JvmStatic @JvmStatic
@Deprecated("Deprecated in favor of requireFromCode", @Deprecated("Deprecated in favor of requireFromCode", ReplaceWith("requireFromCode"))
ReplaceWith("requireFromCode"))
fun valueOf(code: Int): SignatureType { fun valueOf(code: Int): SignatureType {
try { try {
return requireFromCode(code) return requireFromCode(code)

View File

@ -11,61 +11,52 @@ package org.pgpainless.algorithm
*/ */
enum class StreamEncoding(val code: Char) { enum class StreamEncoding(val code: Char) {
/** /** The Literal packet contains binary data. */
* The Literal packet contains binary data.
*/
BINARY('b'), BINARY('b'),
/** /**
* The Literal packet contains text data, and thus may need line ends converted to local form, or other * The Literal packet contains text data, and thus may need line ends converted to local form,
* text-mode changes. * or other text-mode changes.
*/ */
TEXT('t'), TEXT('t'),
/** /** Indication that the implementation believes that the literal data contains UTF-8 text. */
* Indication that the implementation believes that the literal data contains UTF-8 text.
*/
UTF8('u'), UTF8('u'),
/** /**
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions. * Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one). * conversions. RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral
* Both of these local modes are deprecated. * one). Both of these local modes are deprecated.
*/ */
@Deprecated("LOCAL is deprecated.") @Deprecated("LOCAL is deprecated.") LOCAL('l'),
LOCAL('l'),
; ;
companion object { companion object {
/** /**
* Return the [StreamEncoding] corresponding to the provided code identifier. * Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
* If no matching encoding is found, return null. * encoding is found, return null.
* *
* @param code identifier * @param code identifier
* @return encoding enum * @return encoding enum
*/ */
@JvmStatic @JvmStatic
fun fromCode(code: Int): StreamEncoding? { fun fromCode(code: Int): StreamEncoding? {
return values().firstOrNull { return values().firstOrNull { it.code == code.toChar() }
it.code == code.toChar() ?: if (code == 1) return LOCAL else null
} ?: if (code == 1) return LOCAL else null
} }
/** /**
* Return the [StreamEncoding] corresponding to the provided code identifier. * Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
* If no matching encoding is found, throw a [NoSuchElementException]. * encoding is found, throw a [NoSuchElementException].
* *
* @param code identifier * @param code identifier
* @return encoding enum * @return encoding enum
*
* @throws NoSuchElementException in case of an unmatched identifier * @throws NoSuchElementException in case of an unmatched identifier
*/ */
@JvmStatic @JvmStatic
fun requireFromCode(code: Int): StreamEncoding { fun requireFromCode(code: Int): StreamEncoding {
return fromCode(code) ?: return fromCode(code)
throw NoSuchElementException("No StreamEncoding found for code $code") ?: throw NoSuchElementException("No StreamEncoding found for code $code")
} }
} }
} }

View File

@ -11,110 +11,79 @@ package org.pgpainless.algorithm
*/ */
enum class SymmetricKeyAlgorithm(val algorithmId: Int) { enum class SymmetricKeyAlgorithm(val algorithmId: Int) {
/** /** Plaintext or unencrypted data. */
* Plaintext or unencrypted data. NULL(0),
*/
NULL (0),
/** /**
* IDEA is deprecated. * IDEA is deprecated.
*
* @deprecated use a different algorithm. * @deprecated use a different algorithm.
*/ */
@Deprecated("IDEA is deprecated.") @Deprecated("IDEA is deprecated.") IDEA(1),
IDEA (1),
/** /** TripleDES (DES-EDE - 168 bit key derived from 192). */
* TripleDES (DES-EDE - 168 bit key derived from 192). TRIPLE_DES(2),
*/
TRIPLE_DES (2),
/** /** CAST5 (128-bit key, as per RFC2144). */
* CAST5 (128-bit key, as per RFC2144). CAST5(3),
*/
CAST5 (3),
/** /** Blowfish (128-bit key, 16 rounds). */
* Blowfish (128-bit key, 16 rounds). BLOWFISH(4),
*/
BLOWFISH (4),
/** /** Reserved in RFC4880. SAFER-SK128 (13 rounds) */
* Reserved in RFC4880. SAFER(5),
* SAFER-SK128 (13 rounds)
*/
SAFER (5),
/** /** Reserved in RFC4880. Reserved for DES/SK */
* Reserved in RFC4880. DES(6),
* Reserved for DES/SK
*/
DES (6),
/** /** AES with 128-bit key. */
* AES with 128-bit key. AES_128(7),
*/
AES_128 (7),
/** /** AES with 192-bit key. */
* AES with 192-bit key. AES_192(8),
*/
AES_192 (8),
/** /** AES with 256-bit key. */
* AES with 256-bit key. AES_256(9),
*/
AES_256 (9),
/** /** Twofish with 256-bit key. */
* Twofish with 256-bit key. TWOFISH(10),
*/
TWOFISH (10),
/** /** Reserved for Camellia with 128-bit key. */
* Reserved for Camellia with 128-bit key. CAMELLIA_128(11),
*/
CAMELLIA_128 (11),
/** /** Reserved for Camellia with 192-bit key. */
* Reserved for Camellia with 192-bit key. CAMELLIA_192(12),
*/
CAMELLIA_192 (12),
/** /** Reserved for Camellia with 256-bit key. */
* Reserved for Camellia with 256-bit key. CAMELLIA_256(13),
*/
CAMELLIA_256 (13),
; ;
companion object { companion object {
/** /**
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
* If an invalid id is provided, null is returned. * an invalid id is provided, null is returned.
* *
* @param id numeric algorithm id * @param id numeric algorithm id
* @return symmetric key algorithm enum * @return symmetric key algorithm enum
*/ */
@JvmStatic @JvmStatic
fun fromId(id: Int): SymmetricKeyAlgorithm? { fun fromId(id: Int): SymmetricKeyAlgorithm? {
return values().firstOrNull { return values().firstOrNull { it.algorithmId == id }
it.algorithmId == id
}
} }
/** /**
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. * Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
* If an invalid id is provided, throw a [NoSuchElementException]. * an invalid id is provided, throw a [NoSuchElementException].
* *
* @param id numeric algorithm id * @param id numeric algorithm id
* @return symmetric key algorithm enum * @return symmetric key algorithm enum
*
* @throws NoSuchElementException if an unmatched id is provided * @throws NoSuchElementException if an unmatched id is provided
*/ */
@JvmStatic @JvmStatic
fun requireFromId(id: Int): SymmetricKeyAlgorithm { fun requireFromId(id: Int): SymmetricKeyAlgorithm {
return fromId(id) ?: return fromId(id)
throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id") ?: throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
} }
} }
} }

View File

@ -5,26 +5,25 @@
package org.pgpainless.algorithm package org.pgpainless.algorithm
/** /**
* Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. * Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. A trust signature subpacket marks
* A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act * the trustworthiness of a certificate and defines its capabilities to act as a trusted introducer.
* as a trusted introducer.
*/ */
class Trustworthiness(amount: Int, depth: Int) { class Trustworthiness(amount: Int, depth: Int) {
val depth = capDepth(depth) val depth = capDepth(depth)
val amount = capAmount(amount) val amount = capAmount(amount)
/** /**
* Returns true, if the trust amount is equal to 0. * Returns true, if the trust amount is equal to 0. This means the key is not trusted.
* This means the key is not trusted.
* *
* Otherwise return false * Otherwise return false
*
* @return true if untrusted * @return true if untrusted
*/ */
fun isNotTrusted() = amount == NOT_TRUSTED fun isNotTrusted() = amount == NOT_TRUSTED
/** /**
* Return true if the certificate is at least marginally trusted. * Return true if the certificate is at least marginally trusted. That is the case, if the trust
* That is the case, if the trust amount is greater than 0. * amount is greater than 0.
* *
* @return true if the cert is at least marginally trusted * @return true if the cert is at least marginally trusted
*/ */
@ -46,7 +45,8 @@ class Trustworthiness(amount: Int, depth: Int) {
fun isIntroducer() = depth >= 1 fun isIntroducer() = depth >= 1
/** /**
* Return true, if the certified cert can introduce certificates with trust depth of <pre>otherDepth</pre>. * Return true, if the certified cert can introduce certificates with trust depth of
* <pre>otherDepth</pre>.
* *
* @param otherDepth other certifications trust depth * @param otherDepth other certifications trust depth
* @return true if the cert can introduce the other * @return true if the cert can introduce the other
@ -54,7 +54,8 @@ class Trustworthiness(amount: Int, depth: Int) {
fun canIntroduce(otherDepth: Int) = depth > otherDepth fun canIntroduce(otherDepth: Int) = depth > otherDepth
/** /**
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre> trust depth. * Return true, if the certified cert can introduce certificates with the given <pre>other</pre>
* trust depth.
* *
* @param other other certificates trust depth * @param other other certificates trust depth
* @return true if the cert can introduce the other * @return true if the cert can introduce the other
@ -66,33 +67,29 @@ class Trustworthiness(amount: Int, depth: Int) {
const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced
const val NOT_TRUSTED = 0 // 0 is not trusted const val NOT_TRUSTED = 0 // 0 is not trusted
@JvmStatic @JvmStatic private val validRange = 0..255
private val validRange = 0..255
/** /**
* This means that we are fully convinced of the trustworthiness of the key. * This means that we are fully convinced of the trustworthiness of the key.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
/** /**
* This means that we are marginally (partially) convinced of the trustworthiness of the key. * This means that we are marginally (partially) convinced of the trustworthiness of the
* key.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
/** /**
* This means that we do not trust the key. * This means that we do not trust the key. Can be used to overwrite previous trust.
* Can be used to overwrite previous trust.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun untrusted() = Builder(NOT_TRUSTED)
fun untrusted() = Builder(NOT_TRUSTED)
@JvmStatic @JvmStatic
private fun capAmount(amount: Int): Int { private fun capAmount(amount: Int): Int {
@ -114,29 +111,28 @@ class Trustworthiness(amount: Int, depth: Int) {
class Builder(val amount: Int) { class Builder(val amount: Int) {
/** /**
* The key is a trusted introducer (depth 1). * The key is a trusted introducer (depth 1). Certifications made by this key are considered
* Certifications made by this key are considered trustworthy. * trustworthy.
* *
* @return trust * @return trust
*/ */
fun introducer() = Trustworthiness(amount, 1) fun introducer() = Trustworthiness(amount, 1)
/** /**
* The key is a meta introducer (depth 2). * The key is a meta introducer (depth 2). This key can introduce trusted introducers of
* This key can introduce trusted introducers of depth 1. * depth 1.
* *
* @return trust * @return trust
*/ */
fun metaIntroducer() = Trustworthiness(amount, 2) fun metaIntroducer() = Trustworthiness(amount, 2)
/** /**
* The key is a meta introducer of depth <pre>n</pre>. * The key is a meta introducer of depth <pre>n</pre>. This key can introduce meta
* This key can introduce meta introducers of depth <pre>n - 1</pre>. * introducers of depth <pre>n - 1</pre>.
* *
* @param n depth * @param n depth
* @return trust * @return trust
*/ */
fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d) fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d)
} }
} }

View File

@ -26,8 +26,8 @@ interface HashAlgorithmNegotiator {
companion object { companion object {
/** /**
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for
* based on the given [Policy]. * non-revocation signatures based on the given [Policy].
* *
* @param policy algorithm policy * @param policy algorithm policy
* @return negotiator * @return negotiator
@ -38,8 +38,8 @@ interface HashAlgorithmNegotiator {
} }
/** /**
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures * Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation
* based on the given [Policy]. * signatures based on the given [Policy].
* *
* @param policy algorithm policy * @param policy algorithm policy
* @return negotiator * @return negotiator
@ -57,14 +57,16 @@ interface HashAlgorithmNegotiator {
* @return negotiator * @return negotiator
*/ */
@JvmStatic @JvmStatic
fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator { fun negotiateByPolicy(
return object: HashAlgorithmNegotiator { hashAlgorithmPolicy: Policy.HashAlgorithmPolicy
override fun negotiateHashAlgorithm(orderedPrefs: Set<HashAlgorithm>): HashAlgorithm { ): HashAlgorithmNegotiator {
return orderedPrefs.firstOrNull { return object : HashAlgorithmNegotiator {
hashAlgorithmPolicy.isAcceptable(it) override fun negotiateHashAlgorithm(
} ?: hashAlgorithmPolicy.defaultHashAlgorithm() orderedPrefs: Set<HashAlgorithm>
): HashAlgorithm {
return orderedPrefs.firstOrNull { hashAlgorithmPolicy.isAcceptable(it) }
?: hashAlgorithmPolicy.defaultHashAlgorithm()
} }
} }
} }
} }

View File

@ -4,38 +4,41 @@
package org.pgpainless.algorithm.negotiation package org.pgpainless.algorithm.negotiation
import java.lang.IllegalArgumentException
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.policy.Policy import org.pgpainless.policy.Policy
import java.lang.IllegalArgumentException
interface SymmetricKeyAlgorithmNegotiator { interface SymmetricKeyAlgorithmNegotiator {
/** /**
* Negotiate a symmetric encryption algorithm. * Negotiate a symmetric encryption algorithm. If the override is non-null, it will be returned
* If the override is non-null, it will be returned instead of performing an actual negotiation. * instead of performing an actual negotiation. Otherwise, the list of ordered sets containing
* Otherwise, the list of ordered sets containing the preferences of different recipient keys will be * the preferences of different recipient keys will be used to determine a suitable symmetric
* used to determine a suitable symmetric encryption algorithm. * encryption algorithm.
* *
* @param policy algorithm policy * @param policy algorithm policy
* @param override algorithm override (if not null, return this) * @param override algorithm override (if not null, return this)
* @param keyPreferences list of preferences per key * @param keyPreferences list of preferences per key
* @return negotiated algorithm * @return negotiated algorithm
*/ */
fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy, fun negotiate(
policy: Policy.SymmetricKeyAlgorithmPolicy,
override: SymmetricKeyAlgorithm?, override: SymmetricKeyAlgorithm?,
keyPreferences: List<Set<SymmetricKeyAlgorithm>>): SymmetricKeyAlgorithm keyPreferences: List<Set<SymmetricKeyAlgorithm>>
): SymmetricKeyAlgorithm
companion object { companion object {
@JvmStatic @JvmStatic
fun byPopularity(): SymmetricKeyAlgorithmNegotiator { fun byPopularity(): SymmetricKeyAlgorithmNegotiator {
return object: SymmetricKeyAlgorithmNegotiator { return object : SymmetricKeyAlgorithmNegotiator {
override fun negotiate( override fun negotiate(
policy: Policy.SymmetricKeyAlgorithmPolicy, policy: Policy.SymmetricKeyAlgorithmPolicy,
override: SymmetricKeyAlgorithm?, override: SymmetricKeyAlgorithm?,
keyPreferences: List<Set<SymmetricKeyAlgorithm>>): keyPreferences: List<Set<SymmetricKeyAlgorithm>>
SymmetricKeyAlgorithm { ): SymmetricKeyAlgorithm {
if (override == SymmetricKeyAlgorithm.NULL) { if (override == SymmetricKeyAlgorithm.NULL) {
throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).") throw IllegalArgumentException(
"Algorithm override cannot be NULL (plaintext).")
} }
if (override != null) { if (override != null) {
@ -53,7 +56,9 @@ interface SymmetricKeyAlgorithmNegotiator {
// Pivot map and sort by popularity ascending // Pivot map and sort by popularity ascending
// score to list(algo) // score to list(algo)
val byScore = supportWeight.toList() val byScore =
supportWeight
.toList()
.map { e -> e.second to e.first } .map { e -> e.second to e.first }
.groupBy { e -> e.first } .groupBy { e -> e.first }
.map { e -> e.key to e.value.map { it.second }.toList() } .map { e -> e.key to e.value.map { it.second }.toList() }
@ -70,7 +75,6 @@ interface SymmetricKeyAlgorithmNegotiator {
return policy.defaultSymmetricKeyAlgorithm return policy.defaultSymmetricKeyAlgorithm
} }
} }
} }
} }

View File

@ -6,19 +6,19 @@ package org.pgpainless.authentication
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
class CertificateAuthenticity(val userId: String, class CertificateAuthenticity(
val userId: String,
val certificate: PGPPublicKeyRing, val certificate: PGPPublicKeyRing,
val certificationChains: Map<CertificationChain, Int>, val certificationChains: Map<CertificationChain, Int>,
val targetAmount: Int) { val targetAmount: Int
) {
val totalTrustAmount: Int val totalTrustAmount: Int
get() = certificationChains.values.sum() get() = certificationChains.values.sum()
/** /**
* Return the degree of authentication of the binding in percent. * Return the degree of authentication of the binding in percent. 100% means full
* 100% means full authentication. * authentication. Values smaller than 100% mean partial authentication.
* Values smaller than 100% mean partial authentication.
* *
* @return authenticity in percent * @return authenticity in percent
*/ */
@ -42,16 +42,7 @@ class CertificateAuthenticity(val userId: String,
* @param trustAmount actual trust amount of the chain * @param trustAmount actual trust amount of the chain
* @param chainLinks links of the chain, starting at the trust-root, ending at the target. * @param chainLinks links of the chain, starting at the trust-root, ending at the target.
*/ */
class CertificationChain( class CertificationChain(val trustAmount: Int, val chainLinks: List<ChainLink>) {}
val trustAmount: Int,
val chainLinks: List<ChainLink>) {
} /** A chain link contains a node in the trust chain. */
class ChainLink(val certificate: PGPPublicKeyRing) {}
/**
* A chain link contains a node in the trust chain.
*/
class ChainLink(
val certificate: PGPPublicKeyRing) {
}

View File

@ -2,14 +2,14 @@
// //
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package org.pgpainless.authentication; package org.pgpainless.authentication
import org.pgpainless.key.OpenPgpFingerprint
import java.util.* import java.util.*
import org.pgpainless.key.OpenPgpFingerprint
/** /**
* Interface for a CA that can authenticate trust-worthy certificates. * Interface for a CA that can authenticate trust-worthy certificates. Such a CA might be a fixed
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust. * list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
* *
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a> * @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a> * @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
@ -17,52 +17,58 @@ import java.util.*
interface CertificateAuthority { interface CertificateAuthority {
/** /**
* Determine the authenticity of the binding between the given fingerprint and the userId. * Determine the authenticity of the binding between the given fingerprint and the userId. In
* In other words, determine, how much evidence can be gathered, that the certificate with the given * other words, determine, how much evidence can be gathered, that the certificate with the
* fingerprint really belongs to the user with the given userId. * given fingerprint really belongs to the user with the given userId.
* *
* @param fingerprint fingerprint of the certificate * @param fingerprint fingerprint of the certificate
* @param userId userId * @param userId userId
* @param email if true, the userId will be treated as an email address and all user-IDs containing * @param email if true, the userId will be treated as an email address and all user-IDs
* the email address will be matched. * containing the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated * @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* 60 = partially authenticated...) * authenticated, 60 = partially authenticated...)
* @return information about the authenticity of the binding * @return information about the authenticity of the binding
*/ */
fun authenticateBinding(fingerprint: OpenPgpFingerprint, fun authenticateBinding(
fingerprint: OpenPgpFingerprint,
userId: String, userId: String,
email: Boolean, email: Boolean,
referenceTime: Date, referenceTime: Date,
targetAmount: Int): CertificateAuthenticity; targetAmount: Int
): CertificateAuthenticity
/** /**
* Lookup certificates, which carry a trustworthy binding to the given userId. * Lookup certificates, which carry a trustworthy binding to the given userId.
* *
* @param userId userId * @param userId userId
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing * @param email if true, the user-ID will be treated as an email address and all user-IDs
* the email address will be matched. * containing the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated * @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* 60 = partially authenticated...) * authenticated, 60 = partially authenticated...)
* @return list of identified bindings * @return list of identified bindings
*/ */
fun lookupByUserId(userId: String, fun lookupByUserId(
userId: String,
email: Boolean, email: Boolean,
referenceTime: Date, referenceTime: Date,
targetAmount: Int): List<CertificateAuthenticity> targetAmount: Int
): List<CertificateAuthenticity>
/** /**
* Identify trustworthy bindings for a certificate. * Identify trustworthy bindings for a certificate. The result is a list of authenticatable
* The result is a list of authenticatable userIds on the certificate. * userIds on the certificate.
* *
* @param fingerprint fingerprint of the certificate * @param fingerprint fingerprint of the certificate
* @param referenceTime reference time for trust calculations * @param referenceTime reference time for trust calculations
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated, * @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* 60 = partially authenticated...) * authenticated, 60 = partially authenticated...)
* @return list of identified bindings * @return list of identified bindings
*/ */
fun identifyByFingerprint(fingerprint: OpenPgpFingerprint, fun identifyByFingerprint(
fingerprint: OpenPgpFingerprint,
referenceTime: Date, referenceTime: Date,
targetAmount: Int): List<CertificateAuthenticity> targetAmount: Int
): List<CertificateAuthenticity>
} }

View File

@ -4,6 +4,9 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.io.IOException
import java.io.InputStream
import java.util.*
import org.bouncycastle.extensions.getPublicKeyFor import org.bouncycastle.extensions.getPublicKeyFor
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
@ -14,13 +17,8 @@ import org.pgpainless.key.protection.SecretKeyRingProtector
import org.pgpainless.signature.SignatureUtils import org.pgpainless.signature.SignatureUtils
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import org.pgpainless.util.SessionKey import org.pgpainless.util.SessionKey
import java.io.IOException
import java.io.InputStream
import java.util.*
/** /** Options for decryption and signature verification. */
* Options for decryption and signature verification.
*/
class ConsumerOptions { class ConsumerOptions {
private var ignoreMDCErrors = false private var ignoreMDCErrors = false
@ -34,15 +32,16 @@ class ConsumerOptions {
private var missingCertificateCallback: MissingPublicKeyCallback? = null private var missingCertificateCallback: MissingPublicKeyCallback? = null
private var sessionKey: SessionKey? = null private var sessionKey: SessionKey? = null
private val customDecryptorFactories = mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>() private val customDecryptorFactories =
mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>() private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
private val decryptionPassphrases = mutableSetOf<Passphrase>() private val decryptionPassphrases = mutableSetOf<Passphrase>()
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy() private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
/** /**
* Consider signatures on the message made before the given timestamp invalid. * Consider signatures on the message made before the given timestamp invalid. Null means no
* Null means no limitation. * limitation.
* *
* @param timestamp timestamp * @param timestamp timestamp
* @return options * @return options
@ -54,8 +53,8 @@ class ConsumerOptions {
fun getVerifyNotBefore() = verifyNotBefore fun getVerifyNotBefore() = verifyNotBefore
/** /**
* Consider signatures on the message made after the given timestamp invalid. * Consider signatures on the message made after the given timestamp invalid. Null means no
* Null means no limitation. * limitation.
* *
* @param timestamp timestamp * @param timestamp timestamp
* @return options * @return options
@ -82,7 +81,8 @@ class ConsumerOptions {
* @param verificationCerts certificates for signature verification * @param verificationCerts certificates for signature verification
* @return options * @return options
*/ */
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply { fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions =
apply {
for (cert in verificationCerts) { for (cert in verificationCerts) {
addVerificationCert(cert) addVerificationCert(cert)
} }
@ -93,12 +93,12 @@ class ConsumerOptions {
* *
* @param signatureInputStream input stream of detached signatures * @param signatureInputStream input stream of detached signatures
* @return options * @return options
*
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws PGPException in case of an OpenPGP error * @throws PGPException in case of an OpenPGP error
*/ */
@Throws(IOException::class, PGPException::class) @Throws(IOException::class, PGPException::class)
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply { fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions =
apply {
val signatures = SignatureUtils.readSignatures(signatureInputStream) val signatures = SignatureUtils.readSignatures(signatureInputStream)
addVerificationOfDetachedSignatures(signatures) addVerificationOfDetachedSignatures(signatures)
} }
@ -109,7 +109,9 @@ class ConsumerOptions {
* @param detachedSignatures detached signatures * @param detachedSignatures detached signatures
* @return options * @return options
*/ */
fun addVerificationOfDetachedSignatures(detachedSignatures: List<PGPSignature>): ConsumerOptions = apply { fun addVerificationOfDetachedSignatures(
detachedSignatures: List<PGPSignature>
): ConsumerOptions = apply {
for (signature in detachedSignatures) { for (signature in detachedSignatures) {
addVerificationOfDetachedSignature(signature) addVerificationOfDetachedSignature(signature)
} }
@ -121,14 +123,16 @@ class ConsumerOptions {
* @param detachedSignature detached signature * @param detachedSignature detached signature
* @return options * @return options
*/ */
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply { fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions =
apply {
detachedSignatures.add(detachedSignature) detachedSignatures.add(detachedSignature)
} }
fun getDetachedSignatures() = detachedSignatures.toList() fun getDetachedSignatures() = detachedSignatures.toList()
/** /**
* Set a callback that's used when a certificate (public key) is missing for signature verification. * Set a callback that's used when a certificate (public key) is missing for signature
* verification.
* *
* @param callback callback * @param callback callback
* @return options * @return options
@ -152,18 +156,18 @@ class ConsumerOptions {
fun getSessionKey() = sessionKey fun getSessionKey() = sessionKey
/** /**
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] * Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is
* is used to decrypt it when needed. * used to decrypt it when needed.
* *
* @param key key * @param key key
* @param keyRingProtector protector for the secret key * @param keyRingProtector protector for the secret key
* @return options * @return options
*/ */
@JvmOverloads @JvmOverloads
fun addDecryptionKey(key: PGPSecretKeyRing, fun addDecryptionKey(
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { key: PGPSecretKeyRing,
decryptionKeys[key] = protector protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
} ) = apply { decryptionKeys[key] = protector }
/** /**
* Add the keys in the provided key collection for message decryption. * Add the keys in the provided key collection for message decryption.
@ -173,18 +177,21 @@ class ConsumerOptions {
* @return options * @return options
*/ */
@JvmOverloads @JvmOverloads
fun addDecryptionKeys(keys: PGPSecretKeyRingCollection, fun addDecryptionKeys(
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply { keys: PGPSecretKeyRingCollection,
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
) = apply {
for (key in keys) { for (key in keys) {
addDecryptionKey(key, protector) addDecryptionKey(key, protector)
} }
} }
/** /**
* Add a passphrase for message decryption. * Add a passphrase for message decryption. This passphrase will be used to try to decrypt
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase. * messages which were symmetrically encrypted for a passphrase.
* *
* See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7) * See
* [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
* *
* @param passphrase passphrase * @param passphrase passphrase
* @return options * @return options
@ -195,8 +202,8 @@ class ConsumerOptions {
/** /**
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using * Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
* hardware-backed secret keys. * hardware-backed secret keys. (See e.g.
* (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]). * [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
* *
* @param factory decryptor factory * @param factory decryptor factory
* @return options * @return options
@ -206,9 +213,8 @@ class ConsumerOptions {
} }
/** /**
* Return the custom [PublicKeyDataDecryptorFactory] that were * Return the custom [PublicKeyDataDecryptorFactory] that were set by the user. These factories
* set by the user. * can be used to decrypt session keys using a custom logic.
* These factories can be used to decrypt session keys using a custom logic.
* *
* @return custom decryptor factories * @return custom decryptor factories
*/ */
@ -236,8 +242,8 @@ class ConsumerOptions {
fun getCertificateSource() = certificates fun getCertificateSource() = certificates
/** /**
* Return the callback that gets called when a certificate for signature verification is missing. * Return the callback that gets called when a certificate for signature verification is
* This method might return `null` if the users hasn't set a callback. * missing. This method might return `null` if the users hasn't set a callback.
* *
* @return missing public key callback * @return missing public key callback
*/ */
@ -255,45 +261,46 @@ class ConsumerOptions {
/** /**
* By default, PGPainless will require encrypted messages to make use of SEIP data packets. * By default, PGPainless will require encrypted messages to make use of SEIP data packets.
* Those are Symmetrically Encrypted Integrity Protected Data packets. * Those are Symmetrically Encrypted Integrity Protected Data packets. Symmetrically Encrypted
* Symmetrically Encrypted Data Packets without integrity protection are rejected by default. * Data Packets without integrity protection are rejected by default. Furthermore, PGPainless
* Furthermore, PGPainless will throw an exception if verification of the MDC error detection * will throw an exception if verification of the MDC error detection code of the SEIP packet
* code of the SEIP packet fails. * fails.
* *
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an * Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an
* attack or data corruption. * attack or data corruption.
* *
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data * This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data
* without integrity protection. * without integrity protection. If the flag <pre>ignoreMDCErrors</pre> is set to true,
* If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will * PGPainless will
*
* * not throw exceptions for SEIP packets with tampered ciphertext * * not throw exceptions for SEIP packets with tampered ciphertext
* * not throw exceptions for SEIP packets with tampered MDC * * not throw exceptions for SEIP packets with tampered MDC
* * not throw exceptions for MDCs with bad CTB * * not throw exceptions for MDCs with bad CTB
* * not throw exceptions for MDCs with bad length * * not throw exceptions for MDCs with bad length
* *
* It will however still throw an exception if it encounters a SEIP packet with missing or
* truncated MDC
* *
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC * See
* * [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
* See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
* *
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise. * @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
* @return options * @return options
*/ */
@Deprecated("Ignoring non-integrity-protected packets is discouraged.") @Deprecated("Ignoring non-integrity-protected packets is discouraged.")
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors } fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply {
this.ignoreMDCErrors = ignoreMDCErrors
}
fun isIgnoreMDCErrors() = ignoreMDCErrors fun isIgnoreMDCErrors() = ignoreMDCErrors
/** /**
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data. * workaround might come in handy if PGPainless accidentally mistakes the data for binary
* OpenPGP data.
* *
* @return options * @return options
*/ */
fun forceNonOpenPgpData(): ConsumerOptions = apply { fun forceNonOpenPgpData(): ConsumerOptions = apply { this.forceNonOpenPgpData = true }
this.forceNonOpenPgpData = true
}
/** /**
* Return true, if the ciphertext should be handled as binary non-OpenPGP data. * Return true, if the ciphertext should be handled as binary non-OpenPGP data.
@ -303,15 +310,15 @@ class ConsumerOptions {
fun isForceNonOpenPgpData() = forceNonOpenPgpData fun isForceNonOpenPgpData() = forceNonOpenPgpData
/** /**
* Specify the [MissingKeyPassphraseStrategy]. * Specify the [MissingKeyPassphraseStrategy]. This strategy defines, how missing passphrases
* This strategy defines, how missing passphrases for unlocking secret keys are handled. * for unlocking secret keys are handled. In interactive mode
* In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing * ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
* passphrases for secret keys via the [SecretKeyRingProtector] * passphrases for secret keys via the [SecretKeyRingProtector]
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback. * [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback.
* *
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead * In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will
* throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which * instead throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of
* there are missing passphrases. * all keys for which there are missing passphrases.
* *
* @param strategy strategy * @param strategy strategy
* @return options * @return options
@ -331,8 +338,8 @@ class ConsumerOptions {
} }
/** /**
* Set a custom multi-pass strategy for processing cleartext-signed messages. * Set a custom multi-pass strategy for processing cleartext-signed messages. Uses
* Uses [InMemoryMultiPassStrategy] by default. * [InMemoryMultiPassStrategy] by default.
* *
* @param multiPassStrategy multi-pass caching strategy * @param multiPassStrategy multi-pass caching strategy
* @return builder * @return builder
@ -343,8 +350,7 @@ class ConsumerOptions {
} }
/** /**
* Return the currently configured [MultiPassStrategy]. * Return the currently configured [MultiPassStrategy]. Defaults to [InMemoryMultiPassStrategy].
* Defaults to [InMemoryMultiPassStrategy].
* *
* @return multi-pass strategy * @return multi-pass strategy
*/ */
@ -353,8 +359,8 @@ class ConsumerOptions {
} }
/** /**
* Source for OpenPGP certificates. * Source for OpenPGP certificates. When verifying signatures on a message, this object holds
* When verifying signatures on a message, this object holds available signer certificates. * available signer certificates.
*/ */
class CertificateSource { class CertificateSource {
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf() private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
@ -370,6 +376,7 @@ class ConsumerOptions {
/** /**
* Return the set of explicitly set verification certificates. * Return the set of explicitly set verification certificates.
*
* @return explicitly set verification certs * @return explicitly set verification certs
*/ */
fun getExplicitCertificates(): Set<PGPPublicKeyRing> { fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
@ -377,9 +384,9 @@ class ConsumerOptions {
} }
/** /**
* Return a certificate which contains a subkey with the given keyId. * Return a certificate which contains a subkey with the given keyId. This method first
* This method first checks all explicitly set verification certs and if no cert is found it consults * checks all explicitly set verification certs and if no cert is found it consults the
* the certificate stores. * certificate stores.
* *
* @param keyId key id * @param keyId key id
* @return certificate * @return certificate
@ -389,13 +396,10 @@ class ConsumerOptions {
} }
fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? = fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? =
explicitCertificates.firstOrNull { explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null }
it.getPublicKeyFor(signature) != null
}
} }
companion object { companion object {
@JvmStatic @JvmStatic fun get() = ConsumerOptions()
fun get() = ConsumerOptions()
} }
} }

View File

@ -8,17 +8,17 @@ import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
/** /**
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message decryption * Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message
* using public keys. * decryption using public keys. This class can for example be used to implement message encryption
* This class can for example be used to implement message encryption using hardware tokens like smartcards or * using hardware tokens like smartcards or TPMs.
* TPMs. *
* @see [ConsumerOptions.addCustomDecryptorFactory] * @see [ConsumerOptions.addCustomDecryptorFactory]
*/ */
interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory { interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory {
/** /**
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] * Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is
* is intended. * intended.
* *
* @return subkey identifier * @return subkey identifier
*/ */

View File

@ -7,17 +7,17 @@ package org.pgpainless.decryption_verification
import java.io.InputStream import java.io.InputStream
/** /**
* Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) * Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) and combines
* and combines it with a configured [ConsumerOptions] object to form a [DecryptionStream] which * it with a configured [ConsumerOptions] object to form a [DecryptionStream] which can be used to
* can be used to decrypt an OpenPGP message or verify signatures. * decrypt an OpenPGP message or verify signatures.
*/ */
class DecryptionBuilder: DecryptionBuilderInterface { class DecryptionBuilder : DecryptionBuilderInterface {
override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
return DecryptWithImpl(inputStream) return DecryptWithImpl(inputStream)
} }
class DecryptWithImpl(val inputStream: InputStream): DecryptionBuilderInterface.DecryptWith { class DecryptWithImpl(val inputStream: InputStream) : DecryptionBuilderInterface.DecryptWith {
override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream { override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream {
return OpenPgpMessageInputStream.create(inputStream, consumerOptions) return OpenPgpMessageInputStream.create(inputStream, consumerOptions)

View File

@ -4,14 +4,15 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import org.bouncycastle.openpgp.PGPException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import org.bouncycastle.openpgp.PGPException
interface DecryptionBuilderInterface { interface DecryptionBuilderInterface {
/** /**
* Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed data. * Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed
* data.
* *
* @param inputStream encrypted and/or signed data. * @param inputStream encrypted and/or signed data.
* @return api handle * @return api handle

View File

@ -9,11 +9,11 @@ import java.io.InputStream
/** /**
* Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages. * Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages.
*/ */
abstract class DecryptionStream: InputStream() { abstract class DecryptionStream : InputStream() {
/** /**
* Return [MessageMetadata] about the decrypted / verified message. * Return [MessageMetadata] about the decrypted / verified message. The [DecryptionStream] MUST
* The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed. * be closed via [close] before the metadata object can be accessed.
* *
* @return message metadata * @return message metadata
*/ */

View File

@ -4,6 +4,7 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import kotlin.jvm.Throws
import org.bouncycastle.bcpg.AEADEncDataPacket import org.bouncycastle.bcpg.AEADEncDataPacket
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPException
@ -12,25 +13,22 @@ import org.bouncycastle.openpgp.operator.PGPDataDecryptor
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import kotlin.jvm.Throws
/** /** Enable integration of hardware-backed OpenPGP keys. */
* Enable integration of hardware-backed OpenPGP keys.
*/
class HardwareSecurity { class HardwareSecurity {
interface DecryptionCallback { interface DecryptionCallback {
/** /**
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with * Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for
* hardware security modules such as smartcards or TPMs. * dealing with hardware security modules such as smartcards or TPMs.
* *
* If decryption fails for some reason, a subclass of the [HardwareSecurityException] is thrown. * If decryption fails for some reason, a subclass of the [HardwareSecurityException] is
* thrown.
* *
* @param keyId id of the key * @param keyId id of the key
* @param keyAlgorithm algorithm * @param keyAlgorithm algorithm
* @param sessionKeyData encrypted session key * @param sessionKeyData encrypted session key
*
* @return decrypted session key * @return decrypted session key
* @throws HardwareSecurityException exception * @throws HardwareSecurityException exception
*/ */
@ -39,9 +37,9 @@ class HardwareSecurity {
} }
/** /**
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys * Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted
* to a [DecryptionCallback]. * session keys to a [DecryptionCallback]. Users can provide such a callback to delegate
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs. * decryption of messages to hardware security SDKs.
*/ */
class HardwareDataDecryptorFactory( class HardwareDataDecryptorFactory(
override val subkeyIdentifier: SubkeyIdentifier, override val subkeyIdentifier: SubkeyIdentifier,
@ -51,22 +49,35 @@ class HardwareSecurity {
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null) private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null)
override fun createDataDecryptor(withIntegrityPacket: Boolean, encAlgorithm: Int, key: ByteArray?): PGPDataDecryptor { override fun createDataDecryptor(
withIntegrityPacket: Boolean,
encAlgorithm: Int,
key: ByteArray?
): PGPDataDecryptor {
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key) return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key)
} }
override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { override fun createDataDecryptor(
aeadEncDataPacket: AEADEncDataPacket?,
sessionKey: PGPSessionKey?
): PGPDataDecryptor {
return factory.createDataDecryptor(aeadEncDataPacket, sessionKey) return factory.createDataDecryptor(aeadEncDataPacket, sessionKey)
} }
override fun createDataDecryptor(seipd: SymmetricEncIntegrityPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor { override fun createDataDecryptor(
seipd: SymmetricEncIntegrityPacket?,
sessionKey: PGPSessionKey?
): PGPDataDecryptor {
return factory.createDataDecryptor(seipd, sessionKey) return factory.createDataDecryptor(seipd, sessionKey)
} }
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray { override fun recoverSessionData(
keyAlgorithm: Int,
secKeyData: Array<out ByteArray>
): ByteArray {
return try { return try {
callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0]) callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
} catch (e : HardwareSecurityException) { } catch (e: HardwareSecurityException) {
throw PGPException("Hardware-backed decryption failed.", e) throw PGPException("Hardware-backed decryption failed.", e)
} }
} }

View File

@ -4,13 +4,13 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.io.IOException
import java.io.InputStream
import org.bouncycastle.openpgp.PGPEncryptedData import org.bouncycastle.openpgp.PGPEncryptedData
import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPException
import org.pgpainless.exception.ModificationDetectionException import org.pgpainless.exception.ModificationDetectionException
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException
import java.io.InputStream
class IntegrityProtectedInputStream( class IntegrityProtectedInputStream(
private val inputStream: InputStream, private val inputStream: InputStream,
@ -20,7 +20,9 @@ class IntegrityProtectedInputStream(
private var closed: Boolean = false private var closed: Boolean = false
override fun read() = inputStream.read() override fun read() = inputStream.read()
override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len) override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len)
override fun close() { override fun close() {
if (closed) return if (closed) return
@ -29,7 +31,7 @@ class IntegrityProtectedInputStream(
try { try {
if (!encryptedData.verify()) throw ModificationDetectionException() if (!encryptedData.verify()) throw ModificationDetectionException()
LOGGER.debug("Integrity Protection check passed.") LOGGER.debug("Integrity Protection check passed.")
} catch (e : PGPException) { } catch (e: PGPException) {
throw IOException("Data appears to not be integrity protected.", e) throw IOException("Data appears to not be integrity protected.", e)
} }
} }

View File

@ -4,14 +4,15 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.io.IOException
import java.io.InputStream
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmorUtils import org.pgpainless.util.ArmorUtils
import java.io.IOException
import java.io.InputStream
/** /**
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected. * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase
* protected.
*/ */
class MessageInspector { class MessageInspector {
@ -25,7 +26,8 @@ class MessageInspector {
data class EncryptionInfo( data class EncryptionInfo(
val keyIds: List<Long>, val keyIds: List<Long>,
val isPassphraseEncrypted: Boolean, val isPassphraseEncrypted: Boolean,
val isSignedOnly: Boolean) { val isSignedOnly: Boolean
) {
val isEncrypted: Boolean val isEncrypted: Boolean
get() = isPassphraseEncrypted || keyIds.isNotEmpty() get() = isPassphraseEncrypted || keyIds.isNotEmpty()
@ -34,25 +36,26 @@ class MessageInspector {
companion object { companion object {
/** /**
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. * Parses parts of the provided OpenPGP message in order to determine which keys were used
* to encrypt it.
* *
* @param message OpenPGP message * @param message OpenPGP message
* @return encryption info * @return encryption info
*
* @throws PGPException in case the message is broken * @throws PGPException in case the message is broken
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
*/ */
@JvmStatic @JvmStatic
@Throws(PGPException::class, IOException::class) @Throws(PGPException::class, IOException::class)
fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = determineEncryptionInfoForMessage(message.byteInputStream()) fun determineEncryptionInfoForMessage(message: String): EncryptionInfo =
determineEncryptionInfoForMessage(message.byteInputStream())
/** /**
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it. * Parses parts of the provided OpenPGP message in order to determine which keys were used
* Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves. * to encrypt it. Note: This method does not rewind the passed in Stream, so you might need
* to take care of that yourselves.
* *
* @param inputStream openpgp message * @param inputStream openpgp message
* @return encryption information * @return encryption information
*
* @throws IOException in case of an IO error * @throws IOException in case of an IO error
* @throws PGPException if the message is broken * @throws PGPException if the message is broken
*/ */
@ -70,13 +73,12 @@ class MessageInspector {
var n: Any? var n: Any?
while (objectFactory.nextObject().also { n = it } != null) { while (objectFactory.nextObject().also { n = it } != null) {
when (val next = n!!) { when (val next = n!!) {
is PGPOnePassSignatureList -> { is PGPOnePassSignatureList -> {
if (!next.isEmpty) { if (!next.isEmpty) {
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = true) return EncryptionInfo(
listOf(), isPassphraseEncrypted = false, isSignedOnly = true)
} }
} }
is PGPEncryptedDataList -> { is PGPEncryptedDataList -> {
var isPassphraseEncrypted = false var isPassphraseEncrypted = false
val keyIds = mutableListOf<Long>() val keyIds = mutableListOf<Long>()
@ -90,13 +92,12 @@ class MessageInspector {
// Data is encrypted, we cannot go deeper // Data is encrypted, we cannot go deeper
return EncryptionInfo(keyIds, isPassphraseEncrypted, false) return EncryptionInfo(keyIds, isPassphraseEncrypted, false)
} }
is PGPCompressedData -> { is PGPCompressedData -> {
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory( objectFactory =
PGPUtil.getDecoderStream(next.dataStream)) ImplementationFactory.getInstance()
.getPGPObjectFactory(PGPUtil.getDecoderStream(next.dataStream))
continue continue
} }
is PGPLiteralData -> { is PGPLiteralData -> {
break break
} }

View File

@ -4,6 +4,8 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.util.*
import javax.annotation.Nonnull
import org.bouncycastle.extensions.matches import org.bouncycastle.extensions.matches
import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPLiteralData
@ -15,116 +17,118 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.OpenPgpFingerprint
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.util.SessionKey import org.pgpainless.util.SessionKey
import java.util.*
import javax.annotation.Nonnull
/** /** View for extracting metadata about a [Message]. */
* View for extracting metadata about a [Message]. class MessageMetadata(val message: Message) {
*/
class MessageMetadata(
val message: Message
) {
// ################################################################################################################ // ################################################################################################################
// ### Encryption ### // ### Encryption
// ###
// ################################################################################################################ // ################################################################################################################
/** /**
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted. * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
* unencrypted.
*/ */
val encryptionAlgorithm: SymmetricKeyAlgorithm? val encryptionAlgorithm: SymmetricKeyAlgorithm?
get() = encryptionAlgorithms.let { get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
if (it.hasNext()) it.next() else null
}
/** /**
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. * [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
* The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item * returned by the iterator is the algorithm of the outermost encrypted data packet, the next
* that of the next nested encrypted data packet and so on. * item that of the next nested encrypted data packet and so on. The iterator might also be
* The iterator might also be empty, in case of an unencrypted message. * empty, in case of an unencrypted message.
*/ */
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm> val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator() get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
val isEncrypted: Boolean val isEncrypted: Boolean
get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL get() =
if (encryptionAlgorithm == null) false
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
fun isEncryptedFor(keys: PGPKeyRing): Boolean { fun isEncryptedFor(keys: PGPKeyRing): Boolean {
return encryptionLayers.asSequence().any { return encryptionLayers.asSequence().any {
it.recipients.any { keyId -> it.recipients.any { keyId -> keys.getPublicKey(keyId) != null }
keys.getPublicKey(keyId) != null
}
} }
} }
/** /**
* [SessionKey] of the outermost encrypted data packet. * [SessionKey] of the outermost encrypted data packet. If the message was unencrypted, this
* If the message was unencrypted, this method returns `null`. * method returns `null`.
*/ */
val sessionKey: SessionKey? val sessionKey: SessionKey?
get() = sessionKeys.asSequence().firstOrNull() get() = sessionKeys.asSequence().firstOrNull()
/** /**
* [Iterator] of each [SessionKey] for all encrypted data packets in the message. * [Iterator] of each [SessionKey] for all encrypted data packets in the message. The first item
* The first item returned by the iterator is the session key of the outermost encrypted data packet, * returned by the iterator is the session key of the outermost encrypted data packet, the next
* the next item that of the next nested encrypted data packet and so on. * item that of the next nested encrypted data packet and so on. The iterator might also be
* The iterator might also be empty, in case of an unencrypted message. * empty, in case of an unencrypted message.
*/ */
val sessionKeys: Iterator<SessionKey> val sessionKeys: Iterator<SessionKey>
get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator() get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator()
/** /**
* [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption * [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption
* layer. * layer. If the message was unencrypted or was decrypted using a passphrase, this field might
* If the message was unencrypted or was decrypted using a passphrase, this field might be `null`. * be `null`.
*/ */
val decryptionKey: SubkeyIdentifier? val decryptionKey: SubkeyIdentifier?
get() = encryptionLayers.asSequence() get() = encryptionLayers.asSequence().mapNotNull { it.decryptionKey }.firstOrNull()
.mapNotNull { it.decryptionKey }
.firstOrNull()
/** /** List containing all recipient keyIDs. */
* List containing all recipient keyIDs.
*/
val recipientKeyIds: List<Long> val recipientKeyIds: List<Long>
get() = encryptionLayers.asSequence() get() =
encryptionLayers
.asSequence()
.map { it.recipients.toMutableList() } .map { it.recipients.toMutableList() }
.reduce { all, keyIds -> all.addAll(keyIds); all } .reduce { all, keyIds ->
all.addAll(keyIds)
all
}
.toList() .toList()
val encryptionLayers: Iterator<EncryptedData> val encryptionLayers: Iterator<EncryptedData>
get() = object : LayerIterator<EncryptedData>(message) { get() =
object : LayerIterator<EncryptedData>(message) {
override fun matches(layer: Packet) = layer is EncryptedData override fun matches(layer: Packet) = layer is EncryptedData
override fun getProperty(last: Layer) = last as EncryptedData override fun getProperty(last: Layer) = last as EncryptedData
} }
// ################################################################################################################ // ################################################################################################################
// ### Compression ### // ### Compression
// ###
// ################################################################################################################ // ################################################################################################################
/** /**
* [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message * [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message does
* does not contain any compressed data packets. * not contain any compressed data packets.
*/ */
val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull() val compressionAlgorithm: CompressionAlgorithm? =
compressionAlgorithms.asSequence().firstOrNull()
/** /**
* [Iterator] of each [CompressionAlgorithm] encountered in the message. * [Iterator] of each [CompressionAlgorithm] encountered in the message. The first item returned
* The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next * by the iterator is the algorithm of the outermost compressed data packet, the next item that
* item that of the next nested compressed data packet and so on. * of the next nested compressed data packet and so on. The iterator might also be empty, in
* The iterator might also be empty, in case of a message without any compressed data packets. * case of a message without any compressed data packets.
*/ */
val compressionAlgorithms: Iterator<CompressionAlgorithm> val compressionAlgorithms: Iterator<CompressionAlgorithm>
get() = compressionLayers.asSequence().map { it.algorithm }.iterator() get() = compressionLayers.asSequence().map { it.algorithm }.iterator()
val compressionLayers: Iterator<CompressedData> val compressionLayers: Iterator<CompressedData>
get() = object : LayerIterator<CompressedData>(message) { get() =
object : LayerIterator<CompressedData>(message) {
override fun matches(layer: Packet) = layer is CompressedData override fun matches(layer: Packet) = layer is CompressedData
override fun getProperty(last: Layer) = last as CompressedData override fun getProperty(last: Layer) = last as CompressedData
} }
// ################################################################################################################ // ################################################################################################################
// ### Signatures ### // ### Signatures
// ###
// ################################################################################################################ // ################################################################################################################
val isUsingCleartextSignatureFramework: Boolean val isUsingCleartextSignatureFramework: Boolean
@ -133,35 +137,39 @@ class MessageMetadata(
val verifiedSignatures: List<SignatureVerification> val verifiedSignatures: List<SignatureVerification>
get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures) get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures)
/** /** List of all rejected signatures. */
* List of all rejected signatures.
*/
val rejectedSignatures: List<SignatureVerification.Failure> val rejectedSignatures: List<SignatureVerification.Failure>
get() = mutableListOf<SignatureVerification.Failure>() get() =
mutableListOf<SignatureVerification.Failure>()
.plus(rejectedInlineSignatures) .plus(rejectedInlineSignatures)
.plus(rejectedDetachedSignatures) .plus(rejectedDetachedSignatures)
.toList() .toList()
/** /**
* List of all verified inline-signatures. * List of all verified inline-signatures. This list contains all acceptable, correct signatures
* This list contains all acceptable, correct signatures that were part of the message itself. * that were part of the message itself.
*/ */
val verifiedInlineSignatures: List<SignatureVerification> = verifiedInlineSignaturesByLayer val verifiedInlineSignatures: List<SignatureVerification> =
verifiedInlineSignaturesByLayer
.asSequence() .asSequence()
.map { it.toMutableList() } .map { it.toMutableList() }
.reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc } .reduce { acc, signatureVerifications ->
acc.addAll(signatureVerifications)
acc
}
.toList() .toList()
/** /**
* [Iterator] of each [List] of verified inline-signatures of the message, separated by layer. * [Iterator] of each [List] of verified inline-signatures of the message, separated by layer.
* Since signatures might occur in different layers within a message, this method can be used to gain more detailed * Since signatures might occur in different layers within a message, this method can be used to
* insights into what signatures were encountered at what layers of the message structure. * gain more detailed insights into what signatures were encountered at what layers of the
* Each item of the [Iterator] represents a layer of the message and contains only signatures from * message structure. Each item of the [Iterator] represents a layer of the message and contains
* this layer. * only signatures from this layer. An empty list means no (or no acceptable) signatures were
* An empty list means no (or no acceptable) signatures were encountered in that layer. * encountered in that layer.
*/ */
val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>> val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>>
get() = object : LayerIterator<List<SignatureVerification>>(message) { get() =
object : LayerIterator<List<SignatureVerification>>(message) {
override fun matches(layer: Packet) = layer is Layer override fun matches(layer: Packet) = layer is Layer
override fun getProperty(last: Layer): List<SignatureVerification> { override fun getProperty(last: Layer): List<SignatureVerification> {
@ -169,24 +177,26 @@ class MessageMetadata(
.plus(last.verifiedOnePassSignatures) .plus(last.verifiedOnePassSignatures)
.plus(last.verifiedPrependedSignatures) .plus(last.verifiedPrependedSignatures)
} }
} }
/** /** List of all rejected inline-signatures of the message. */
* List of all rejected inline-signatures of the message. val rejectedInlineSignatures: List<SignatureVerification.Failure> =
*/ rejectedInlineSignaturesByLayer
val rejectedInlineSignatures: List<SignatureVerification.Failure> = rejectedInlineSignaturesByLayer
.asSequence() .asSequence()
.map { it.toMutableList() } .map { it.toMutableList() }
.reduce { acc, failures -> acc.addAll(failures); acc} .reduce { acc, failures ->
acc.addAll(failures)
acc
}
.toList() .toList()
/** /**
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures * Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected
* of the message, but organized by layer. * inline-signatures of the message, but organized by layer.
*/ */
val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>> val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>>
get() = object : LayerIterator<List<SignatureVerification.Failure>>(message) { get() =
object : LayerIterator<List<SignatureVerification.Failure>>(message) {
override fun matches(layer: Packet) = layer is Layer override fun matches(layer: Packet) = layer is Layer
override fun getProperty(last: Layer): List<SignatureVerification.Failure> = override fun getProperty(last: Layer): List<SignatureVerification.Failure> =
@ -196,18 +206,18 @@ class MessageMetadata(
} }
/** /**
* List of all verified detached signatures. * List of all verified detached signatures. This list contains all acceptable, correct detached
* This list contains all acceptable, correct detached signatures. * signatures.
*/ */
val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures
/** /** List of all rejected detached signatures. */
* List of all rejected detached signatures. val rejectedDetachedSignatures: List<SignatureVerification.Failure> =
*/ message.rejectedDetachedSignatures
val rejectedDetachedSignatures: List<SignatureVerification.Failure> = message.rejectedDetachedSignatures
/** /**
* True, if the message contains any (verified or rejected) signature, false if no signatures are present. * True, if the message contains any (verified or rejected) signature, false if no signatures
* are present.
*/ */
val hasSignature: Boolean val hasSignature: Boolean
get() = isVerifiedSigned() || hasRejectedSignatures() get() = isVerifiedSigned() || hasRejectedSignatures()
@ -217,26 +227,39 @@ class MessageMetadata(
fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty() fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty()
/** /**
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId. * Return true, if the message was signed by a certificate for which we can authenticate a
* binding to the given userId.
* *
* @param userId userId * @param userId userId
* @param email if true, treat the user-id as an email address and match all userIDs containing this address * @param email if true, treat the user-id as an email address and match all userIDs containing
* this address
* @param certificateAuthority certificate authority * @param certificateAuthority certificate authority
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated. * @param targetAmount targeted trust amount that needs to be reached by the binding to qualify
* defaults to 120. * as authenticated. defaults to 120.
* @return true, if we can authenticate a binding for a signing key with sufficient evidence * @return true, if we can authenticate a binding for a signing key with sufficient evidence
*/ */
@JvmOverloads @JvmOverloads
fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean { fun isAuthenticatablySignedBy(
return verifiedSignatures.any { certificateAuthority userId: String,
.authenticateBinding(it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount) email: Boolean,
certificateAuthority: CertificateAuthority,
targetAmount: Int = 120
): Boolean {
return verifiedSignatures.any {
certificateAuthority
.authenticateBinding(
it.signingKey.fingerprint,
userId,
email,
it.signature.creationTime,
targetAmount)
.authenticated .authenticated
} }
} }
/** /**
* Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint * Return rue, if the message was verifiable signed by a certificate that either has the given
* as primary key, or as the signing subkey. * fingerprint as primary key, or as the signing subkey.
* *
* @param fingerprint fingerprint * @param fingerprint fingerprint
* @return true if message was signed by a cert identified by the given fingerprint * @return true if message was signed by a cert identified by the given fingerprint
@ -260,59 +283,66 @@ class MessageMetadata(
verifiedInlineSignatures.any { keys.matches(it.signingKey) } verifiedInlineSignatures.any { keys.matches(it.signingKey) }
// ################################################################################################################ // ################################################################################################################
// ### Literal Data ### // ### Literal Data
// ###
// ################################################################################################################ // ################################################################################################################
/** /**
* Value of the literal data packet's filename field. * Value of the literal data packet's filename field. This value can be used to store a
* This value can be used to store a decrypted file under its original filename, * decrypted file under its original filename, but since this field is not necessarily part of
* but since this field is not necessarily part of the signed data of a message, usage of this field is * the signed data of a message, usage of this field is discouraged.
* discouraged.
* *
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
* Packet</a>
*/ */
val filename: String? = findLiteralData()?.fileName val filename: String? = findLiteralData()?.fileName
/** /**
* True, if the sender signals an increased degree of confidentiality by setting the filename of the literal * True, if the sender signals an increased degree of confidentiality by setting the filename of
* data packet to a special value that indicates that the data is intended for your eyes only. * the literal data packet to a special value that indicates that the data is intended for your
* eyes only.
*/ */
@Deprecated("Reliance on this signaling mechanism is discouraged.") @Deprecated("Reliance on this signaling mechanism is discouraged.")
val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename
/** /**
* Value of the literal data packets modification date field. * Value of the literal data packets modification date field. This value can be used to restore
* This value can be used to restore the modification date of a decrypted file, * the modification date of a decrypted file, but since this field is not necessarily part of
* but since this field is not necessarily part of the signed data, its use is discouraged. * the signed data, its use is discouraged.
* *
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
* Packet</a>
*/ */
val modificationDate: Date? = findLiteralData()?.modificationDate val modificationDate: Date? = findLiteralData()?.modificationDate
/** /**
* Value of the format field of the literal data packet. * Value of the format field of the literal data packet. This value indicates what format (text,
* This value indicates what format (text, binary data, ...) the data has. * binary data, ...) the data has. Since this field is not necessarily part of the signed data
* Since this field is not necessarily part of the signed data of a message, its usage is discouraged. * of a message, its usage is discouraged.
* *
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
* Packet</a>
*/ */
val literalDataEncoding: StreamEncoding? = findLiteralData()?.format val literalDataEncoding: StreamEncoding? = findLiteralData()?.format
/** /**
* Find the [LiteralData] layer of an OpenPGP message. * Find the [LiteralData] layer of an OpenPGP message. This method might return null, for
* This method might return null, for example for a cleartext signed message without OpenPGP packets. * example for a cleartext signed message without OpenPGP packets.
* *
* @return literal data * @return literal data
*/ */
private fun findLiteralData(): LiteralData? { private fun findLiteralData(): LiteralData? {
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message, // If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed
// message,
// we might not have a Literal Data packet. // we might not have a Literal Data packet.
var nested = message.child ?: return null var nested = message.child ?: return null
while (nested.hasNestedChild()) { while (nested.hasNestedChild()) {
val layer = nested as Layer val layer = nested as Layer
nested = checkNotNull(layer.child) { nested =
// Otherwise, we MUST find a Literal Data packet, or else the message is malformed checkNotNull(layer.child) {
// Otherwise, we MUST find a Literal Data packet, or else the message is
// malformed
"Malformed OpenPGP message. Cannot find Literal Data Packet" "Malformed OpenPGP message. Cannot find Literal Data Packet"
} }
} }
@ -320,7 +350,8 @@ class MessageMetadata(
} }
// ################################################################################################################ // ################################################################################################################
// ### Message Structure ### // ### Message Structure
// ###
// ################################################################################################################ // ################################################################################################################
interface Packet interface Packet
@ -329,13 +360,12 @@ class MessageMetadata(
fun hasNestedChild(): Boolean fun hasNestedChild(): Boolean
} }
abstract class Layer( abstract class Layer(val depth: Int) : Packet {
val depth: Int
) : Packet {
init { init {
if (depth > MAX_LAYER_DEPTH) { if (depth > MAX_LAYER_DEPTH) {
throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.") throw MalformedOpenPgpMessageException(
"Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
} }
} }
@ -347,9 +377,8 @@ class MessageMetadata(
val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf() val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf()
/** /**
* Nested child element of this layer. * Nested child element of this layer. Might be `null`, if this layer does not have a child
* Might be `null`, if this layer does not have a child element * element (e.g. if this is a [LiteralData] packet).
* (e.g. if this is a [LiteralData] packet).
*/ */
var child: Nested? = null var child: Nested? = null
@ -386,8 +415,8 @@ class MessageMetadata(
* Outermost OpenPGP Message structure. * Outermost OpenPGP Message structure.
* *
* @param cleartextSigned whether the message is using the Cleartext Signature Framework * @param cleartextSigned whether the message is using the Cleartext Signature Framework
* * @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a> * Signature Framework</a>
*/ */
class Message(var cleartextSigned: Boolean = false) : Layer(0) { class Message(var cleartextSigned: Boolean = false) : Layer(0) {
fun setCleartextSigned() = apply { cleartextSigned = true } fun setCleartextSigned() = apply { cleartextSigned = true }
@ -397,8 +426,8 @@ class MessageMetadata(
* Literal Data Packet. * Literal Data Packet.
* *
* @param fileName value of the filename field. An empty String represents no filename. * @param fileName value of the filename field. An empty String represents no filename.
* @param modificationDate value of the modification date field. The special value `Date(0)` indicates no * @param modificationDate value of the modification date field. The special value `Date(0)`
* modification date. * indicates no modification date.
* @param format value of the format field. * @param format value of the format field.
*/ */
class LiteralData( class LiteralData(
@ -417,9 +446,7 @@ class MessageMetadata(
* @param algorithm [CompressionAlgorithm] used to compress the packet. * @param algorithm [CompressionAlgorithm] used to compress the packet.
* @param depth nesting depth at which this packet was encountered. * @param depth nesting depth at which this packet was encountered.
*/ */
class CompressedData( class CompressedData(val algorithm: CompressionAlgorithm, depth: Int) : Layer(depth), Nested {
val algorithm: CompressionAlgorithm,
depth: Int) : Layer(depth), Nested {
// A compressed data packet MUST have a child element // A compressed data packet MUST have a child element
override fun hasNestedChild() = true override fun hasNestedChild() = true
@ -431,38 +458,30 @@ class MessageMetadata(
* @param algorithm symmetric key algorithm used to encrypt the packet. * @param algorithm symmetric key algorithm used to encrypt the packet.
* @param depth nesting depth at which this packet was encountered. * @param depth nesting depth at which this packet was encountered.
*/ */
class EncryptedData( class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested {
val algorithm: SymmetricKeyAlgorithm,
depth: Int
) : Layer(depth), Nested {
/** /** [SessionKey] used to decrypt the packet. */
* [SessionKey] used to decrypt the packet.
*/
var sessionKey: SessionKey? = null var sessionKey: SessionKey? = null
/** /** List of all recipient key ids to which the packet was encrypted for. */
* List of all recipient key ids to which the packet was encrypted for.
*/
val recipients: List<Long> = mutableListOf() val recipients: List<Long> = mutableListOf()
fun addRecipients(keyIds: List<Long>) = apply { fun addRecipients(keyIds: List<Long>) = apply { (recipients as MutableList).addAll(keyIds) }
(recipients as MutableList).addAll(keyIds)
}
/** /**
* Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet). * Identifier of the subkey that was used to decrypt the packet (in case of a public key
* encrypted packet).
*/ */
var decryptionKey: SubkeyIdentifier? = null var decryptionKey: SubkeyIdentifier? = null
// An encrypted data packet MUST have a child element // An encrypted data packet MUST have a child element
override fun hasNestedChild() = true override fun hasNestedChild() = true
} }
/** /**
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of * Iterator that iterates the packet structure from outermost to innermost packet, emitting the
* a transformation ([getProperty]) on those packets that match ([matches]) a given criterion. * results of a transformation ([getProperty]) on those packets that match ([matches]) a given
* criterion.
* *
* @param message outermost structure object * @param message outermost structure object
*/ */
@ -519,6 +538,7 @@ class MessageMetadata(
} }
abstract fun matches(layer: Packet): Boolean abstract fun matches(layer: Packet): Boolean
abstract fun getProperty(last: Layer): O abstract fun getProperty(last: Layer): O
} }
} }

View File

@ -4,19 +4,18 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
/** /** Strategy defining how missing secret key passphrases are handled. */
* Strategy defining how missing secret key passphrases are handled.
*/
enum class MissingKeyPassphraseStrategy { enum class MissingKeyPassphraseStrategy {
/** /**
* Try to interactively obtain key passphrases one-by-one via callbacks, * Try to interactively obtain key passphrases one-by-one via callbacks, eg
* eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider]. * [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
*/ */
INTERACTIVE, INTERACTIVE,
/** /**
* Do not try to obtain passphrases interactively and instead throw a * Do not try to obtain passphrases interactively and instead throw a
* [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing passphrases. * [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing
* passphrases.
*/ */
THROW_EXCEPTION THROW_EXCEPTION
} }

View File

@ -9,19 +9,18 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing
fun interface MissingPublicKeyCallback { fun interface MissingPublicKeyCallback {
/** /**
* This method gets called if we encounter a signature made by a key which was not provided for signature verification. * This method gets called if we encounter a signature made by a key which was not provided for
* If you cannot provide the requested key, it is safe to return null here. * signature verification. If you cannot provide the requested key, it is safe to return null
* PGPainless will then continue verification with the next signature. * here. PGPainless will then continue verification with the next signature.
* *
* Note: The key-id might belong to a subkey, so be aware that when looking up the [PGPPublicKeyRing], * Note: The key-id might belong to a subkey, so be aware that when looking up the
* you may not only search for the key-id on the key rings primary key! * [PGPPublicKeyRing], you may not only search for the key-id on the key rings primary key!
* *
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures * It would be super cool to provide the OpenPgp fingerprint here, but unfortunately
* only contain the key id. * one-pass-signatures only contain the key id.
* *
* @param keyId ID of the missing signing (sub)key * @param keyId ID of the missing signing (sub)key
* @return keyring containing the key or null * @return keyring containing the key or null
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a> * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
*/ */
fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing? fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing?

View File

@ -4,6 +4,9 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import openpgp.openPgpKeyId import openpgp.openPgpKeyId
import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.BCPGInputStream
import org.bouncycastle.bcpg.UnsupportedPacketVersionException import org.bouncycastle.bcpg.UnsupportedPacketVersionException
@ -34,16 +37,14 @@ import org.pgpainless.signature.consumer.SignatureValidator
import org.pgpainless.util.ArmoredInputStreamFactory import org.pgpainless.util.ArmoredInputStreamFactory
import org.pgpainless.util.SessionKey import org.pgpainless.util.SessionKey
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
class OpenPgpMessageInputStream( class OpenPgpMessageInputStream(
type: Type, type: Type,
inputStream: InputStream, inputStream: InputStream,
private val options: ConsumerOptions, private val options: ConsumerOptions,
private val layerMetadata: Layer, private val layerMetadata: Layer,
private val policy: Policy) : DecryptionStream() { private val policy: Policy
) : DecryptionStream() {
private val signatures: Signatures = Signatures(options) private val signatures: Signatures = Signatures(options)
private var packetInputStream: TeeBCPGInputStream? = null private var packetInputStream: TeeBCPGInputStream? = null
@ -58,19 +59,20 @@ class OpenPgpMessageInputStream(
signatures.addDetachedSignatures(options.getDetachedSignatures()) signatures.addDetachedSignatures(options.getDetachedSignatures())
} }
when(type) { when (type) {
Type.standard -> { Type.standard -> {
// tee out packet bytes for signature verification // tee out packet bytes for signature verification
packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures) packetInputStream =
TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures)
// *omnomnom* // *omnomnom*
consumePackets() consumePackets()
} }
Type.cleartext_signed -> { Type.cleartext_signed -> {
val multiPassStrategy = options.getMultiPassStrategy() val multiPassStrategy = options.getMultiPassStrategy()
val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage( val detachedSignatures =
ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
inputStream, multiPassStrategy.messageOutputStream) inputStream, multiPassStrategy.messageOutputStream)
for (signature in detachedSignatures) { for (signature in detachedSignatures) {
@ -78,9 +80,9 @@ class OpenPgpMessageInputStream(
} }
options.isForceNonOpenPgpData() options.isForceNonOpenPgpData()
nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures) nestedInputStream =
TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
} }
Type.non_openpgp -> { Type.non_openpgp -> {
packetInputStream = null packetInputStream = null
nestedInputStream = TeeInputStream(inputStream, this.signatures) nestedInputStream = TeeInputStream(inputStream, this.signatures)
@ -89,11 +91,17 @@ class OpenPgpMessageInputStream(
} }
enum class Type { enum class Type {
standard, cleartext_signed, non_openpgp standard,
cleartext_signed,
non_openpgp
} }
constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy): constructor(
this(Type.standard, inputStream, options, metadata, policy) inputStream: InputStream,
options: ConsumerOptions,
metadata: Layer,
policy: Policy
) : this(Type.standard, inputStream, options, metadata, policy)
private fun consumePackets() { private fun consumePackets() {
val pIn = packetInputStream ?: return val pIn = packetInputStream ?: return
@ -108,44 +116,47 @@ class OpenPgpMessageInputStream(
signatures.nextPacket(packet!!) signatures.nextPacket(packet!!)
// Consume packets in a layer // Consume packets in a layer
when(packet) { when (packet) {
OpenPgpPacket.LIT -> { OpenPgpPacket.LIT -> {
processLiteralData() processLiteralData()
break@layer // nest down break@layer // nest down
} }
OpenPgpPacket.COMP -> { OpenPgpPacket.COMP -> {
processCompressedData() processCompressedData()
break@layer // nest down break@layer // nest down
} }
OpenPgpPacket.OPS -> { OpenPgpPacket.OPS -> {
processOnePassSignature() // OPS is on the same layer, no nest down processOnePassSignature() // OPS is on the same layer, no nest down
} }
OpenPgpPacket.SIG -> { OpenPgpPacket.SIG -> {
processSignature() // SIG is on the same layer, no nest down processSignature() // SIG is on the same layer, no nest down
} }
OpenPgpPacket.PKESK,
OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> { OpenPgpPacket.SKESK,
OpenPgpPacket.SED,
OpenPgpPacket.SEIPD -> {
if (processEncryptedData()) { if (processEncryptedData()) {
break@layer break@layer
} }
throw MissingDecryptionMethodException("No working decryption method found.") throw MissingDecryptionMethodException("No working decryption method found.")
} }
OpenPgpPacket.MARKER -> { OpenPgpPacket.MARKER -> {
LOGGER.debug("Skipping Marker Packet") LOGGER.debug("Skipping Marker Packet")
pIn.readMarker() pIn.readMarker()
} }
OpenPgpPacket.SK,
OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR -> OpenPgpPacket.PK,
OpenPgpPacket.SSK,
OpenPgpPacket.PSK,
OpenPgpPacket.TRUST,
OpenPgpPacket.UID,
OpenPgpPacket.UATTR ->
throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet") throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet")
OpenPgpPacket.EXP_1,
OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 -> OpenPgpPacket.EXP_2,
OpenPgpPacket.EXP_3,
OpenPgpPacket.EXP_4 ->
throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet") throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet")
else -> else ->
throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet") throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet")
} }
@ -158,8 +169,10 @@ class OpenPgpMessageInputStream(
val literalData = packetInputStream!!.readLiteralData() val literalData = packetInputStream!!.readLiteralData()
// Extract Metadata // Extract Metadata
layerMetadata.child = LiteralData( layerMetadata.child =
literalData.fileName, literalData.modificationTime, LiteralData(
literalData.fileName,
literalData.modificationTime,
StreamEncoding.requireFromCode(literalData.format)) StreamEncoding.requireFromCode(literalData.format))
nestedInputStream = literalData.inputStream nestedInputStream = literalData.inputStream
@ -171,18 +184,22 @@ class OpenPgpMessageInputStream(
val compressedData = packetInputStream!!.readCompressedData() val compressedData = packetInputStream!!.readCompressedData()
// Extract Metadata // Extract Metadata
val compressionLayer = CompressedData( val compressionLayer =
CompressedData(
CompressionAlgorithm.requireFromId(compressedData.algorithm), CompressionAlgorithm.requireFromId(compressedData.algorithm),
layerMetadata.depth + 1) layerMetadata.depth + 1)
LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.") LOGGER.debug(
nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy) "Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.")
nestedInputStream =
OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy)
} }
private fun processOnePassSignature() { private fun processOnePassSignature() {
syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE) syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE)
val ops = packetInputStream!!.readOnePassSignature() val ops = packetInputStream!!.readOnePassSignature()
LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") LOGGER.debug(
"One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
signatures.addOnePassSignature(ops) signatures.addOnePassSignature(ops)
} }
@ -190,26 +207,33 @@ class OpenPgpMessageInputStream(
// true if signature corresponds to OPS // true if signature corresponds to OPS
val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS
syntaxVerifier.next(InputSymbol.SIGNATURE) syntaxVerifier.next(InputSymbol.SIGNATURE)
val signature = try { val signature =
try {
packetInputStream!!.readSignature() packetInputStream!!.readSignature()
} catch (e : UnsupportedPacketVersionException) { } catch (e: UnsupportedPacketVersionException) {
LOGGER.debug("Unsupported Signature at depth ${layerMetadata.depth} encountered.", e) LOGGER.debug(
"Unsupported Signature at depth ${layerMetadata.depth} encountered.", e)
return return
} }
val keyId = signature.issuerKeyId val keyId = signature.issuerKeyId
if (isSigForOps) { if (isSigForOps) {
LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") LOGGER.debug(
signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with "Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
signatures
.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are
// dealt with
signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy) signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy)
} else { } else {
LOGGER.debug("Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.") LOGGER.debug(
"Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
signatures.addPrependedSignature(signature) signatures.addPrependedSignature(signature)
} }
} }
private fun processEncryptedData(): Boolean { private fun processEncryptedData(): Boolean {
LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.") LOGGER.debug(
"Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
val encDataList = packetInputStream!!.readEncryptedDataList() val encDataList = packetInputStream!!.readEncryptedDataList()
if (!encDataList.isIntegrityProtected) { if (!encDataList.isIntegrityProtected) {
@ -220,17 +244,20 @@ class OpenPgpMessageInputStream(
} }
val esks = SortedESKs(encDataList) val esks = SortedESKs(encDataList)
LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + LOGGER.debug(
"Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" + " ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
" have an anonymous recipient.") " have an anonymous recipient.")
// try custom decryptor factories // try custom decryptor factories
for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) { for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) {
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.") LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
esks.pkesks.filter { esks.pkesks
.filter {
// find matching PKESK // find matching PKESK
it.keyID == key.subkeyId it.keyID == key.subkeyId
}.forEach { }
.forEach {
// attempt decryption // attempt decryption
if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) { if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) {
return true return true
@ -244,20 +271,24 @@ class OpenPgpMessageInputStream(
LOGGER.debug("Attempt decryption with provided session key.") LOGGER.debug("Attempt decryption with provided session key.")
throwIfUnacceptable(sk.algorithm) throwIfUnacceptable(sk.algorithm)
val decryptorFactory = ImplementationFactory.getInstance() val decryptorFactory =
.getSessionKeyDataDecryptorFactory(sk) ImplementationFactory.getInstance().getSessionKeyDataDecryptorFactory(sk)
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1) val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1)
val skEncData = encDataList.extractSessionKeyEncryptedData() val skEncData = encDataList.extractSessionKeyEncryptedData()
try { try {
val decrypted = skEncData.getDataStream(decryptorFactory) val decrypted = skEncData.getDataStream(decryptorFactory)
layer.sessionKey = sk layer.sessionKey = sk
val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options) val integrityProtected =
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy) IntegrityProtectedInputStream(decrypted, skEncData, options)
nestedInputStream =
OpenPgpMessageInputStream(integrityProtected, options, layer, policy)
LOGGER.debug("Successfully decrypted data using provided session key") LOGGER.debug("Successfully decrypted data using provided session key")
return true return true
} catch (e : PGPException) { } catch (e: PGPException) {
// Session key mismatch? // Session key mismatch?
LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e) LOGGER.debug(
"Decryption using provided session key failed. Mismatched session key and message?",
e)
} }
} }
@ -267,19 +298,21 @@ class OpenPgpMessageInputStream(
LOGGER.debug("Attempt decryption with provided passphrase") LOGGER.debug("Attempt decryption with provided passphrase")
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm) val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
if (!isAcceptable(algorithm)) { if (!isAcceptable(algorithm)) {
LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm") LOGGER.debug(
"Skipping SKESK with unacceptable encapsulation algorithm $algorithm")
continue continue
} }
val decryptorFactory = ImplementationFactory.getInstance() val decryptorFactory =
.getPBEDataDecryptorFactory(passphrase) ImplementationFactory.getInstance().getPBEDataDecryptorFactory(passphrase)
if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) { if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) {
return true return true
} }
} }
} }
val postponedDueToMissingPassphrase = mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>() val postponedDueToMissingPassphrase =
mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
// try (known) secret keys // try (known) secret keys
esks.pkesks.forEach { pkesk -> esks.pkesks.forEach { pkesk ->
@ -295,7 +328,8 @@ class OpenPgpMessageInputStream(
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
if (!protector.hasPassphraseFor(secretKey.keyID)) { if (!protector.hasPassphraseFor(secretKey.keyID)) {
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") LOGGER.debug(
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
postponedDueToMissingPassphrase.add(secretKey to pkesk) postponedDueToMissingPassphrase.add(secretKey to pkesk)
continue continue
} }
@ -319,7 +353,8 @@ class OpenPgpMessageInputStream(
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
if (!protector.hasPassphraseFor(secretKey.keyID)) { if (!protector.hasPassphraseFor(secretKey.keyID)) {
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") LOGGER.debug(
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
postponedDueToMissingPassphrase.add(secretKey to pkesk) postponedDueToMissingPassphrase.add(secretKey to pkesk)
continue continue
} }
@ -331,15 +366,14 @@ class OpenPgpMessageInputStream(
} }
} }
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) { if (options.getMissingKeyPassphraseStrategy() ==
MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
// Non-interactive mode: Throw an exception with all locked decryption keys // Non-interactive mode: Throw an exception with all locked decryption keys
postponedDueToMissingPassphrase.map { postponedDueToMissingPassphrase
SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) .map { SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) }
}.also { .also { if (it.isNotEmpty()) throw MissingPassphraseException(it.toSet()) }
if (it.isNotEmpty()) } else if (options.getMissingKeyPassphraseStrategy() ==
throw MissingPassphraseException(it.toSet()) MissingKeyPassphraseStrategy.INTERACTIVE) {
}
} else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
val keyId = secretKey.keyID val keyId = secretKey.keyID
val decryptionKeys = getDecryptionKey(pkesk)!! val decryptionKeys = getDecryptionKey(pkesk)!!
@ -348,7 +382,8 @@ class OpenPgpMessageInputStream(
continue continue
} }
LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.") LOGGER.debug(
"Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.")
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
val privateKey = secretKey.unlock(protector) val privateKey = secretKey.unlock(protector)
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
@ -364,29 +399,37 @@ class OpenPgpMessageInputStream(
return false return false
} }
private fun decryptWithPrivateKey(esks: SortedESKs, private fun decryptWithPrivateKey(
esks: SortedESKs,
privateKey: PGPPrivateKey, privateKey: PGPPrivateKey,
decryptionKeyId: SubkeyIdentifier, decryptionKeyId: SubkeyIdentifier,
pkesk: PGPPublicKeyEncryptedData): Boolean { pkesk: PGPPublicKeyEncryptedData
val decryptorFactory = ImplementationFactory.getInstance() ): Boolean {
.getPublicKeyDataDecryptorFactory(privateKey) val decryptorFactory =
ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey)
return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk) return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk)
} }
private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean { private fun hasUnsupportedS2KSpecifier(
secretKey: PGPSecretKey,
decryptionKeyId: SubkeyIdentifier
): Boolean {
val s2k = secretKey.s2K val s2k = secretKey.s2K
if (s2k != null) { if (s2k != null) {
if (s2k.type in 100..110) { if (s2k.type in 100..110) {
LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") LOGGER.debug(
"Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
return true return true
} }
} }
return false return false
} }
private fun decryptSKESKAndStream(esks: SortedESKs, private fun decryptSKESKAndStream(
esks: SortedESKs,
skesk: PGPPBEEncryptedData, skesk: PGPPBEEncryptedData,
decryptorFactory: PBEDataDecryptorFactory): Boolean { decryptorFactory: PBEDataDecryptorFactory
): Boolean {
try { try {
val decrypted = skesk.getDataStream(decryptorFactory) val decrypted = skesk.getDataStream(decryptorFactory)
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
@ -396,38 +439,45 @@ class OpenPgpMessageInputStream(
encryptedData.addRecipients(esks.pkesks.map { it.keyID }) encryptedData.addRecipients(esks.pkesks.map { it.keyID })
LOGGER.debug("Successfully decrypted data with passphrase") LOGGER.debug("Successfully decrypted data with passphrase")
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options) val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) nestedInputStream =
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
return true return true
} catch (e : UnacceptableAlgorithmException) { } catch (e: UnacceptableAlgorithmException) {
throw e throw e
} catch (e : PGPException) { } catch (e: PGPException) {
LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e) LOGGER.debug(
"Decryption of encrypted data packet using password failed. Password mismatch?", e)
} }
return false return false
} }
private fun decryptPKESKAndStream(esks: SortedESKs, private fun decryptPKESKAndStream(
esks: SortedESKs,
decryptionKeyId: SubkeyIdentifier, decryptionKeyId: SubkeyIdentifier,
decryptorFactory: PublicKeyDataDecryptorFactory, decryptorFactory: PublicKeyDataDecryptorFactory,
pkesk: PGPPublicKeyEncryptedData): Boolean { pkesk: PGPPublicKeyEncryptedData
): Boolean {
try { try {
val decrypted = pkesk.getDataStream(decryptorFactory) val decrypted = pkesk.getDataStream(decryptorFactory)
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory)) val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
throwIfUnacceptable(sessionKey.algorithm) throwIfUnacceptable(sessionKey.algorithm)
val encryptedData = EncryptedData( val encryptedData =
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)), EncryptedData(
SymmetricKeyAlgorithm.requireFromId(
pkesk.getSymmetricAlgorithm(decryptorFactory)),
layerMetadata.depth + 1) layerMetadata.depth + 1)
encryptedData.decryptionKey = decryptionKeyId encryptedData.decryptionKey = decryptionKeyId
encryptedData.sessionKey = sessionKey encryptedData.sessionKey = sessionKey
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID }) encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID })
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId") LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options) val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy) nestedInputStream =
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
return true return true
} catch (e : UnacceptableAlgorithmException) { } catch (e: UnacceptableAlgorithmException) {
throw e throw e
} catch (e : PGPException) { } catch (e: PGPException) {
LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e) LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e)
} }
return false return false
@ -441,7 +491,8 @@ class OpenPgpMessageInputStream(
return -1 return -1
} }
val r: Int = try { val r: Int =
try {
nestedInputStream!!.read() nestedInputStream!!.read()
} catch (e: IOException) { } catch (e: IOException) {
-1 -1
@ -532,16 +583,16 @@ class OpenPgpMessageInputStream(
return MessageMetadata((layerMetadata as Message)) return MessageMetadata((layerMetadata as Message))
} }
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? =
it.any { options.getDecryptionKeys().firstOrNull {
k -> k.keyID == keyId it.any { k -> k.keyID == keyId }
}.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { .and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k -> k.keyID == keyId })
k -> k.keyID == keyId
})
} }
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull { private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? =
it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> options.getDecryptionKeys().firstOrNull {
it.getSecretKeyFor(pkesk) != null &&
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
when (pkesk.version) { when (pkesk.version) {
3 -> pkesk.keyID == subkey.keyID 3 -> pkesk.keyID == subkey.keyID
else -> throw NotImplementedError("Version 6 PKESK not yet supported.") else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
@ -551,7 +602,8 @@ class OpenPgpMessageInputStream(
private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> = private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> =
options.getDecryptionKeys().filter { options.getDecryptionKeys().filter {
it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> it.getSecretKeyFor(pkesk) != null &&
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
when (pkesk.version) { when (pkesk.version) {
3 -> pkesk.keyID == subkey.keyID 3 -> pkesk.keyID == subkey.keyID
else -> throw NotImplementedError("Version 6 PKESK not yet supported.") else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
@ -559,7 +611,9 @@ class OpenPgpMessageInputStream(
} }
} }
private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<Pair<PGPSecretKeyRing, PGPSecretKey>> { private fun findPotentialDecryptionKeys(
pkesk: PGPPublicKeyEncryptedData
): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
val algorithm = pkesk.algorithm val algorithm = pkesk.algorithm
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>() val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
options.getDecryptionKeys().forEach { options.getDecryptionKeys().forEach {
@ -578,7 +632,8 @@ class OpenPgpMessageInputStream(
private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) { private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) {
if (!isAcceptable(algorithm)) { if (!isAcceptable(algorithm)) {
throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.") throw UnacceptableAlgorithmException(
"Symmetric-Key algorithm $algorithm is not acceptable for message decryption.")
} }
} }
@ -610,9 +665,7 @@ class OpenPgpMessageInputStream(
get() = skesks.plus(pkesks).plus(anonPkesks) get() = skesks.plus(pkesks).plus(anonPkesks)
} }
private class Signatures( private class Signatures(val options: ConsumerOptions) : OutputStream() {
val options: ConsumerOptions
) : OutputStream() {
val detachedSignatures = mutableListOf<SignatureCheck>() val detachedSignatures = mutableListOf<SignatureCheck>()
val prependedSignatures = mutableListOf<SignatureCheck>() val prependedSignatures = mutableListOf<SignatureCheck>()
val onePassSignatures = mutableListOf<OnePassSignatureCheck>() val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
@ -636,8 +689,10 @@ class OpenPgpMessageInputStream(
if (check != null) { if (check != null) {
detachedSignatures.add(check) detachedSignatures.add(check)
} else { } else {
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") LOGGER.debug(
detachedSignaturesWithMissingCert.add(SignatureVerification.Failure( "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
detachedSignaturesWithMissingCert.add(
SignatureVerification.Failure(
signature, null, SignatureValidationException("Missing verification key."))) signature, null, SignatureValidationException("Missing verification key.")))
} }
} }
@ -648,10 +703,11 @@ class OpenPgpMessageInputStream(
if (check != null) { if (check != null) {
prependedSignatures.add(check) prependedSignatures.add(check)
} else { } else {
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") LOGGER.debug(
prependedSignaturesWithMissingCert.add(SignatureVerification.Failure( "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
signature, null, SignatureValidationException("Missing verification key") prependedSignaturesWithMissingCert.add(
)) SignatureVerification.Failure(
signature, null, SignatureValidationException("Missing verification key")))
} }
} }
@ -680,7 +736,11 @@ class OpenPgpMessageInputStream(
} }
} }
fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) { fun addCorrespondingOnePassSignature(
signature: PGPSignature,
layer: Layer,
policy: Policy
) {
var found = false var found = false
val keyId = signature.issuerKeyId val keyId = signature.issuerKeyId
for ((i, check) in onePassSignatures.withIndex().reversed()) { for ((i, check) in onePassSignatures.withIndex().reversed()) {
@ -694,27 +754,32 @@ class OpenPgpMessageInputStream(
} }
check.signature = signature check.signature = signature
val verification = SignatureVerification(signature, val verification =
SignatureVerification(
signature,
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
try { try {
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) SignatureValidator.signatureWasCreatedInBounds(
options.getVerifyNotBefore(), options.getVerifyNotAfter())
.verify(signature) .verify(signature)
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy) CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
LOGGER.debug("Acceptable signature by key ${verification.signingKey}") LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
layer.addVerifiedOnePassSignature(verification) layer.addVerifiedOnePassSignature(verification)
} catch (e: SignatureValidationException) { } catch (e: SignatureValidationException) {
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e)) layer.addRejectedOnePassSignature(
SignatureVerification.Failure(verification, e))
} }
break break
} }
if (!found) { if (!found) {
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.") LOGGER.debug(
inbandSignaturesWithMissingCert.add(SignatureVerification.Failure( "No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
signature, null, SignatureValidationException("Missing verification key.") inbandSignaturesWithMissingCert.add(
)) SignatureVerification.Failure(
signature, null, SignatureValidationException("Missing verification key.")))
} }
} }
@ -737,7 +802,9 @@ class OpenPgpMessageInputStream(
} }
if (options.getMissingCertificateCallback() != null) { if (options.getMissingCertificateCallback() != null) {
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) return options
.getMissingCertificateCallback()!!
.onMissingPublicKeyEncountered(signature.keyID)
} }
return null // TODO: Missing cert for sig return null // TODO: Missing cert for sig
} }
@ -749,7 +816,9 @@ class OpenPgpMessageInputStream(
} }
if (options.getMissingCertificateCallback() != null) { if (options.getMissingCertificateCallback() != null) {
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID) return options
.getMissingCertificateCallback()!!
.onMissingPublicKeyEncountered(signature.keyID)
} }
return null // TODO: Missing cert for sig return null // TODO: Missing cert for sig
} }
@ -800,32 +869,42 @@ class OpenPgpMessageInputStream(
fun finish(layer: Layer, policy: Policy) { fun finish(layer: Layer, policy: Policy) {
for (detached in detachedSignatures) { for (detached in detachedSignatures) {
val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier) val verification =
SignatureVerification(detached.signature, detached.signingKeyIdentifier)
try { try {
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) SignatureValidator.signatureWasCreatedInBounds(
options.getVerifyNotBefore(), options.getVerifyNotAfter())
.verify(detached.signature) .verify(detached.signature)
CertificateValidator.validateCertificateAndVerifyInitializedSignature( CertificateValidator.validateCertificateAndVerifyInitializedSignature(
detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy) detached.signature,
KeyRingUtils.publicKeys(detached.signingKeyRing),
policy)
LOGGER.debug("Acceptable signature by key ${verification.signingKey}") LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
layer.addVerifiedDetachedSignature(verification) layer.addVerifiedDetachedSignature(verification)
} catch (e : SignatureValidationException) { } catch (e: SignatureValidationException) {
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e)) layer.addRejectedDetachedSignature(
SignatureVerification.Failure(verification, e))
} }
} }
for (prepended in prependedSignatures) { for (prepended in prependedSignatures) {
val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) val verification =
SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
try { try {
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()) SignatureValidator.signatureWasCreatedInBounds(
options.getVerifyNotBefore(), options.getVerifyNotAfter())
.verify(prepended.signature) .verify(prepended.signature)
CertificateValidator.validateCertificateAndVerifyInitializedSignature( CertificateValidator.validateCertificateAndVerifyInitializedSignature(
prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy) prepended.signature,
KeyRingUtils.publicKeys(prepended.signingKeyRing),
policy)
LOGGER.debug("Acceptable signature by key ${verification.signingKey}") LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
layer.addVerifiedPrependedSignature(verification) layer.addVerifiedPrependedSignature(verification)
} catch (e : SignatureValidationException) { } catch (e: SignatureValidationException) {
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e) LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e)) layer.addRejectedPrependedSignature(
SignatureVerification.Failure(verification, e))
} }
} }
@ -864,22 +943,22 @@ class OpenPgpMessageInputStream(
companion object { companion object {
@JvmStatic @JvmStatic
private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) { private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) {
val verifierProvider = ImplementationFactory.getInstance() val verifierProvider =
.pgpContentVerifierBuilderProvider ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
try { try {
signature.init(verifierProvider, publicKey) signature.init(verifierProvider, publicKey)
} catch (e : PGPException) { } catch (e: PGPException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
@JvmStatic @JvmStatic
private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) { private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) {
val verifierProvider = ImplementationFactory.getInstance() val verifierProvider =
.pgpContentVerifierBuilderProvider ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
try { try {
ops.init(verifierProvider, publicKey) ops.init(verifierProvider, publicKey)
} catch (e : PGPException) { } catch (e: PGPException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
@ -891,36 +970,40 @@ class OpenPgpMessageInputStream(
private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java) private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java)
@JvmStatic @JvmStatic
fun create(inputStream: InputStream, fun create(inputStream: InputStream, options: ConsumerOptions) =
options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy()) create(inputStream, options, PGPainless.getPolicy())
@JvmStatic @JvmStatic
fun create(inputStream: InputStream, fun create(inputStream: InputStream, options: ConsumerOptions, policy: Policy) =
options: ConsumerOptions, create(inputStream, options, Message(), policy)
policy: Policy) = create(inputStream, options, Message(), policy)
@JvmStatic @JvmStatic
internal fun create(inputStream: InputStream, internal fun create(
inputStream: InputStream,
options: ConsumerOptions, options: ConsumerOptions,
metadata: Layer, metadata: Layer,
policy: Policy): OpenPgpMessageInputStream { policy: Policy
): OpenPgpMessageInputStream {
val openPgpIn = OpenPgpInputStream(inputStream) val openPgpIn = OpenPgpInputStream(inputStream)
openPgpIn.reset() openPgpIn.reset()
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) { if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy) return OpenPgpMessageInputStream(
Type.non_openpgp, openPgpIn, options, metadata, policy)
} }
if (openPgpIn.isBinaryOpenPgp) { if (openPgpIn.isBinaryOpenPgp) {
// Simply consume OpenPGP message // Simply consume OpenPGP message
return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy) return OpenPgpMessageInputStream(
Type.standard, openPgpIn, options, metadata, policy)
} }
return if (openPgpIn.isAsciiArmored) { return if (openPgpIn.isAsciiArmored) {
val armorIn = ArmoredInputStreamFactory.get(openPgpIn) val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
if (armorIn.isClearText) { if (armorIn.isClearText) {
(metadata as Message).setCleartextSigned() (metadata as Message).setCleartextSigned()
OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy) OpenPgpMessageInputStream(
Type.cleartext_signed, armorIn, options, metadata, policy)
} else { } else {
// Simply consume dearmored OpenPGP message // Simply consume dearmored OpenPGP message
OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy) OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy)

View File

@ -11,19 +11,16 @@ import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.signature.SignatureUtils import org.pgpainless.signature.SignatureUtils
/** /**
* Tuple of a signature and an identifier of its corresponding verification key. * Tuple of a signature and an identifier of its corresponding verification key. Semantic meaning of
* Semantic meaning of the signature verification (success, failure) is merely given by context. * the signature verification (success, failure) is merely given by context. E.g.
* E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, * [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, while the class
* while the class [Failure] contains failed verifications. * [Failure] contains failed verifications.
* *
* @param signature PGPSignature object * @param signature PGPSignature object
* @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification. * @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification.
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key. * Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
*/ */
data class SignatureVerification( data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) {
val signature: PGPSignature,
val signingKey: SubkeyIdentifier
) {
override fun toString(): String { override fun toString(): String {
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" + return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
@ -31,10 +28,11 @@ data class SignatureVerification(
} }
/** /**
* Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException] * Tuple object of a [SignatureVerification] and the corresponding
* that caused the verification to fail. * [SignatureValidationException] that caused the verification to fail.
* *
* @param signatureVerification verification (tuple of [PGPSignature] and corresponding [SubkeyIdentifier]) * @param signatureVerification verification (tuple of [PGPSignature] and corresponding
* [SubkeyIdentifier])
* @param validationException exception that caused the verification to fail * @param validationException exception that caused the verification to fail
*/ */
data class Failure( data class Failure(
@ -43,8 +41,10 @@ data class SignatureVerification(
val validationException: SignatureValidationException val validationException: SignatureValidationException
) { ) {
constructor(verification: SignatureVerification, validationException: SignatureValidationException): constructor(
this(verification.signature, verification.signingKey, validationException) verification: SignatureVerification,
validationException: SignatureValidationException
) : this(verification.signature, verification.signingKey, validationException)
override fun toString(): String { override fun toString(): String {
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}" return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"

View File

@ -4,6 +4,9 @@
package org.pgpainless.decryption_verification package org.pgpainless.decryption_verification
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.BCPGInputStream
import org.bouncycastle.bcpg.MarkerPacket import org.bouncycastle.bcpg.MarkerPacket
import org.bouncycastle.bcpg.Packet import org.bouncycastle.bcpg.Packet
@ -13,25 +16,21 @@ import org.bouncycastle.openpgp.PGPLiteralData
import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPOnePassSignature
import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignature
import org.pgpainless.algorithm.OpenPgpPacket import org.pgpainless.algorithm.OpenPgpPacket
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
/** /**
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. * Since we need to update signatures with data from the underlying stream, this class is used to
* Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data out though, since * tee out the data. Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data
* [BCPGInputStream.readPacket] inconsistently calls a mix of [BCPGInputStream.read] and * out though, since [BCPGInputStream.readPacket] inconsistently calls a mix of
* [InputStream.read] of the underlying stream. This would cause the second length byte to get swallowed up. * [BCPGInputStream.read] and [InputStream.read] of the underlying stream. This would cause the
* second length byte to get swallowed up.
* *
* Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the underlying * Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the
* stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the next packets tag, * underlying stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the
* we need to delay teeing out that byte to signature verifiers. * next packets tag, we need to delay teeing out that byte to signature verifiers. Hence, the
* Hence, the reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using * reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using
* [DelayedTeeInputStream.squeeze]. * [DelayedTeeInputStream.squeeze].
*/ */
class TeeBCPGInputStream( class TeeBCPGInputStream(inputStream: BCPGInputStream, outputStream: OutputStream) {
inputStream: BCPGInputStream,
outputStream: OutputStream) {
private val delayedTee: DelayedTeeInputStream private val delayedTee: DelayedTeeInputStream
private val packetInputStream: BCPGInputStream private val packetInputStream: BCPGInputStream
@ -43,8 +42,7 @@ class TeeBCPGInputStream(
fun nextPacketTag(): OpenPgpPacket? { fun nextPacketTag(): OpenPgpPacket? {
return packetInputStream.nextPacketTag().let { return packetInputStream.nextPacketTag().let {
if (it == -1) null if (it == -1) null else OpenPgpPacket.requireFromTag(it)
else OpenPgpPacket.requireFromTag(it)
} }
} }
@ -94,7 +92,7 @@ class TeeBCPGInputStream(
return try { return try {
last = inputStream.read() last = inputStream.read()
last last
} catch (e : IOException) { } catch (e: IOException) {
if (e.message?.contains("crc check failed in armored message") == true) { if (e.message?.contains("crc check failed in armored message") == true) {
throw e throw e
} }
@ -108,7 +106,8 @@ class TeeBCPGInputStream(
} }
inputStream.read(b, off, len).let { r -> inputStream.read(b, off, len).let { r ->
last = if (r > 0) { last =
if (r > 0) {
outputStream.write(b, off, r - 1) outputStream.write(b, off, r - 1)
b[off + r - 1].toInt() b[off + r - 1].toInt()
} else { } else {
@ -118,9 +117,7 @@ class TeeBCPGInputStream(
} }
} }
/** /** Squeeze the last byte out and update the output stream. */
* Squeeze the last byte out and update the output stream.
*/
fun squeeze() { fun squeeze() {
if (last != -1) { if (last != -1) {
outputStream.write(last) outputStream.write(last)

View File

@ -4,31 +4,30 @@
package org.pgpainless.decryption_verification.cleartext_signatures package org.pgpainless.decryption_verification.cleartext_signatures
import java.io.*
import kotlin.jvm.Throws
import org.bouncycastle.bcpg.ArmoredInputStream import org.bouncycastle.bcpg.ArmoredInputStream
import org.bouncycastle.openpgp.PGPSignatureList import org.bouncycastle.openpgp.PGPSignatureList
import org.bouncycastle.util.Strings import org.bouncycastle.util.Strings
import org.pgpainless.exception.WrongConsumingMethodException import org.pgpainless.exception.WrongConsumingMethodException
import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmoredInputStreamFactory import org.pgpainless.util.ArmoredInputStreamFactory
import java.io.*
import kotlin.jvm.Throws
/** /**
* Utility class to deal with cleartext-signed messages. * Utility class to deal with cleartext-signed messages. Based on Bouncycastle's
* Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor]. * [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
*/ */
class ClearsignedMessageUtil { class ClearsignedMessageUtil {
companion object { companion object {
/** /**
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided * Dearmor a clearsigned message, detach the inband signatures and write the plaintext
* messageOutputStream. * message to the provided messageOutputStream.
* *
* @param clearsignedInputStream input stream containing a clearsigned message * @param clearsignedInputStream input stream containing a clearsigned message
* @param messageOutputStream output stream to which the dearmored message shall be written * @param messageOutputStream output stream to which the dearmored message shall be written
* @return signatures * @return signatures
*
* @throws IOException if the message is not clearsigned or some other IO error happens * @throws IOException if the message is not clearsigned or some other IO error happens
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed * @throws WrongConsumingMethodException in case the armored message is not cleartext signed
*/ */
@ -36,15 +35,18 @@ class ClearsignedMessageUtil {
@Throws(WrongConsumingMethodException::class, IOException::class) @Throws(WrongConsumingMethodException::class, IOException::class)
fun detachSignaturesFromInbandClearsignedMessage( fun detachSignaturesFromInbandClearsignedMessage(
clearsignedInputStream: InputStream, clearsignedInputStream: InputStream,
messageOutputStream: OutputStream): PGPSignatureList { messageOutputStream: OutputStream
val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) { ): PGPSignatureList {
val input: ArmoredInputStream =
if (clearsignedInputStream is ArmoredInputStream) {
clearsignedInputStream clearsignedInputStream
} else { } else {
ArmoredInputStreamFactory.get(clearsignedInputStream) ArmoredInputStreamFactory.get(clearsignedInputStream)
} }
if (!input.isClearText) { if (!input.isClearText) {
throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.") throw WrongConsumingMethodException(
"Message isn't using the Cleartext Signature Framework.")
} }
BufferedOutputStream(messageOutputStream).use { output -> BufferedOutputStream(messageOutputStream).use { output ->
@ -94,7 +96,11 @@ class ClearsignedMessageUtil {
} }
@JvmStatic @JvmStatic
private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int { private fun readInputLine(
bOut: ByteArrayOutputStream,
lookAhead: Int,
fIn: InputStream
): Int {
var mLookAhead = lookAhead var mLookAhead = lookAhead
bOut.reset() bOut.reset()
var ch = mLookAhead var ch = mLookAhead

View File

@ -8,12 +8,12 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
/** /**
* Implementation of the [MultiPassStrategy]. * Implementation of the [MultiPassStrategy]. This class keeps the read data in memory by caching
* This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream]. * the data inside a [ByteArrayOutputStream].
* *
* Note, that this class is suitable and efficient for processing small amounts of data. * Note, that this class is suitable and efficient for processing small amounts of data. For larger
* For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to * data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to prevent
* prevent [OutOfMemoryError] and other issues. * [OutOfMemoryError] and other issues.
*/ */
class InMemoryMultiPassStrategy : MultiPassStrategy { class InMemoryMultiPassStrategy : MultiPassStrategy {

View File

@ -7,16 +7,16 @@ package org.pgpainless.decryption_verification.cleartext_signatures
import java.io.* import java.io.*
/** /**
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, * Since for verification of cleartext signed messages, we need to read the whole data twice in
* a strategy for how to cache the read data is required. * order to verify signatures, a strategy for how to cache the read data is required. Otherwise,
* Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues. * large data kept in memory could cause an [OutOfMemoryError] or other issues.
* *
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes * This is an Interface that describes a strategy to deal with the fact that detached signatures
* to do verification. * require multiple passes to do verification.
* *
* This interface can be used to write the signed data stream out via [messageOutputStream] and later * This interface can be used to write the signed data stream out via [messageOutputStream] and
* get access to the data again via [messageInputStream]. * later get access to the data again via [messageInputStream]. Thereby the detail where the data is
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. * being stored (memory, file, etc.) can be abstracted away.
*/ */
interface MultiPassStrategy { interface MultiPassStrategy {
@ -32,8 +32,8 @@ interface MultiPassStrategy {
* Provide an [InputStream] which contains the data that was previously written away in * Provide an [InputStream] which contains the data that was previously written away in
* [messageOutputStream]. * [messageOutputStream].
* *
* As there may be multiple signatures that need to be processed, each call of this method MUST return * As there may be multiple signatures that need to be processed, each call of this method MUST
* a new [InputStream]. * return a new [InputStream].
* *
* @return input stream * @return input stream
* @throws IOException io error * @throws IOException io error
@ -43,9 +43,10 @@ interface MultiPassStrategy {
companion object { companion object {
/** /**
* Write the message content out to a file and re-read it to verify signatures. * Write the message content out to a file and re-read it to verify signatures. This
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. * strategy is best suited for larger messages (e.g. plaintext signed files) which might not
* After the message has been processed completely, the messages content are available at the provided file. * fit into memory. After the message has been processed completely, the messages content
* are available at the provided file.
* *
* @param file target file * @param file target file
* @return strategy * @return strategy
@ -56,10 +57,10 @@ interface MultiPassStrategy {
} }
/** /**
* Read the message content into memory. * Read the message content into memory. This strategy is best suited for small messages
* This strategy is best suited for small messages which fit into memory. * which fit into memory. After the message has been processed completely, the message
* After the message has been processed completely, the message content can be accessed by calling * content can be accessed by calling [ByteArrayOutputStream.toByteArray] on
* [ByteArrayOutputStream.toByteArray] on [messageOutputStream]. * [messageOutputStream].
* *
* @return strategy * @return strategy
*/ */

View File

@ -7,18 +7,15 @@ package org.pgpainless.decryption_verification.cleartext_signatures
import java.io.* import java.io.*
/** /**
* Implementation of the [MultiPassStrategy]. * Implementation of the [MultiPassStrategy]. When processing signed data the first time, the data
* When processing signed data the first time, the data is being written out into a file. * is being written out into a file. For the second pass, that file is being read again.
* For the second pass, that file is being read again.
* *
* This strategy is recommended when larger amounts of data need to be processed. * This strategy is recommended when larger amounts of data need to be processed. For smaller files,
* For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency. * [InMemoryMultiPassStrategy] yields higher efficiency.
* *
* @param file file to write the data to and read from * @param file file to write the data to and read from
*/ */
class WriteToFileMultiPassStrategy( class WriteToFileMultiPassStrategy(private val file: File) : MultiPassStrategy {
private val file: File
) : MultiPassStrategy {
override val messageOutputStream: OutputStream override val messageOutputStream: OutputStream
@Throws(IOException::class) @Throws(IOException::class)
@ -39,5 +36,4 @@ class WriteToFileMultiPassStrategy(
} }
return FileInputStream(file) return FileInputStream(file)
} }
} }

View File

@ -5,35 +5,26 @@
package org.pgpainless.decryption_verification.syntax_check package org.pgpainless.decryption_verification.syntax_check
enum class InputSymbol { enum class InputSymbol {
/** /** A [PGPLiteralData] packet. */
* A [PGPLiteralData] packet.
*/
LITERAL_DATA, LITERAL_DATA,
/** /** A [PGPSignatureList] object. */
* A [PGPSignatureList] object.
*/
SIGNATURE, SIGNATURE,
/** /** A [PGPOnePassSignatureList] object. */
* A [PGPOnePassSignatureList] object.
*/
ONE_PASS_SIGNATURE, ONE_PASS_SIGNATURE,
/** /**
* A [PGPCompressedData] packet. * A [PGPCompressedData] packet. The contents of this packet MUST form a valid OpenPGP message,
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify * so a nested PDA is opened to verify its nested packet sequence.
* its nested packet sequence.
*/ */
COMPRESSED_DATA, COMPRESSED_DATA,
/** /**
* A [PGPEncryptedDataList] object. * A [PGPEncryptedDataList] object. This object combines multiple ESKs and the corresponding
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted * Symmetrically Encrypted (possibly Integrity Protected) Data packet.
* (possibly Integrity Protected) Data packet.
*/ */
ENCRYPTED_DATA, ENCRYPTED_DATA,
/** /**
* Marks the end of a (sub-) sequence. * Marks the end of a (sub-) sequence. This input is given if the end of an OpenPGP message is
* This input is given if the end of an OpenPGP message is reached. * reached. This might be the case for the end of the whole ciphertext, or the end of a packet
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents * with nested contents (e.g. the end of a Compressed Data packet).
* (e.g. the end of a Compressed Data packet).
*/ */
END_OF_SEQUENCE END_OF_SEQUENCE
} }

View File

@ -9,9 +9,10 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
/** /**
* This class describes the syntax for OpenPGP messages as specified by rfc4880. * This class describes the syntax for OpenPGP messages as specified by rfc4880.
* *
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) * See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) See
* See [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/) * [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/)
* See [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/) * See
* [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/)
*/ */
class OpenPgpMessageSyntax : Syntax { class OpenPgpMessageSyntax : Syntax {
@ -33,10 +34,12 @@ class OpenPgpMessageSyntax : Syntax {
return when (input) { return when (input) {
InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE) InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE)
InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG) InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG)
InputSymbol.ONE_PASS_SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG) InputSymbol.ONE_PASS_SIGNATURE ->
Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG)
InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE) InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE)
InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE) InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE)
InputSymbol.END_OF_SEQUENCE -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) InputSymbol.END_OF_SEQUENCE ->
throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem) else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
} }
} }

View File

@ -8,12 +8,13 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
/** /**
* Pushdown Automaton for validating context-free languages. * Pushdown Automaton for validating context-free languages. In PGPainless, this class is used to
* In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax. * validate OpenPGP message packet sequences against the allowed syntax.
* *
* See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) * See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
*/ */
class PDA constructor( class PDA
constructor(
private val syntax: Syntax, private val syntax: Syntax,
private val stack: ArrayDeque<StackSymbol>, private val stack: ArrayDeque<StackSymbol>,
private val inputs: MutableList<InputSymbol>, private val inputs: MutableList<InputSymbol>,
@ -21,24 +22,27 @@ class PDA constructor(
) { ) {
/** /**
* Construct a PDA with a custom [Syntax], initial [State] and initial [StackSymbols][StackSymbol]. * Construct a PDA with a custom [Syntax], initial [State] and initial
* [StackSymbols][StackSymbol].
* *
* @param syntax syntax * @param syntax syntax
* @param initialState initial state * @param initialState initial state
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance) * @param initialStack zero or more initial stack items (get pushed onto the stack in order of
* appearance)
*/ */
constructor(syntax: Syntax, initialState: State, vararg initialStack: StackSymbol): this ( constructor(
syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState) syntax: Syntax,
initialState: State,
vararg initialStack: StackSymbol
) : this(syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
/** Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. */
constructor() :
this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
/** /**
* Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. * Process the next [InputSymbol]. This will either leave the PDA in the next state, or throw a
*/ * [MalformedOpenPgpMessageException] if the input symbol is rejected.
constructor(): this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
/**
* Process the next [InputSymbol].
* This will either leave the PDA in the next state, or throw a [MalformedOpenPgpMessageException] if the
* input symbol is rejected.
* *
* @param input input symbol * @param input input symbol
* @throws MalformedOpenPgpMessageException if the input symbol is rejected * @throws MalformedOpenPgpMessageException if the input symbol is rejected
@ -53,14 +57,17 @@ class PDA constructor(
} }
inputs.add(input) inputs.add(input)
} catch (e: MalformedOpenPgpMessageException) { } catch (e: MalformedOpenPgpMessageException) {
val stackFormat = if (stackSymbol != null) { val stackFormat =
if (stackSymbol != null) {
"${stack.joinToString()}||$stackSymbol" "${stack.joinToString()}||$stackSymbol"
} else { } else {
stack.joinToString() stack.joinToString()
} }
val wrapped = MalformedOpenPgpMessageException( val wrapped =
MalformedOpenPgpMessageException(
"Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" + "Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" +
"No transition from state '$state' with stack $stackFormat", e) "No transition from state '$state' with stack $stackFormat",
e)
LOGGER.debug("Invalid input '$input'", wrapped) LOGGER.debug("Invalid input '$input'", wrapped)
throw wrapped throw wrapped
} }
@ -87,7 +94,8 @@ class PDA constructor(
*/ */
fun assertValid() { fun assertValid() {
if (!isValid()) { if (!isValid()) {
throw MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: ${toString()}") throw MalformedOpenPgpMessageException(
"Pushdown Automaton is not in an acceptable state: ${toString()}")
} }
} }
@ -114,7 +122,6 @@ class PDA constructor(
} }
companion object { companion object {
@JvmStatic @JvmStatic private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
} }
} }

View File

@ -5,16 +5,10 @@
package org.pgpainless.decryption_verification.syntax_check package org.pgpainless.decryption_verification.syntax_check
enum class StackSymbol { enum class StackSymbol {
/** /** OpenPGP Message. */
* OpenPGP Message.
*/
MSG, MSG,
/** /** OnePassSignature (in case of BC this represents a OnePassSignatureList). */
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
*/
OPS, OPS,
/** /** Special symbol representing the end of the message. */
* Special symbol representing the end of the message.
*/
TERMINUS TERMINUS
} }

View File

@ -4,9 +4,7 @@
package org.pgpainless.decryption_verification.syntax_check package org.pgpainless.decryption_verification.syntax_check
/** /** Set of states of the automaton. */
* Set of states of the automaton.
*/
enum class State { enum class State {
OPENPGP_MESSAGE, OPENPGP_MESSAGE,
LITERAL_MESSAGE, LITERAL_MESSAGE,

View File

@ -6,24 +6,23 @@ package org.pgpainless.decryption_verification.syntax_check
import org.pgpainless.exception.MalformedOpenPgpMessageException import org.pgpainless.exception.MalformedOpenPgpMessageException
/** /** This interface can be used to define a custom syntax for the [PDA]. */
* This interface can be used to define a custom syntax for the [PDA].
*/
interface Syntax { interface Syntax {
/** /**
* Describe a transition rule from [State] <pre>from</pre> for [InputSymbol] <pre>input</pre> * Describe a transition rule from [State] <pre>from</pre> for [InputSymbol] <pre>input</pre>
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack. * with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack. The resulting
* The resulting [Transition] contains the new [State], as well as a list of * [Transition] contains the new [State], as well as a list of [StackSymbols][StackSymbol] that
* [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule. * get pushed onto the stack by the transition rule. If there is no applicable rule, a
* If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case * [MalformedOpenPgpMessageException] is thrown, since in this case the [InputSymbol] must be
* the [InputSymbol] must be considered illegal. * considered illegal.
* *
* @param from current state of the PDA * @param from current state of the PDA
* @param input input symbol * @param input input symbol
* @param stackItem item that got popped from the top of the stack * @param stackItem item that got popped from the top of the stack
* @return applicable transition rule containing the new state and pushed stack symbols * @return applicable transition rule containing the new state and pushed stack symbols
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal) * @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input
* symbol is illegal)
*/ */
@Throws(MalformedOpenPgpMessageException::class) @Throws(MalformedOpenPgpMessageException::class)
fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition

View File

@ -5,18 +5,18 @@
package org.pgpainless.decryption_verification.syntax_check package org.pgpainless.decryption_verification.syntax_check
/** /**
* Result of applying a transition rule. * Result of applying a transition rule. Transition rules can be described by implementing the
* Transition rules can be described by implementing the [Syntax] interface. * [Syntax] interface.
* *
* @param newState new [State] that is reached by applying the transition. * @param newState new [State] that is reached by applying the transition.
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the transition. * @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the
* The list contains items in the order in which they are pushed onto the stack. * transition. The list contains items in the order in which they are pushed onto the stack. The
* The list may be empty. * list may be empty.
*/ */
class Transition private constructor( class Transition private constructor(val pushedItems: List<StackSymbol>, val newState: State) {
val pushedItems: List<StackSymbol>,
val newState: State
) {
constructor(newState: State, vararg pushedItems: StackSymbol): this(pushedItems.toList(), newState) constructor(
newState: State,
vararg pushedItems: StackSymbol
) : this(pushedItems.toList(), newState)
} }

View File

@ -4,6 +4,7 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.security.MessageDigest
import org.bouncycastle.extensions.unlock import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPrivateKey import org.bouncycastle.openpgp.PGPPrivateKey
@ -13,18 +14,21 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.SignatureType import org.pgpainless.algorithm.SignatureType
import org.pgpainless.key.protection.SecretKeyRingProtector import org.pgpainless.key.protection.SecretKeyRingProtector
import java.security.MessageDigest
class BcHashContextSigner { class BcHashContextSigner {
companion object { companion object {
@JvmStatic @JvmStatic
fun signHashContext(hashContext: MessageDigest, fun signHashContext(
hashContext: MessageDigest,
signatureType: SignatureType, signatureType: SignatureType,
secretKey: PGPSecretKeyRing, secretKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector): PGPSignature { protector: SecretKeyRingProtector
): PGPSignature {
val info = PGPainless.inspectKeyRing(secretKey) val info = PGPainless.inspectKeyRing(secretKey)
return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull() return info.signingSubkeys
.mapNotNull { info.getSecretKey(it.keyID) }
.firstOrNull()
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) } ?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
?: throw PGPException("Key does not contain suitable signing subkey.") ?: throw PGPException("Key does not contain suitable signing subkey.")
} }
@ -38,9 +42,11 @@ class BcHashContextSigner {
* @throws PGPException in case of an OpenPGP error * @throws PGPException in case of an OpenPGP error
*/ */
@JvmStatic @JvmStatic
internal fun signHashContext(hashContext: MessageDigest, internal fun signHashContext(
hashContext: MessageDigest,
signatureType: SignatureType, signatureType: SignatureType,
privateKey: PGPPrivateKey): PGPSignature { privateKey: PGPPrivateKey
): PGPSignature {
return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext)) return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext))
.apply { init(signatureType.code, privateKey) } .apply { init(signatureType.code, privateKey) }
.generate() .generate()

View File

@ -4,6 +4,8 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.io.OutputStream
import java.security.MessageDigest
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
import org.bouncycastle.crypto.CipherParameters import org.bouncycastle.crypto.CipherParameters
import org.bouncycastle.crypto.CryptoException import org.bouncycastle.crypto.CryptoException
@ -23,17 +25,14 @@ import org.bouncycastle.openpgp.operator.PGPContentSigner
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter
import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import java.io.OutputStream
import java.security.MessageDigest
/** /**
* Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash contexts. * Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash
* This can come in handy to sign data, which was already processed to calculate the hash context, without the * contexts. This can come in handy to sign data, which was already processed to calculate the hash
* need to process it again to calculate the OpenPGP signature. * context, without the need to process it again to calculate the OpenPGP signature.
*/ */
class BcPGPHashContextContentSignerBuilder( class BcPGPHashContextContentSignerBuilder(private val messageDigest: MessageDigest) :
private val messageDigest: MessageDigest PGPHashContextContentSignerBuilder() {
) : PGPHashContextContentSignerBuilder() {
private val keyConverter = BcPGPKeyConverter() private val keyConverter = BcPGPKeyConverter()
private val _hashAlgorithm: HashAlgorithm private val _hashAlgorithm: HashAlgorithm
@ -50,15 +49,22 @@ class BcPGPHashContextContentSignerBuilder(
return object : PGPContentSigner { return object : PGPContentSigner {
override fun getOutputStream(): OutputStream = SignerOutputStream(signer) override fun getOutputStream(): OutputStream = SignerOutputStream(signer)
override fun getSignature(): ByteArray = try {
override fun getSignature(): ByteArray =
try {
signer.generateSignature() signer.generateSignature()
} catch (e : CryptoException) { } catch (e: CryptoException) {
throw IllegalStateException("unable to create signature.", e) throw IllegalStateException("unable to create signature.", e)
} }
override fun getDigest(): ByteArray = messageDigest.digest() override fun getDigest(): ByteArray = messageDigest.digest()
override fun getType(): Int = signatureType override fun getType(): Int = signatureType
override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId
override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId
override fun getKeyID(): Long = privateKey.keyID override fun getKeyID(): Long = privateKey.keyID
} }
} }
@ -67,21 +73,25 @@ class BcPGPHashContextContentSignerBuilder(
@JvmStatic @JvmStatic
private fun requireFromName(digestName: String): HashAlgorithm { private fun requireFromName(digestName: String): HashAlgorithm {
val algorithm = HashAlgorithm.fromName(digestName) val algorithm = HashAlgorithm.fromName(digestName)
require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName"} require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName" }
return algorithm return algorithm
} }
@JvmStatic @JvmStatic
private fun createSigner(keyAlgorithm: PublicKeyAlgorithm, private fun createSigner(
keyAlgorithm: PublicKeyAlgorithm,
messageDigest: MessageDigest, messageDigest: MessageDigest,
keyParam: CipherParameters): Signer { keyParam: CipherParameters
): Signer {
val staticDigest = ExistingMessageDigest(messageDigest) val staticDigest = ExistingMessageDigest(messageDigest)
return when (keyAlgorithm.algorithmId) { return when (keyAlgorithm.algorithmId) {
PublicKeyAlgorithmTags.RSA_GENERAL, PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest) PublicKeyAlgorithmTags.RSA_GENERAL,
PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest)
PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest) PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest)
PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest) PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest)
PublicKeyAlgorithmTags.EDDSA_LEGACY -> { PublicKeyAlgorithmTags.EDDSA_LEGACY -> {
if (keyParam is Ed25519PrivateKeyParameters || keyParam is Ed25519PublicKeyParameters) if (keyParam is Ed25519PrivateKeyParameters ||
keyParam is Ed25519PublicKeyParameters)
EdDsaSigner(Ed25519Signer(), staticDigest) EdDsaSigner(Ed25519Signer(), staticDigest)
else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest) else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest)
} }
@ -91,10 +101,7 @@ class BcPGPHashContextContentSignerBuilder(
} }
// Copied from BCs BcImplProvider - required since BCs class is package visible only :/ // Copied from BCs BcImplProvider - required since BCs class is package visible only :/
internal class EdDsaSigner( internal class EdDsaSigner(private val signer: Signer, private val digest: Digest) : Signer {
private val signer: Signer,
private val digest: Digest
) : Signer {
private val digBuf: ByteArray = ByteArray(digest.digestSize) private val digBuf: ByteArray = ByteArray(digest.digestSize)
override fun init(forSigning: Boolean, param: CipherParameters) { override fun init(forSigning: Boolean, param: CipherParameters) {

View File

@ -5,17 +5,15 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import org.pgpainless.algorithm.StreamEncoding
import java.io.OutputStream import java.io.OutputStream
import org.pgpainless.algorithm.StreamEncoding
/** /**
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding]. * [OutputStream] which applies CR-LF encoding of its input data, based on the desired
* This implementation originates from the Bouncy Castle library. * [StreamEncoding]. This implementation originates from the Bouncy Castle library.
*/ */
class CRLFGeneratorStream( class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEncoding) :
private val crlfOut: OutputStream, OutputStream() {
encoding: StreamEncoding
) : OutputStream() {
private val isBinary: Boolean private val isBinary: Boolean
private var lastB = 0 private var lastB = 0

View File

@ -4,16 +4,18 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.io.OutputStream
import org.pgpainless.PGPainless.Companion.getPolicy import org.pgpainless.PGPainless.Companion.getPolicy
import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.OutputStream
class EncryptionBuilder : EncryptionBuilderInterface { class EncryptionBuilder : EncryptionBuilderInterface {
override fun onOutputStream(outputStream: OutputStream): EncryptionBuilderInterface.WithOptions { override fun onOutputStream(
outputStream: OutputStream
): EncryptionBuilderInterface.WithOptions {
return WithOptionsImpl(outputStream) return WithOptionsImpl(outputStream)
} }
@ -26,8 +28,7 @@ class EncryptionBuilder : EncryptionBuilderInterface {
companion object { companion object {
@JvmStatic @JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
/** /**
* Negotiate the [SymmetricKeyAlgorithm] used for message encryption. * Negotiate the [SymmetricKeyAlgorithm] used for message encryption.
@ -36,22 +37,30 @@ class EncryptionBuilder : EncryptionBuilderInterface {
* @return negotiated symmetric key algorithm * @return negotiated symmetric key algorithm
*/ */
@JvmStatic @JvmStatic
fun negotiateSymmetricEncryptionAlgorithm(encryptionOptions: EncryptionOptions): SymmetricKeyAlgorithm { fun negotiateSymmetricEncryptionAlgorithm(
val preferences = encryptionOptions.keyViews.values encryptionOptions: EncryptionOptions
): SymmetricKeyAlgorithm {
val preferences =
encryptionOptions.keyViews.values
.map { it.preferredSymmetricKeyAlgorithms } .map { it.preferredSymmetricKeyAlgorithms }
.toList() .toList()
val algorithm = byPopularity().negotiate( val algorithm =
byPopularity()
.negotiate(
getPolicy().symmetricKeyEncryptionAlgorithmPolicy, getPolicy().symmetricKeyEncryptionAlgorithmPolicy,
encryptionOptions.encryptionAlgorithmOverride, encryptionOptions.encryptionAlgorithmOverride,
preferences) preferences)
LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm) LOGGER.debug(
"Negotiation resulted in {} being the symmetric encryption algorithm of choice.",
algorithm)
return algorithm return algorithm
} }
@JvmStatic @JvmStatic
fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm { fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm {
val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride
return compressionAlgorithmOverride ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm() return compressionAlgorithmOverride
?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm()
// TODO: Negotiation // TODO: Negotiation
} }

View File

@ -4,9 +4,9 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import org.bouncycastle.openpgp.PGPException
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import org.bouncycastle.openpgp.PGPException
fun interface EncryptionBuilderInterface { fun interface EncryptionBuilderInterface {
@ -26,9 +26,9 @@ fun interface EncryptionBuilderInterface {
* *
* @param options options * @param options options
* @return encryption stream * @return encryption stream
*
* @throws PGPException if something goes wrong during encryption stream preparation * @throws PGPException if something goes wrong during encryption stream preparation
* @throws IOException if something goes wrong during encryption stream preparation (writing headers) * @throws IOException if something goes wrong during encryption stream preparation (writing
* headers)
*/ */
@Throws(PGPException::class, IOException::class) @Throws(PGPException::class, IOException::class)
fun withOptions(options: ProducerOptions): EncryptionStream fun withOptions(options: ProducerOptions): EncryptionStream

View File

@ -4,6 +4,7 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
@ -19,12 +20,8 @@ import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.key.info.KeyAccessor import org.pgpainless.key.info.KeyAccessor
import org.pgpainless.key.info.KeyRingInfo import org.pgpainless.key.info.KeyRingInfo
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import java.util.*
class EncryptionOptions(private val purpose: EncryptionPurpose) {
class EncryptionOptions(
private val purpose: EncryptionPurpose
) {
private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf() private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf()
private val _encryptionKeyIdentifiers: MutableSet<SubkeyIdentifier> = mutableSetOf() private val _encryptionKeyIdentifiers: MutableSet<SubkeyIdentifier> = mutableSetOf()
private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf() private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf()
@ -37,16 +34,20 @@ class EncryptionOptions(
val encryptionMethods val encryptionMethods
get() = _encryptionMethods.toSet() get() = _encryptionMethods.toSet()
val encryptionKeyIdentifiers val encryptionKeyIdentifiers
get() = _encryptionKeyIdentifiers.toSet() get() = _encryptionKeyIdentifiers.toSet()
val keyRingInfo val keyRingInfo
get() = _keyRingInfo.toMap() get() = _keyRingInfo.toMap()
val keyViews val keyViews
get() = _keyViews.toMap() get() = _keyViews.toMap()
val encryptionAlgorithmOverride val encryptionAlgorithmOverride
get() = _encryptionAlgorithmOverride get() = _encryptionAlgorithmOverride
constructor(): this(EncryptionPurpose.ANY) constructor() : this(EncryptionPurpose.ANY)
/** /**
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
@ -54,54 +55,56 @@ class EncryptionOptions(
* *
* @return encryption options * @return encryption options
*/ */
fun setEvaluationDate(evaluationDate: Date) = apply { fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate }
this.evaluationDate = evaluationDate
}
/** /**
* Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for * Identify authenticatable certificates for the given user-ID by querying the {@link
* identifiable bindings. * CertificateAuthority} for identifiable bindings. Add all acceptable bindings, whose trust
* Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients. * amount is larger or equal to the target amount to the list of recipients.
*
* @param userId userId * @param userId userId
* @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address * @param email if true, treat the user-ID as an email address and match all user-IDs containing
* the mail address
* @param authority certificate authority * @param authority certificate authority
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, * @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, 60
* 60 = partially authenticated...) * = partially authenticated...)
* @return encryption options * @return encryption options
*/ */
@JvmOverloads @JvmOverloads
fun addAuthenticatableRecipients(userId: String, email: Boolean, authority: CertificateAuthority, targetAmount: Int = 120) = apply { fun addAuthenticatableRecipients(
userId: String,
email: Boolean,
authority: CertificateAuthority,
targetAmount: Int = 120
) = apply {
var foundAcceptable = false var foundAcceptable = false
authority.lookupByUserId(userId, email, evaluationDate, targetAmount) authority
.lookupByUserId(userId, email, evaluationDate, targetAmount)
.filter { it.isAuthenticated() } .filter { it.isAuthenticated() }
.forEach { addRecipient(it.certificate) .forEach { addRecipient(it.certificate).also { foundAcceptable = true } }
.also {
foundAcceptable = true
}
}
require(foundAcceptable) { require(foundAcceptable) {
"Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount." "Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount."
} }
} }
/** /**
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
* as recipients.
* *
* @param keys keys * @param keys keys
* @return this * @return this
*/ */
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply { fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
keys.toList().let { keys.toList().let {
require(it.isNotEmpty()) { require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
"Set of recipient keys cannot be empty."
}
it.forEach { key -> addRecipient(key) } it.forEach { key -> addRecipient(key) }
} }
} }
/** /**
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients. * Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
* Per key ring, the selector is applied to select one or more encryption subkeys. * as recipients. Per key ring, the selector is applied to select one or more encryption
* subkeys.
* *
* @param keys keys * @param keys keys
* @param selector encryption key selector * @param selector encryption key selector
@ -109,9 +112,7 @@ class EncryptionOptions(
*/ */
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply { fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
keys.toList().let { keys.toList().let {
require(it.isNotEmpty()) { require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
"Set of recipient keys cannot be empty."
}
it.forEach { key -> addRecipient(key, selector) } it.forEach { key -> addRecipient(key, selector) }
} }
} }
@ -125,8 +126,8 @@ class EncryptionOptions(
fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector) fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector)
/** /**
* Add a recipient by providing a key and recipient user-id. * Add a recipient by providing a key and recipient user-id. The user-id is used to determine
* The user-id is used to determine the recipients preferences (algorithms etc.). * the recipients preferences (algorithms etc.).
* *
* @param key key ring * @param key key ring
* @param userId user id * @param userId user id
@ -135,9 +136,15 @@ class EncryptionOptions(
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) = fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
addRecipient(key, userId, encryptionKeySelector) addRecipient(key, userId, encryptionKeySelector)
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector) = apply { fun addRecipient(
key: PGPPublicKeyRing,
userId: CharSequence,
encryptionKeySelector: EncryptionKeySelector
) = apply {
val info = KeyRingInfo(key, evaluationDate) val info = KeyRingInfo(key, evaluationDate)
val subkeys = encryptionKeySelector.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose)) val subkeys =
encryptionKeySelector.selectEncryptionSubkeys(
info.getEncryptionSubkeys(userId, purpose))
if (subkeys.isEmpty()) { if (subkeys.isEmpty()) {
throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)) throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
} }
@ -155,13 +162,19 @@ class EncryptionOptions(
} }
@JvmOverloads @JvmOverloads
fun addHiddenRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector) = apply { fun addHiddenRecipient(
addAsRecipient(key, selector, true) key: PGPPublicKeyRing,
} selector: EncryptionKeySelector = encryptionKeySelector
) = apply { addAsRecipient(key, selector, true) }
private fun addAsRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector, wildcardKeyId: Boolean) = apply { private fun addAsRecipient(
key: PGPPublicKeyRing,
selector: EncryptionKeySelector,
wildcardKeyId: Boolean
) = apply {
val info = KeyRingInfo(key, evaluationDate) val info = KeyRingInfo(key, evaluationDate)
val primaryKeyExpiration = try { val primaryKeyExpiration =
try {
info.primaryKeyExpirationDate info.primaryKeyExpirationDate
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)) throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
@ -174,10 +187,12 @@ class EncryptionOptions(
var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose)) var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose))
// There are some legacy keys around without key flags. // There are some legacy keys around without key flags.
// If we allow encryption for those keys, we add valid keys without any key flags, if they are // If we allow encryption for those keys, we add valid keys without any key flags, if they
// are
// capable of encryption by means of their algorithm // capable of encryption by means of their algorithm
if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) { if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) {
encryptionSubkeys = info.validSubkeys encryptionSubkeys =
info.validSubkeys
.filter { it.isEncryptionKey } .filter { it.isEncryptionKey }
.filter { info.getKeyFlagsOf(it.keyID).isEmpty() } .filter { info.getKeyFlagsOf(it.keyID).isEmpty() }
} }
@ -194,13 +209,16 @@ class EncryptionOptions(
} }
} }
private fun addRecipientKey(certificate: PGPPublicKeyRing, private fun addRecipientKey(
certificate: PGPPublicKeyRing,
key: PGPPublicKey, key: PGPPublicKey,
wildcardKeyId: Boolean) { wildcardKeyId: Boolean
) {
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID)) _encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
addEncryptionMethod(ImplementationFactory.getInstance() addEncryptionMethod(
.getPublicKeyKeyEncryptionMethodGenerator(key) ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also {
.also { it.setUseWildcardKeyID(wildcardKeyId) }) it.setUseWildcardKeyID(wildcardKeyId)
})
} }
/** /**
@ -210,19 +228,19 @@ class EncryptionOptions(
* @return this * @return this
*/ */
fun addPassphrase(passphrase: Passphrase) = apply { fun addPassphrase(passphrase: Passphrase) = apply {
require(!passphrase.isEmpty) { require(!passphrase.isEmpty) { "Passphrase MUST NOT be empty." }
"Passphrase MUST NOT be empty." addEncryptionMethod(
} ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
addEncryptionMethod(ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
} }
/** /**
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message. * Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) * Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) or {@link
* or {@link PGPKeyEncryptionMethodGenerator} (public key). * PGPKeyEncryptionMethodGenerator} (public key).
* *
* This method is intended for advanced users to allow encryption for specific subkeys. * This method is intended for advanced users to allow encryption for specific subkeys. This can
* This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless. * come in handy for example if data needs to be encrypted to a subkey that's ignored by
* PGPainless.
* *
* @param encryptionMethod encryption method * @param encryptionMethod encryption method
* @return this * @return this
@ -232,10 +250,9 @@ class EncryptionOptions(
} }
/** /**
* Override the used symmetric encryption algorithm. * Override the used symmetric encryption algorithm. The symmetric encryption algorithm is used
* The symmetric encryption algorithm is used to encrypt the message itself, * to encrypt the message itself, while the used symmetric key will be encrypted to all
* while the used symmetric key will be encrypted to all recipients using public key * recipients using public key cryptography.
* cryptography.
* *
* If the algorithm is not overridden, a suitable algorithm will be negotiated. * If the algorithm is not overridden, a suitable algorithm will be negotiated.
* *
@ -250,10 +267,10 @@ class EncryptionOptions(
} }
/** /**
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption * If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will
* for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket. * allow encryption for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag}
* This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm * subpacket. This is a workaround for dealing with legacy keys that have no key flags subpacket
* type to convey the subkeys use. * but rely on the key algorithm type to convey the subkeys use.
* *
* @return this * @return this
*/ */
@ -263,20 +280,16 @@ class EncryptionOptions(
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
fun interface EncryptionKeySelector { fun interface EncryptionKeySelector {
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey> fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey>
} }
companion object { companion object {
@JvmStatic @JvmStatic fun get() = EncryptionOptions()
fun get() = EncryptionOptions()
@JvmStatic @JvmStatic fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
@JvmStatic @JvmStatic fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
/** /**
* Only encrypt to the first valid encryption capable subkey we stumble upon. * Only encrypt to the first valid encryption capable subkey we stumble upon.
@ -285,7 +298,8 @@ class EncryptionOptions(
*/ */
@JvmStatic @JvmStatic
fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys -> fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys ->
encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() } encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf()
}
/** /**
* Encrypt to any valid, encryption capable subkey on the key ring. * Encrypt to any valid, encryption capable subkey on the key ring.
@ -293,6 +307,8 @@ class EncryptionOptions(
* @return encryption key selector * @return encryption key selector
*/ */
@JvmStatic @JvmStatic
fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> encryptionCapableKeys } fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys ->
encryptionCapableKeys
}
} }
} }

View File

@ -4,6 +4,7 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.extensions.matches import org.bouncycastle.extensions.matches
import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPLiteralData
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
@ -13,7 +14,6 @@ import org.pgpainless.algorithm.StreamEncoding
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.util.MultiMap import org.pgpainless.util.MultiMap
import java.util.*
data class EncryptionResult( data class EncryptionResult(
val encryptionAlgorithm: SymmetricKeyAlgorithm, val encryptionAlgorithm: SymmetricKeyAlgorithm,
@ -26,8 +26,8 @@ data class EncryptionResult(
) { ) {
/** /**
* Return true, if the message is marked as for-your-eyes-only. * Return true, if the message is marked as for-your-eyes-only. This is typically done by
* This is typically done by setting the filename "_CONSOLE". * setting the filename "_CONSOLE".
* *
* @return is message for your eyes only? * @return is message for your eyes only?
*/ */
@ -48,8 +48,7 @@ data class EncryptionResult(
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun builder() = Builder()
fun builder() = Builder()
} }
class Builder { class Builder {
@ -70,32 +69,35 @@ data class EncryptionResult(
_compressionAlgorithm = compressionAlgorithm _compressionAlgorithm = compressionAlgorithm
} }
fun setFileName(fileName: String) = apply { fun setFileName(fileName: String) = apply { _fileName = fileName }
_fileName = fileName
}
fun setModificationDate(modificationDate: Date) = apply { fun setModificationDate(modificationDate: Date) = apply {
_modificationDate = modificationDate _modificationDate = modificationDate
} }
fun setFileEncoding(encoding: StreamEncoding) = apply { fun setFileEncoding(encoding: StreamEncoding) = apply { _encoding = encoding }
_encoding = encoding
}
fun addRecipient(recipient: SubkeyIdentifier) = apply { fun addRecipient(recipient: SubkeyIdentifier) = apply {
(recipients as MutableSet).add(recipient) (recipients as MutableSet).add(recipient)
} }
fun addDetachedSignature(signingSubkeyIdentifier: SubkeyIdentifier, detachedSignature: PGPSignature) = apply { fun addDetachedSignature(
detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) signingSubkeyIdentifier: SubkeyIdentifier,
} detachedSignature: PGPSignature
) = apply { detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) }
fun build(): EncryptionResult { fun build(): EncryptionResult {
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." } checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." } checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." }
return EncryptionResult(_encryptionAlgorithm!!, _compressionAlgorithm!!, detachedSignatures, recipients, return EncryptionResult(
_fileName, _modificationDate, _encoding) _encryptionAlgorithm!!,
_compressionAlgorithm!!,
detachedSignatures,
recipients,
_fileName,
_modificationDate,
_encoding)
} }
} }
} }

View File

@ -4,6 +4,9 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.io.BufferedOutputStream
import java.io.IOException
import java.io.OutputStream
import org.bouncycastle.bcpg.ArmoredOutputStream import org.bouncycastle.bcpg.ArmoredOutputStream
import org.bouncycastle.bcpg.BCPGOutputStream import org.bouncycastle.bcpg.BCPGOutputStream
import org.bouncycastle.openpgp.PGPCompressedDataGenerator import org.bouncycastle.openpgp.PGPCompressedDataGenerator
@ -16,9 +19,6 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmoredOutputStreamFactory import org.pgpainless.util.ArmoredOutputStreamFactory
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.BufferedOutputStream
import java.io.IOException
import java.io.OutputStream
// 1 << 8 causes wrong partial body length encoding // 1 << 8 causes wrong partial body length encoding
// 1 << 9 fixes this. // 1 << 9 fixes this.
@ -30,7 +30,9 @@ const val BUFFER_SIZE = 1 shl 9
* depending on its configuration. * depending on its configuration.
* *
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream. * This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a> *
* @see <a
* href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
*/ */
class EncryptionStream( class EncryptionStream(
private var outermostStream: OutputStream, private var outermostStream: OutputStream,
@ -66,8 +68,8 @@ class EncryptionStream(
outermostStream = BufferedOutputStream(outermostStream) outermostStream = BufferedOutputStream(outermostStream)
LOGGER.debug("Wrap encryption output in ASCII armor.") LOGGER.debug("Wrap encryption output in ASCII armor.")
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options) armorOutputStream =
.also { outermostStream = it } ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it }
} }
@Throws(IOException::class, PGPException::class) @Throws(IOException::class, PGPException::class)
@ -84,9 +86,11 @@ class EncryptionStream(
EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let { EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let {
resultBuilder.setEncryptionAlgorithm(it) resultBuilder.setEncryptionAlgorithm(it)
LOGGER.debug("Encrypt message using symmetric algorithm $it.") LOGGER.debug("Encrypt message using symmetric algorithm $it.")
val encryptedDataGenerator = PGPEncryptedDataGenerator( val encryptedDataGenerator =
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it) PGPEncryptedDataGenerator(
.apply { setWithIntegrityPacket(true) }) ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it).apply {
setWithIntegrityPacket(true)
})
options.encryptionOptions.encryptionMethods.forEach { m -> options.encryptionOptions.encryptionMethods.forEach { m ->
encryptedDataGenerator.addMethod(m) encryptedDataGenerator.addMethod(m)
} }
@ -94,8 +98,11 @@ class EncryptionStream(
resultBuilder.addRecipient(r) resultBuilder.addRecipient(r)
} }
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)) publicKeyEncryptedStream =
.also { stream -> outermostStream = stream } encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream
->
outermostStream = stream
}
} }
} }
@ -107,8 +114,10 @@ class EncryptionStream(
if (it == CompressionAlgorithm.UNCOMPRESSED) return if (it == CompressionAlgorithm.UNCOMPRESSED) return
LOGGER.debug("Compress using $it.") LOGGER.debug("Compress using $it.")
basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)) basicCompressionStream =
.also { stream -> outermostStream = stream } BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream ->
outermostStream = stream
}
} }
} }
@ -138,11 +147,16 @@ class EncryptionStream(
return return
} }
literalDataGenerator = PGPLiteralDataGenerator().also { gen -> literalDataGenerator =
literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName, PGPLiteralDataGenerator().also { gen ->
options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream -> literalDataStream =
outermostStream = stream gen.open(
} outermostStream,
options.encoding.code,
options.fileName,
options.modificationDate,
ByteArray(BUFFER_SIZE))
.also { stream -> outermostStream = stream }
} }
resultBuilder.apply { resultBuilder.apply {
setFileName(options.fileName) setFileName(options.fileName)
@ -156,39 +170,47 @@ class EncryptionStream(
} }
private fun prepareInputEncoding() { private fun prepareInputEncoding() {
outermostStream = CRLFGeneratorStream( outermostStream =
CRLFGeneratorStream(
// By buffering here, we drastically improve performance // By buffering here, we drastically improve performance
// Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to // Reason is that CRLFGeneratorStream only implements write(int), so we need
// BufferedOutputStream to
// "convert" to write(buf) calls again // "convert" to write(buf) calls again
BufferedOutputStream(outermostStream), BufferedOutputStream(outermostStream),
if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY) if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY)
} }
private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> { private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> {
return options.signingOptions?.signingMethods?.values return options.signingOptions
?.map { it.hashAlgorithm }?.toSet() ?.signingMethods
?.map { it.algorithmId }?.toTypedArray() ?.values
?.map { it.hashAlgorithm }
?.toSet()
?.map { it.algorithmId }
?.toTypedArray()
?: arrayOf() ?: arrayOf()
} }
@Throws(IOException::class) @Throws(IOException::class) override fun write(data: Int) = outermostStream.write(data)
override fun write(data: Int) = outermostStream.write(data)
@Throws(IOException::class) @Throws(IOException::class)
override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size) override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size)
@Throws(IOException::class) @Throws(IOException::class)
override fun write(buffer: ByteArray, off: Int, len: Int) = outermostStream.write(buffer, off, len) override fun write(buffer: ByteArray, off: Int, len: Int) =
outermostStream.write(buffer, off, len)
@Throws(IOException::class) @Throws(IOException::class) override fun flush() = outermostStream.flush()
override fun flush() = outermostStream.flush()
@Throws(IOException::class) @Throws(IOException::class)
override fun close() { override fun close() {
if (closed) return if (closed) return
outermostStream.close() outermostStream.close()
literalDataStream?.apply { flush(); close() } literalDataStream?.apply {
flush()
close()
}
literalDataGenerator?.close() literalDataGenerator?.close()
if (options.isCleartextSigned) { if (options.isCleartextSigned) {
@ -201,7 +223,7 @@ class EncryptionStream(
try { try {
writeSignatures() writeSignatures()
} catch (e : PGPException) { } catch (e: PGPException) {
throw IOException("Exception while writing signatures.", e) throw IOException("Exception while writing signatures.", e)
} }
@ -238,14 +260,14 @@ class EncryptionStream(
} }
val result: EncryptionResult val result: EncryptionResult
get() = check(closed) { "EncryptionStream must be closed before accessing the result." } get() =
check(closed) { "EncryptionStream must be closed before accessing the result." }
.let { resultBuilder.build() } .let { resultBuilder.build() }
val isClosed val isClosed
get() = closed get() = closed
companion object { companion object {
@JvmStatic @JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
} }
} }

View File

@ -4,38 +4,42 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.io.OutputStream
import java.security.MessageDigest
import org.bouncycastle.crypto.Digest import org.bouncycastle.crypto.Digest
import org.bouncycastle.crypto.Signer import org.bouncycastle.crypto.Signer
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
import java.io.OutputStream
import java.security.MessageDigest
abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder { abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder {
// Copied from BC, required since BCs class is package visible only // Copied from BC, required since BCs class is package visible only
internal class SignerOutputStream( internal class SignerOutputStream(private val signer: Signer) : OutputStream() {
private val signer: Signer
) : OutputStream() {
override fun write(p0: Int) = signer.update(p0.toByte()) override fun write(p0: Int) = signer.update(p0.toByte())
override fun write(b: ByteArray) = signer.update(b, 0, b.size) override fun write(b: ByteArray) = signer.update(b, 0, b.size)
override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len) override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len)
} }
internal class ExistingMessageDigest( internal class ExistingMessageDigest(private val digest: MessageDigest) : Digest {
private val digest: MessageDigest
) : Digest {
override fun getAlgorithmName(): String = digest.algorithm override fun getAlgorithmName(): String = digest.algorithm
override fun getDigestSize(): Int = digest.digestLength override fun getDigestSize(): Int = digest.digestLength
override fun update(b: Byte) = digest.update(b) override fun update(b: Byte) = digest.update(b)
override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf) override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf)
override fun doFinal(out: ByteArray, outOff: Int): Int { override fun doFinal(out: ByteArray, outOff: Int): Int {
digest.digest().copyInto(out, outOff) digest.digest().copyInto(out, outOff)
return digestSize return digestSize
} }
override fun reset() { override fun reset() {
// Nope! // Nope!
// We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset // We cannot reset, since BCs signer classes are resetting in their init() methods,
// which would also reset
// the messageDigest, losing its state. This would shatter our intention. // the messageDigest, losing its state. This would shatter our intention.
} }
} }

View File

@ -4,15 +4,17 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPLiteralData
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.StreamEncoding
import java.util.*
class ProducerOptions private constructor( class ProducerOptions
private constructor(
val encryptionOptions: EncryptionOptions?, val encryptionOptions: EncryptionOptions?,
val signingOptions: SigningOptions?) { val signingOptions: SigningOptions?
) {
private var _fileName: String = "" private var _fileName: String = ""
private var _modificationDate: Date = PGPLiteralData.NOW private var _modificationDate: Date = PGPLiteralData.NOW
@ -22,14 +24,15 @@ class ProducerOptions private constructor(
private var _hideArmorHeaders = false private var _hideArmorHeaders = false
var isDisableAsciiArmorCRC = false var isDisableAsciiArmorCRC = false
private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm private var _compressionAlgorithmOverride: CompressionAlgorithm =
PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm
private var asciiArmor = true private var asciiArmor = true
private var _comment: String? = null private var _comment: String? = null
private var _version: String? = null private var _version: String? = null
/** /**
* Specify, whether the result of the encryption/signing operation shall be ascii armored. * Specify, whether the result of the encryption/signing operation shall be ascii armored. The
* The default value is true. * default value is true.
* *
* @param asciiArmor ascii armor * @param asciiArmor ascii armor
* @return builder * @return builder
@ -50,19 +53,15 @@ class ProducerOptions private constructor(
get() = asciiArmor get() = asciiArmor
/** /**
* Set the comment header in ASCII armored output. * Set the comment header in ASCII armored output. The default value is null, which means no
* The default value is null, which means no comment header is added. * comment header is added. Multiline comments are possible using '\\n'. <br> Note: If a default
* Multiline comments are possible using '\\n'. * header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], then
* <br> * both comments will be written to the produced ASCII armor.
* Note: If a default header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment],
* then both comments will be written to the produced ASCII armor.
* *
* @param comment comment header text * @param comment comment header text
* @return builder * @return builder
*/ */
fun setComment(comment: String?) = apply { fun setComment(comment: String?) = apply { _comment = comment }
_comment = comment
}
/** /**
* Return comment set for header in ascii armored output. * Return comment set for header in ascii armored output.
@ -80,18 +79,15 @@ class ProducerOptions private constructor(
fun hasComment() = _comment != null fun hasComment() = _comment != null
/** /**
* Set the version header in ASCII armored output. * Set the version header in ASCII armored output. The default value is null, which means no
* The default value is null, which means no version header is added. * version header is added. <br> Note: If the value is non-null, then this method overrides the
* <br> * default version header set using
* Note: If the value is non-null, then this method overrides the default version header set using
* [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo]. * [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo].
* *
* @param version version header, or null for no version info. * @param version version header, or null for no version info.
* @return builder * @return builder
*/ */
fun setVersion(version: String?) = apply { fun setVersion(version: String?) = apply { _version = version }
_version = version
}
/** /**
* Return the version info header in ascii armored output. * Return the version info header in ascii armored output.
@ -128,15 +124,13 @@ class ProducerOptions private constructor(
get() = cleartextSigned get() = cleartextSigned
/** /**
* Set the name of the encrypted file. * Set the name of the encrypted file. Note: This option cannot be used simultaneously with
* Note: This option cannot be used simultaneously with [setForYourEyesOnly]. * [setForYourEyesOnly].
* *
* @param fileName name of the encrypted file * @param fileName name of the encrypted file
* @return this * @return this
*/ */
fun setFileName(fileName: String) = apply { fun setFileName(fileName: String) = apply { _fileName = fileName }
_fileName = fileName
}
/** /**
* Return the encrypted files name. * Return the encrypted files name.
@ -147,17 +141,15 @@ class ProducerOptions private constructor(
get() = _fileName get() = _fileName
/** /**
* Mark the encrypted message as for-your-eyes-only by setting a special file name. * Mark the encrypted message as for-your-eyes-only by setting a special file name. Note:
* Note: Therefore this method cannot be used simultaneously with [setFileName]. * Therefore this method cannot be used simultaneously with [setFileName].
* *
* @return this * @return this
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in * @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this
* newly generated literal data packets * special filename in newly generated literal data packets
*/ */
@Deprecated("Signaling using special file name is discouraged.") @Deprecated("Signaling using special file name is discouraged.")
fun setForYourEyesOnly() = apply { fun setForYourEyesOnly() = apply { _fileName = PGPLiteralData.CONSOLE }
_fileName = PGPLiteralData.CONSOLE
}
/** /**
* Set the modification date of the encrypted file. * Set the modification date of the encrypted file.
@ -165,9 +157,7 @@ class ProducerOptions private constructor(
* @param modificationDate Modification date of the encrypted file. * @param modificationDate Modification date of the encrypted file.
* @return this * @return this
*/ */
fun setModificationDate(modificationDate: Date) = apply { fun setModificationDate(modificationDate: Date) = apply { _modificationDate = modificationDate }
_modificationDate = modificationDate
}
/** /**
* Return the modification date of the encrypted file. * Return the modification date of the encrypted file.
@ -178,40 +168,32 @@ class ProducerOptions private constructor(
get() = _modificationDate get() = _modificationDate
/** /**
* Set format metadata field of the literal data packet. * Set format metadata field of the literal data packet. Defaults to [StreamEncoding.BINARY].
* Defaults to [StreamEncoding.BINARY]. * <br> This does not change the encoding of the wrapped data itself. To apply CR/LF encoding to
* <br> * your input data before processing, use [applyCRLFEncoding] instead.
* This does not change the encoding of the wrapped data itself.
* To apply CR/LF encoding to your input data before processing, use [applyCRLFEncoding] instead.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
* *
* @param encoding encoding * @param encoding encoding
* @return this * @return this
* * @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9.
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged. * Literal Data Packet</a>
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are
* discouraged.
*/ */
@Deprecated("Options other than BINARY are discouraged.") @Deprecated("Options other than BINARY are discouraged.")
fun setEncoding(encoding: StreamEncoding) = apply { fun setEncoding(encoding: StreamEncoding) = apply { encodingField = encoding }
encodingField = encoding
}
val encoding: StreamEncoding val encoding: StreamEncoding
get() = encodingField get() = encodingField
/** /**
* Apply special encoding of line endings to the input data. * Apply special encoding of line endings to the input data. By default, this is disabled, which
* By default, this is disabled, which means that the data is not altered. * means that the data is not altered. <br> Enabling it will change the line endings to CR/LF.
* <br> * Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will
* Enabling it will change the line endings to CR/LF. * result in the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in
* the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
* *
* @return this * @return this
*/ */
fun applyCRLFEncoding() = apply { fun applyCRLFEncoding() = apply { applyCRLFEncoding = true }
applyCRLFEncoding = true
}
/** /**
* Return the input encoding that will be applied before signing / encryption. * Return the input encoding that will be applied before signing / encryption.
@ -239,9 +221,8 @@ class ProducerOptions private constructor(
/** /**
* If set to `true`, armor headers like version or comments will be omitted from armored output. * If set to `true`, armor headers like version or comments will be omitted from armored output.
* By default, armor headers are not hidden. * By default, armor headers are not hidden. Note: If comments are added via [setComment], those
* Note: If comments are added via [setComment], those are not omitted, even if * are not omitted, even if [hideArmorHeaders] is set to `true`.
* [hideArmorHeaders] is set to `true`.
* *
* @param hideArmorHeaders true or false * @param hideArmorHeaders true or false
* @return this * @return this
@ -268,8 +249,7 @@ class ProducerOptions private constructor(
* @param signingOptions signing options * @param signingOptions signing options
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
/** /**
* Encrypt some data without signing. * Encrypt some data without signing.
@ -281,12 +261,10 @@ class ProducerOptions private constructor(
fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null) fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null)
/** /**
* Only wrap the data in an OpenPGP packet. * Only wrap the data in an OpenPGP packet. No encryption or signing will be applied.
* No encryption or signing will be applied.
* *
* @return builder * @return builder
*/ */
@JvmStatic @JvmStatic fun noEncryptionNoSigning() = ProducerOptions(null, null)
fun noEncryptionNoSigning() = ProducerOptions(null, null)
} }
} }

View File

@ -6,23 +6,20 @@ package org.pgpainless.encryption_signing
import java.io.OutputStream import java.io.OutputStream
/** /** OutputStream which has the task of updating signature generators for written data. */
* OutputStream which has the task of updating signature generators for written data.
*/
class SignatureGenerationStream( class SignatureGenerationStream(
private val wrapped: OutputStream, private val wrapped: OutputStream,
private val options: SigningOptions? private val options: SigningOptions?
) : OutputStream() { ) : OutputStream() {
override fun close() = wrapped.close() override fun close() = wrapped.close()
override fun flush() = wrapped.flush() override fun flush() = wrapped.flush()
override fun write(b: Int) { override fun write(b: Int) {
wrapped.write(b) wrapped.write(b)
options?.run { options?.run {
signingMethods.values.forEach { signingMethods.values.forEach { it.signatureGenerator.update((b and 0xff).toByte()) }
it.signatureGenerator.update((b and 0xff).toByte())
}
} }
} }
@ -30,10 +27,6 @@ class SignatureGenerationStream(
override fun write(b: ByteArray, off: Int, len: Int) { override fun write(b: ByteArray, off: Int, len: Int) {
wrapped.write(b, off, len) wrapped.write(b, off, len)
options?.run { options?.run { signingMethods.values.forEach { it.signatureGenerator.update(b, off, len) } }
signingMethods.values.forEach {
it.signatureGenerator.update(b, off, len)
}
}
} }
} }

View File

@ -4,6 +4,7 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.extensions.unlock import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.pgpainless.PGPainless.Companion.getPolicy import org.pgpainless.PGPainless.Companion.getPolicy
@ -22,7 +23,6 @@ import org.pgpainless.policy.Policy
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback
import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import java.util.*
class SigningOptions { class SigningOptions {
@ -34,10 +34,10 @@ class SigningOptions {
get() = _hashAlgorithmOverride get() = _hashAlgorithmOverride
/** /**
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used. * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. If no
* If no override has been set, an acceptable algorithm will be negotiated instead. * override has been set, an acceptable algorithm will be negotiated instead. Note: To override
* Note: To override the hash algorithm for signing, call this method *before* calling * the hash algorithm for signing, call this method *before* calling [addInlineSignature] or
* [addInlineSignature] or [addDetachedSignature]. * [addDetachedSignature].
* *
* @param hashAlgorithmOverride override hash algorithm * @param hashAlgorithmOverride override hash algorithm
* @return this * @return this
@ -55,9 +55,7 @@ class SigningOptions {
* @param evaluationDate new evaluation date * @param evaluationDate new evaluation date
* @return this * @return this
*/ */
fun setEvaluationDate(evaluationDate: Date) = apply { fun setEvaluationDate(evaluationDate: Date) = apply { _evaluationDate = evaluationDate }
_evaluationDate = evaluationDate
}
/** /**
* Sign the message using an inline signature made by the provided signing key. * Sign the message using an inline signature made by the provided signing key.
@ -65,13 +63,14 @@ class SigningOptions {
* @param signingKeyProtector protector to unlock the signing key * @param signingKeyProtector protector to unlock the signing key
* @param signingKey key ring containing the signing key * @param signingKey key ring containing the signing key
* @return this * @return this
*
* @throws KeyException if something is wrong with the key * @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created * @throws PGPException if the key cannot be unlocked or a signing method cannot be created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply { fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) =
addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) apply {
addInlineSignature(
signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
} }
/** /**
@ -81,44 +80,41 @@ class SigningOptions {
* @param signingKeys collection of signing keys * @param signingKeys collection of signing keys
* @param signatureType type of signature (binary, canonical text) * @param signatureType type of signature (binary, canonical text)
* @return this * @return this
*
* @throws KeyException if something is wrong with any of the keys * @throws KeyException if something is wrong with any of the keys
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be
* created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector, fun addInlineSignatures(
signingKeyProtector: SecretKeyRingProtector,
signingKeys: Iterable<PGPSecretKeyRing>, signingKeys: Iterable<PGPSecretKeyRing>,
signatureType: DocumentSignatureType) = apply { signatureType: DocumentSignatureType
signingKeys.forEach { ) = apply {
addInlineSignature(signingKeyProtector, it, null, signatureType) signingKeys.forEach { addInlineSignature(signingKeyProtector, it, null, signatureType) }
} }
}
/** /**
* Add an inline-signature. * Add an inline-signature. Inline signatures are being embedded into the message itself and can
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use * be processed in one pass, thanks to the use of one-pass-signature packets.
* of one-pass-signature packets.
* *
* @param signingKeyProtector decryptor to unlock the signing secret key * @param signingKeyProtector decryptor to unlock the signing secret key
* @param signingKey signing key * @param signingKey signing key
* @param signatureType type of signature (binary, canonical text) * @param signatureType type of signature (binary, canonical text)
* @return this * @return this
*
* @throws KeyException if something is wrong with the key * @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, fun addInlineSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
signatureType: DocumentSignatureType) = apply { signatureType: DocumentSignatureType
addInlineSignature(signingKeyProtector, signingKey, null, signatureType) ) = apply { addInlineSignature(signingKeyProtector, signingKey, null, signatureType) }
}
/** /**
* Add an inline-signature. * Add an inline-signature. Inline signatures are being embedded into the message itself and can
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use * be processed in one pass, thanks to the use of one-pass-signature packets.
* of one-pass-signature packets. *
* <p> * <p>
* This method uses the passed in user-id to select user-specific hash algorithms. * This method uses the passed in user-id to select user-specific hash algorithms.
* *
@ -126,27 +122,28 @@ class SigningOptions {
* @param signingKey signing key * @param signingKey signing key
* @param userId user-id of the signer * @param userId user-id of the signer
* @param signatureType signature type (binary, canonical text) * @param signatureType signature type (binary, canonical text)
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the
* signature
* @return this * @return this
*
* @throws KeyException if the key is invalid * @throws KeyException if the key is invalid
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
@JvmOverloads @JvmOverloads
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, fun addInlineSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
userId: CharSequence? = null, userId: CharSequence? = null,
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
subpacketsCallback: Callback? = null) = apply { subpacketsCallback: Callback? = null
) = apply {
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
if (userId != null && !keyRingInfo.isUserIdValid(userId)) { if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw UnboundUserIdException( throw UnboundUserIdException(
of(signingKey), of(signingKey),
userId.toString(), userId.toString(),
keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId) keyRingInfo.getUserIdRevocation(userId))
)
} }
val signingPubKeys = keyRingInfo.signingSubkeys val signingPubKeys = keyRingInfo.signingSubkeys
@ -155,14 +152,16 @@ class SigningOptions {
} }
for (signingPubKey in signingPubKeys) { for (signingPubKey in signingPubKeys) {
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) val signingSecKey: PGPSecretKey =
signingKey.getSecretKey(signingPubKey.keyID)
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
val hashAlgorithms = val hashAlgorithms =
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) addSigningMethod(
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
} }
} }
@ -175,17 +174,22 @@ class SigningOptions {
* @param signatureType signature type * @param signatureType signature type
* @param subpacketsCallback callback to modify the signatures subpackets * @param subpacketsCallback callback to modify the signatures subpackets
* @return builder * @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. * @throws PGPException if the secret key cannot be unlocked or if no signing method can be
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys * created.
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
* signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
* identified secret key
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
@JvmOverloads @JvmOverloads
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, fun addInlineSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
keyId: Long, keyId: Long,
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
subpacketsCallback: Callback? = null) = apply { subpacketsCallback: Callback? = null
) = apply {
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
val signingPubKeys = keyRingInfo.signingSubkeys val signingPubKeys = keyRingInfo.signingSubkeys
if (signingPubKeys.isEmpty()) { if (signingPubKeys.isEmpty()) {
@ -197,12 +201,14 @@ class SigningOptions {
continue continue
} }
val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID) val signingSecKey =
signingKey.getSecretKey(signingPubKey.keyID)
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
val signingSubkey = signingSecKey.unlock(signingKeyProtector) val signingSubkey = signingSecKey.unlock(signingKeyProtector)
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) addSigningMethod(
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
return this return this
} }
throw MissingSecretKeyException(of(signingKey), keyId) throw MissingSecretKeyException(of(signingKey), keyId)
@ -215,45 +221,44 @@ class SigningOptions {
* @param signingKeys collection of signing key rings * @param signingKeys collection of signing key rings
* @param signatureType type of the signature (binary, canonical text) * @param signatureType type of the signature (binary, canonical text)
* @return this * @return this
*
* @throws KeyException if something is wrong with any of the keys * @throws KeyException if something is wrong with any of the keys
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing
* method cannot be created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector, fun addDetachedSignatures(
signingKeyProtector: SecretKeyRingProtector,
signingKeys: Iterable<PGPSecretKeyRing>, signingKeys: Iterable<PGPSecretKeyRing>,
signatureType: DocumentSignatureType) = apply { signatureType: DocumentSignatureType
signingKeys.forEach { ) = apply {
addDetachedSignature(signingKeyProtector, it, null, signatureType) signingKeys.forEach { addDetachedSignature(signingKeyProtector, it, null, signatureType) }
}
} }
/** /**
* Create a detached signature. * Create a detached signature. Detached signatures are not being added into the PGP message
* Detached signatures are not being added into the PGP message itself. * itself. Instead, they can be distributed separately to the message. Detached signatures are
* Instead, they can be distributed separately to the message. * useful if the data that is being signed shall not be modified (e.g. when signing a file).
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
* *
* @param signingKeyProtector decryptor to unlock the secret signing key * @param signingKeyProtector decryptor to unlock the secret signing key
* @param signingKey signing key * @param signingKey signing key
* @param signatureType type of data that is signed (binary, canonical text) * @param signatureType type of data that is signed (binary, canonical text)
* @return this * @return this
*
* @throws KeyException if something is wrong with the key * @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created * @throws PGPException if the key cannot be validated or unlocked, or if no signature method
* can be created
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, fun addDetachedSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
signatureType: DocumentSignatureType) = apply { signatureType: DocumentSignatureType
addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) ) = apply { addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) }
}
/** /**
* Create a detached signature. * Create a detached signature. Detached signatures are not being added into the PGP message
* Detached signatures are not being added into the PGP message itself. * itself. Instead, they can be distributed separately to the message. Detached signatures are
* Instead, they can be distributed separately to the message. * useful if the data that is being signed shall not be modified (e.g. when signing a file).
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). *
* <p> * <p>
* This method uses the passed in user-id to select user-specific hash algorithms. * This method uses the passed in user-id to select user-specific hash algorithms.
* *
@ -263,25 +268,26 @@ class SigningOptions {
* @param signatureType type of data that is signed (binary, canonical text) * @param signatureType type of data that is signed (binary, canonical text)
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
* @return this * @return this
*
* @throws KeyException if something is wrong with the key * @throws KeyException if something is wrong with the key
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created * @throws PGPException if the key cannot be validated or unlocked, or if no signature method
* can be created
*/ */
@JvmOverloads @JvmOverloads
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, fun addDetachedSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
userId: String? = null, userId: String? = null,
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
subpacketCallback: Callback? = null) = apply { subpacketCallback: Callback? = null
) = apply {
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
if (userId != null && !keyRingInfo.isUserIdValid(userId)) { if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
throw UnboundUserIdException( throw UnboundUserIdException(
of(signingKey), of(signingKey),
userId.toString(), userId.toString(),
keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getLatestUserIdCertification(userId),
keyRingInfo.getUserIdRevocation(userId) keyRingInfo.getUserIdRevocation(userId))
)
} }
val signingPubKeys = keyRingInfo.signingSubkeys val signingPubKeys = keyRingInfo.signingSubkeys
@ -290,14 +296,16 @@ class SigningOptions {
} }
for (signingPubKey in signingPubKeys) { for (signingPubKey in signingPubKeys) {
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) val signingSecKey: PGPSecretKey =
signingKey.getSecretKey(signingPubKey.keyID)
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
val hashAlgorithms = val hashAlgorithms =
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) addSigningMethod(
signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
} }
} }
@ -310,17 +318,22 @@ class SigningOptions {
* @param signatureType signature type * @param signatureType signature type
* @param subpacketsCallback callback to modify the signatures subpackets * @param subpacketsCallback callback to modify the signatures subpackets
* @return builder * @return builder
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. * @throws PGPException if the secret key cannot be unlocked or if no signing method can be
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys * created.
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
* signing-capable subkeys
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
* identified secret key
*/ */
@Throws(KeyException::class, PGPException::class) @Throws(KeyException::class, PGPException::class)
@JvmOverloads @JvmOverloads
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, fun addDetachedSignature(
signingKeyProtector: SecretKeyRingProtector,
signingKey: PGPSecretKeyRing, signingKey: PGPSecretKeyRing,
keyId: Long, keyId: Long,
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
subpacketsCallback: Callback? = null) = apply { subpacketsCallback: Callback? = null
) = apply {
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
val signingPubKeys = keyRingInfo.signingSubkeys val signingPubKeys = keyRingInfo.signingSubkeys
@ -330,12 +343,20 @@ class SigningOptions {
for (signingPubKey in signingPubKeys) { for (signingPubKey in signingPubKeys) {
if (signingPubKey.keyID == keyId) { if (signingPubKey.keyID == keyId) {
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) val signingSecKey: PGPSecretKey =
signingKey.getSecretKey(signingPubKey.keyID)
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector) val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) val hashAlgorithm: HashAlgorithm =
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback) negotiateHashAlgorithm(hashAlgorithms, getPolicy())
addSigningMethod(
signingKey,
signingSubkey,
hashAlgorithm,
signatureType,
true,
subpacketsCallback)
return this return this
} }
} }
@ -343,12 +364,14 @@ class SigningOptions {
throw MissingSecretKeyException(of(signingKey), keyId) throw MissingSecretKeyException(of(signingKey), keyId)
} }
private fun addSigningMethod(signingKey: PGPSecretKeyRing, private fun addSigningMethod(
signingKey: PGPSecretKeyRing,
signingSubkey: PGPPrivateKey, signingSubkey: PGPPrivateKey,
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
signatureType: DocumentSignatureType, signatureType: DocumentSignatureType,
detached: Boolean, detached: Boolean,
subpacketCallback: Callback? = null) { subpacketCallback: Callback? = null
) {
val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID) val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID)
val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID) val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID)
val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm)
@ -359,10 +382,12 @@ class SigningOptions {
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
} }
val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) val generator: PGPSignatureGenerator =
createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
// Subpackets // Subpackets
val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) val hashedSubpackets =
SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets() val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets()
if (subpacketCallback != null) { if (subpacketCallback != null) {
subpacketCallback.modifyHashedSubpackets(hashedSubpackets) subpacketCallback.modifyHashedSubpackets(hashedSubpackets)
@ -380,42 +405,46 @@ class SigningOptions {
/** /**
* Negotiate, which hash algorithm to use. * Negotiate, which hash algorithm to use.
* *
* * This method gives the highest priority to the algorithm override, which can be set via
* This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm]. * [.overrideHashAlgorithm]. After that, the signing keys hash algorithm preferences are
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. * iterated to find the first acceptable algorithm. Lastly, should no acceptable algorithm be
* Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is * found, the [Policies][Policy] default signature hash algorithm is used as a fallback.
* used as a fallback.
* *
* @param preferences preferences * @param preferences preferences
* @param policy policy * @param policy policy
* @return selected hash algorithm * @return selected hash algorithm
*/ */
private fun negotiateHashAlgorithm(preferences: Set<HashAlgorithm>, private fun negotiateHashAlgorithm(
policy: Policy): HashAlgorithm { preferences: Set<HashAlgorithm>,
return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences) policy: Policy
): HashAlgorithm {
return _hashAlgorithmOverride
?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
} }
@Throws(PGPException::class) @Throws(PGPException::class)
private fun createSignatureGenerator(privateKey: PGPPrivateKey, private fun createSignatureGenerator(
privateKey: PGPPrivateKey,
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
signatureType: DocumentSignatureType): PGPSignatureGenerator { signatureType: DocumentSignatureType
): PGPSignatureGenerator {
return ImplementationFactory.getInstance() return ImplementationFactory.getInstance()
.getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId) .getPGPContentSignerBuilder(
privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
.let { csb -> .let { csb ->
PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) } PGPSignatureGenerator(csb).also {
it.init(signatureType.signatureType.code, privateKey)
}
} }
} }
companion object { companion object {
@JvmStatic @JvmStatic fun get() = SigningOptions()
fun get() = SigningOptions()
} }
/** /** A method of signing. */
* A method of signing. class SigningMethod
*/ private constructor(
class SigningMethod private constructor(
val signatureGenerator: PGPSignatureGenerator, val signatureGenerator: PGPSignatureGenerator,
val isDetached: Boolean, val isDetached: Boolean,
val hashAlgorithm: HashAlgorithm val hashAlgorithm: HashAlgorithm
@ -423,29 +452,32 @@ class SigningOptions {
companion object { companion object {
/** /**
* Inline-signature method. * Inline-signature method. The resulting signature will be written into the message
* The resulting signature will be written into the message itself, together with a one-pass-signature packet. * itself, together with a one-pass-signature packet.
* *
* @param signatureGenerator signature generator * @param signatureGenerator signature generator
* @param hashAlgorithm hash algorithm used to generate the signature * @param hashAlgorithm hash algorithm used to generate the signature
* @return inline signing method * @return inline signing method
*/ */
@JvmStatic @JvmStatic
fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = fun inlineSignature(
SigningMethod(signatureGenerator, false, hashAlgorithm) signatureGenerator: PGPSignatureGenerator,
hashAlgorithm: HashAlgorithm
) = SigningMethod(signatureGenerator, false, hashAlgorithm)
/** /**
* Detached signing method. * Detached signing method. The resulting signature will not be added to the message,
* The resulting signature will not be added to the message, and instead can be distributed separately * and instead can be distributed separately to the signed message.
* to the signed message.
* *
* @param signatureGenerator signature generator * @param signatureGenerator signature generator
* @param hashAlgorithm hash algorithm used to generate the signature * @param hashAlgorithm hash algorithm used to generate the signature
* @return detached signing method * @return detached signing method
*/ */
@JvmStatic @JvmStatic
fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = fun detachedSignature(
SigningMethod(signatureGenerator, true, hashAlgorithm) signatureGenerator: PGPSignatureGenerator,
hashAlgorithm: HashAlgorithm
) = SigningMethod(signatureGenerator, true, hashAlgorithm)
} }
} }
} }

View File

@ -4,6 +4,9 @@
package org.pgpainless.implementation package org.pgpainless.implementation
import java.io.InputStream
import java.security.KeyPair
import java.util.*
import org.bouncycastle.crypto.AsymmetricCipherKeyPair import org.bouncycastle.crypto.AsymmetricCipherKeyPair
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
@ -27,70 +30,85 @@ import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import java.io.InputStream
import java.security.KeyPair
import java.util.*
class BcImplementationFactory : ImplementationFactory() { class BcImplementationFactory : ImplementationFactory() {
override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = BcPGPDigestCalculatorProvider() override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider =
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = BcPGPContentVerifierBuilderProvider() BcPGPDigestCalculatorProvider()
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider =
BcPGPContentVerifierBuilderProvider()
override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator() override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator()
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, override fun getPBESecretKeyEncryptor(
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
digestCalculator: PGPDigestCalculator, digestCalculator: PGPDigestCalculator,
passphrase: Passphrase): PBESecretKeyEncryptor = passphrase: Passphrase
): PBESecretKeyEncryptor =
BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
.build(passphrase.getChars()) .build(passphrase.getChars())
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, override fun getPBESecretKeyEncryptor(
encryptionAlgorithm: SymmetricKeyAlgorithm,
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
s2kCount: Int, s2kCount: Int,
passphrase: Passphrase): PBESecretKeyEncryptor = passphrase: Passphrase
): PBESecretKeyEncryptor =
BcPBESecretKeyEncryptorBuilder( BcPBESecretKeyEncryptorBuilder(
encryptionAlgorithm.algorithmId, encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
getPGPDigestCalculator(hashAlgorithm),
s2kCount)
.build(passphrase.getChars()) .build(passphrase.getChars())
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor = override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider) BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider).build(passphrase.getChars())
.build(passphrase.getChars())
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = override fun getPGPContentSignerBuilder(
BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) keyAlgorithm: Int,
hashAlgorithm: Int
): PGPContentSignerBuilder = BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory = override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider) BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider)
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = override fun getPublicKeyDataDecryptorFactory(
BcPublicKeyDataDecryptorFactory(privateKey) privateKey: PGPPrivateKey
): PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(privateKey)
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = override fun getSessionKeyDataDecryptorFactory(
BcSessionKeyDataDecryptorFactory(sessionKey) sessionKey: PGPSessionKey
): SessionKeyDataDecryptorFactory = BcSessionKeyDataDecryptorFactory(sessionKey)
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = override fun getPublicKeyKeyEncryptionMethodGenerator(
BcPublicKeyKeyEncryptionMethodGenerator(key) key: PGPPublicKey
): PublicKeyKeyEncryptionMethodGenerator = BcPublicKeyKeyEncryptionMethodGenerator(key)
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = override fun getPBEKeyEncryptionMethodGenerator(
BcPBEKeyEncryptionMethodGenerator(passphrase.getChars()) passphrase: Passphrase
): PBEKeyEncryptionMethodGenerator = BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm) BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm)
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = override fun getPGPKeyPair(
publicKeyAlgorithm: PublicKeyAlgorithm,
keyPair: KeyPair,
creationDate: Date
): PGPKeyPair =
BcPGPKeyPair( BcPGPKeyPair(
publicKeyAlgorithm.algorithmId, publicKeyAlgorithm.algorithmId,
jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate), jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate),
creationDate) creationDate)
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = BcPGPObjectFactory(inputStream) override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
BcPGPObjectFactory(inputStream)
private fun jceToBcKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, private fun jceToBcKeyPair(
publicKeyAlgorithm: PublicKeyAlgorithm,
keyPair: KeyPair, keyPair: KeyPair,
creationDate: Date): AsymmetricCipherKeyPair = creationDate: Date
): AsymmetricCipherKeyPair =
BcPGPKeyConverter().let { converter -> BcPGPKeyConverter().let { converter ->
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair -> JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair ->
AsymmetricCipherKeyPair(converter.getPublicKey(pair.publicKey), converter.getPrivateKey(pair.privateKey)) AsymmetricCipherKeyPair(
converter.getPublicKey(pair.publicKey),
converter.getPrivateKey(pair.privateKey))
} }
} }
} }

View File

@ -4,6 +4,9 @@
package org.pgpainless.implementation package org.pgpainless.implementation
import java.io.InputStream
import java.security.KeyPair
import java.util.*
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.* import org.bouncycastle.openpgp.operator.*
import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.HashAlgorithm
@ -11,18 +14,13 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import org.pgpainless.util.SessionKey import org.pgpainless.util.SessionKey
import java.io.InputStream
import java.security.KeyPair
import java.util.*
abstract class ImplementationFactory { abstract class ImplementationFactory {
companion object { companion object {
@JvmStatic @JvmStatic private var instance: ImplementationFactory = BcImplementationFactory()
private var instance: ImplementationFactory = BcImplementationFactory()
@JvmStatic @JvmStatic fun getInstance() = instance
fun getInstance() = instance
@JvmStatic @JvmStatic
fun setFactoryImplementation(implementation: ImplementationFactory) = apply { fun setFactoryImplementation(implementation: ImplementationFactory) = apply {
@ -38,16 +36,22 @@ abstract class ImplementationFactory {
get() = getPGPDigestCalculator(HashAlgorithm.SHA1) get() = getPGPDigestCalculator(HashAlgorithm.SHA1)
@Throws(PGPException::class) @Throws(PGPException::class)
abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, abstract fun getPBESecretKeyEncryptor(
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
digestCalculator: PGPDigestCalculator, digestCalculator: PGPDigestCalculator,
passphrase: Passphrase): PBESecretKeyEncryptor passphrase: Passphrase
): PBESecretKeyEncryptor
@Throws(PGPException::class) @Throws(PGPException::class)
abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor
@Throws(PGPException::class) @Throws(PGPException::class)
abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm, abstract fun getPBESecretKeyEncryptor(
s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor encryptionAlgorithm: SymmetricKeyAlgorithm,
hashAlgorithm: HashAlgorithm,
s2kCount: Int,
passphrase: Passphrase
): PBESecretKeyEncryptor
fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator = fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator =
getPGPDigestCalculator(hashAlgorithm.algorithmId) getPGPDigestCalculator(hashAlgorithm.algorithmId)
@ -55,32 +59,52 @@ abstract class ImplementationFactory {
fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator = fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator =
pgpDigestCalculatorProvider.get(hashAlgorithm) pgpDigestCalculatorProvider.get(hashAlgorithm)
fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder = fun getPGPContentSignerBuilder(
keyAlgorithm: PublicKeyAlgorithm,
hashAlgorithm: HashAlgorithm
): PGPContentSignerBuilder =
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId) getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
abstract fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder abstract fun getPGPContentSignerBuilder(
keyAlgorithm: Int,
hashAlgorithm: Int
): PGPContentSignerBuilder
@Throws(PGPException::class) @Throws(PGPException::class)
abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory
abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory abstract fun getPublicKeyDataDecryptorFactory(
privateKey: PGPPrivateKey
): PublicKeyDataDecryptorFactory
fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory = fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory =
getSessionKeyDataDecryptorFactory(PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key)) getSessionKeyDataDecryptorFactory(
PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key))
abstract fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory abstract fun getSessionKeyDataDecryptorFactory(
sessionKey: PGPSessionKey
): SessionKeyDataDecryptorFactory
abstract fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator abstract fun getPublicKeyKeyEncryptionMethodGenerator(
key: PGPPublicKey
): PublicKeyKeyEncryptionMethodGenerator
abstract fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator abstract fun getPBEKeyEncryptionMethodGenerator(
passphrase: Passphrase
): PBEKeyEncryptionMethodGenerator
fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: SymmetricKeyAlgorithm): PGPDataEncryptorBuilder = fun getPGPDataEncryptorBuilder(
getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId) symmetricKeyAlgorithm: SymmetricKeyAlgorithm
): PGPDataEncryptorBuilder = getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder
@Throws(PGPException::class) @Throws(PGPException::class)
abstract fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair abstract fun getPGPKeyPair(
publicKeyAlgorithm: PublicKeyAlgorithm,
keyPair: KeyPair,
creationDate: Date
): PGPKeyPair
fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory = fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory =
getPGPObjectFactory(bytes.inputStream()) getPGPObjectFactory(bytes.inputStream())

View File

@ -4,6 +4,9 @@
package org.pgpainless.implementation package org.pgpainless.implementation
import java.io.InputStream
import java.security.KeyPair
import java.util.*
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.* import org.bouncycastle.openpgp.operator.*
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
@ -24,37 +27,32 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.provider.ProviderFactory import org.pgpainless.provider.ProviderFactory
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import java.io.InputStream
import java.security.KeyPair
import java.util.*
class JceImplementationFactory : ImplementationFactory() { class JceImplementationFactory : ImplementationFactory() {
override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider = override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider =
JcaPGPDigestCalculatorProviderBuilder() JcaPGPDigestCalculatorProviderBuilder().setProvider(ProviderFactory.provider).build()
.setProvider(ProviderFactory.provider)
.build()
override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider = override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider =
JcaPGPContentVerifierBuilderProvider() JcaPGPContentVerifierBuilderProvider().setProvider(ProviderFactory.provider)
.setProvider(ProviderFactory.provider)
override val keyFingerprintCalculator: KeyFingerPrintCalculator = override val keyFingerprintCalculator: KeyFingerPrintCalculator =
JcaKeyFingerprintCalculator() JcaKeyFingerprintCalculator().setProvider(ProviderFactory.provider)
.setProvider(ProviderFactory.provider)
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm, override fun getPBESecretKeyEncryptor(
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
digestCalculator: PGPDigestCalculator, digestCalculator: PGPDigestCalculator,
passphrase: Passphrase): PBESecretKeyEncryptor = passphrase: Passphrase
): PBESecretKeyEncryptor =
JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator) JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(passphrase.getChars()) .build(passphrase.getChars())
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, override fun getPBESecretKeyEncryptor(
encryptionAlgorithm: SymmetricKeyAlgorithm,
hashAlgorithm: HashAlgorithm, hashAlgorithm: HashAlgorithm,
s2kCount: Int, s2kCount: Int,
passphrase: Passphrase): PBESecretKeyEncryptor = passphrase: Passphrase
): PBESecretKeyEncryptor =
JcePBESecretKeyEncryptorBuilder( JcePBESecretKeyEncryptorBuilder(
encryptionAlgorithm.algorithmId, encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
getPGPDigestCalculator(hashAlgorithm),
s2kCount)
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(passphrase.getChars()) .build(passphrase.getChars())
@ -63,7 +61,10 @@ class JceImplementationFactory : ImplementationFactory() {
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(passphrase.getChars()) .build(passphrase.getChars())
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder = override fun getPGPContentSignerBuilder(
keyAlgorithm: Int,
hashAlgorithm: Int
): PGPContentSignerBuilder =
JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm) JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
@ -72,30 +73,39 @@ class JceImplementationFactory : ImplementationFactory() {
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(passphrase.getChars()) .build(passphrase.getChars())
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory = override fun getPublicKeyDataDecryptorFactory(
privateKey: PGPPrivateKey
): PublicKeyDataDecryptorFactory =
JcePublicKeyDataDecryptorFactoryBuilder() JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(privateKey) .build(privateKey)
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory = override fun getSessionKeyDataDecryptorFactory(
sessionKey: PGPSessionKey
): SessionKeyDataDecryptorFactory =
JceSessionKeyDataDecryptorFactoryBuilder() JceSessionKeyDataDecryptorFactoryBuilder()
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
.build(sessionKey) .build(sessionKey)
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator = override fun getPublicKeyKeyEncryptionMethodGenerator(
JcePublicKeyKeyEncryptionMethodGenerator(key) key: PGPPublicKey
.setProvider(ProviderFactory.provider) ): PublicKeyKeyEncryptionMethodGenerator =
JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(ProviderFactory.provider)
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator = override fun getPBEKeyEncryptionMethodGenerator(
passphrase: Passphrase
): PBEKeyEncryptionMethodGenerator =
JcePBEKeyEncryptionMethodGenerator(passphrase.getChars()) JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
.setProvider(ProviderFactory.provider) .setProvider(ProviderFactory.provider)
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder = override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm) JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm).setProvider(ProviderFactory.provider)
.setProvider(ProviderFactory.provider)
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair = override fun getPGPKeyPair(
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate) publicKeyAlgorithm: PublicKeyAlgorithm,
keyPair: KeyPair,
creationDate: Date
): PGPKeyPair = JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate)
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
PGPObjectFactory(inputStream, keyFingerprintCalculator) PGPObjectFactory(inputStream, keyFingerprintCalculator)

View File

@ -4,16 +4,13 @@
package org.pgpainless.key package org.pgpainless.key
import java.nio.charset.Charset
import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.util.encoders.Hex import org.bouncycastle.util.encoders.Hex
import java.nio.charset.Charset
/** /** Abstract super class of different version OpenPGP fingerprints. */
* Abstract super class of different version OpenPGP fingerprints.
*
*/
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> { abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
val fingerprint: String val fingerprint: String
val bytes: ByteArray val bytes: ByteArray
@ -26,39 +23,41 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
abstract fun getVersion(): Int abstract fun getVersion(): Int
/** /**
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. * Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. This
* This method can be implemented for V4 and V5 fingerprints. * method can be implemented for V4 and V5 fingerprints. V3 key-IDs cannot be derived from the
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated. * fingerprint, but we don't care, since V3 is deprecated.
* *
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
* @return key id * @return key id
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2"> RFC-4880 §12.2: Key IDs and
* Fingerprints</a>
*/ */
abstract val keyId: Long abstract val keyId: Long
constructor(fingerprint: String) { constructor(fingerprint: String) {
val prep = fingerprint.replace(" ", "").trim().uppercase() val prep = fingerprint.replace(" ", "").trim().uppercase()
if (!isValid(prep)) { if (!isValid(prep)) {
throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.") throw IllegalArgumentException(
"Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
} }
this.fingerprint = prep this.fingerprint = prep
this.bytes = Hex.decode(prep) this.bytes = Hex.decode(prep)
} }
constructor(bytes: ByteArray): this(Hex.toHexString(bytes)) constructor(bytes: ByteArray) : this(Hex.toHexString(bytes))
constructor(key: PGPPublicKey): this(key.fingerprint) { constructor(key: PGPPublicKey) : this(key.fingerprint) {
if (key.version != getVersion()) { if (key.version != getVersion()) {
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.") throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
} }
} }
constructor(key: PGPSecretKey): this(key.publicKey) constructor(key: PGPSecretKey) : this(key.publicKey)
constructor(keys: PGPKeyRing): this(keys.publicKey) constructor(keys: PGPKeyRing) : this(keys.publicKey)
/** /**
* Check, whether the fingerprint consists of 40 valid hexadecimal characters. * Check, whether the fingerprint consists of 40 valid hexadecimal characters.
*
* @param fp fingerprint to check. * @param fp fingerprint to check.
* @return true if fingerprint is valid. * @return true if fingerprint is valid.
*/ */
@ -69,7 +68,9 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
override fun get(index: Int) = fingerprint.get(index) override fun get(index: Int) = fingerprint.get(index)
override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex) override fun subSequence(startIndex: Int, endIndex: Int) =
fingerprint.subSequence(startIndex, endIndex)
override fun compareTo(other: OpenPgpFingerprint): Int { override fun compareTo(other: OpenPgpFingerprint): Int {
return fingerprint.compareTo(other.fingerprint) return fingerprint.compareTo(other.fingerprint)
} }
@ -87,49 +88,49 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
abstract fun prettyPrint(): String abstract fun prettyPrint(): String
companion object { companion object {
@JvmStatic @JvmStatic val utf8: Charset = Charset.forName("UTF-8")
val utf8: Charset = Charset.forName("UTF-8")
/** /**
* Return the fingerprint of the given key. * Return the fingerprint of the given key. This method automatically matches key versions
* This method automatically matches key versions to fingerprint implementations. * to fingerprint implementations.
*
* @param key key
* @return fingerprint
*/
@JvmStatic fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
/**
* Return the fingerprint of the given key. This method automatically matches key versions
* to fingerprint implementations.
* *
* @param key key * @param key key
* @return fingerprint * @return fingerprint
*/ */
@JvmStatic @JvmStatic
fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey) fun of(key: PGPPublicKey): OpenPgpFingerprint =
when (key.version) {
/**
* Return the fingerprint of the given key.
* This method automatically matches key versions to fingerprint implementations.
*
* @param key key
* @return fingerprint
*/
@JvmStatic
fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) {
4 -> OpenPgpV4Fingerprint(key) 4 -> OpenPgpV4Fingerprint(key)
5 -> OpenPgpV5Fingerprint(key) 5 -> OpenPgpV5Fingerprint(key)
6 -> OpenPgpV6Fingerprint(key) 6 -> OpenPgpV6Fingerprint(key)
else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.") else ->
throw IllegalArgumentException(
"OpenPGP keys of version ${key.version} are not supported.")
} }
/** /**
* Return the fingerprint of the primary key of the given key ring. * Return the fingerprint of the primary key of the given key ring. This method
* This method automatically matches key versions to fingerprint implementations. * automatically matches key versions to fingerprint implementations.
* *
* @param ring key ring * @param ring key ring
* @return fingerprint * @return fingerprint
*/ */
@JvmStatic @JvmStatic fun of(keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
/** /**
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. * Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. If the
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint. * trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended * fingerprint. In this case, we return a {@link _64DigitFingerprint}. Since this is
* to know the version of the key beforehand. * ambiguous, it is generally recommended to know the version of the key beforehand.
* *
* @param fingerprint fingerprint * @param fingerprint fingerprint
* @return parsed fingerprint * @return parsed fingerprint
@ -146,7 +147,8 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
// Might be v5 or v6 :/ // Might be v5 or v6 :/
return _64DigitFingerprint(prep) return _64DigitFingerprint(prep)
} }
throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.") throw IllegalArgumentException(
"Fingerprint does not appear to match any known fingerprint pattern.")
} }
/** /**

View File

@ -4,22 +4,26 @@
package org.pgpainless.key package org.pgpainless.key
import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.util.encoders.Hex
import java.net.URI import java.net.URI
import java.nio.Buffer import java.nio.Buffer
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.charset.Charset import java.nio.charset.Charset
import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.util.encoders.Hex
class OpenPgpV4Fingerprint: OpenPgpFingerprint { class OpenPgpV4Fingerprint : OpenPgpFingerprint {
constructor(fingerprint: String): super(fingerprint) constructor(fingerprint: String) : super(fingerprint)
constructor(bytes: ByteArray): super(bytes)
constructor(key: PGPPublicKey): super(key) constructor(bytes: ByteArray) : super(bytes)
constructor(key: PGPSecretKey): super(key)
constructor(keys: PGPKeyRing): super(keys) constructor(key: PGPPublicKey) : super(key)
constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
override fun getVersion() = 4 override fun getVersion() = 4
@ -47,7 +51,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
append(fingerprint, i * 4, (i + 1) * 4).append(' ') append(fingerprint, i * 4, (i + 1) * 4).append(' ')
} }
append(' ') append(' ')
for (i in 5 .. 8) { for (i in 5..8) {
append(fingerprint, i * 4, (i + 1) * 4).append(' ') append(fingerprint, i * 4, (i + 1) * 4).append(' ')
} }
append(fingerprint, 36, 40) append(fingerprint, 36, 40)
@ -55,8 +59,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
} }
companion object { companion object {
@JvmStatic @JvmStatic val SCHEME = "openpgp4fpr"
val SCHEME = "openpgp4fpr"
@JvmStatic @JvmStatic
fun fromUri(uri: URI): OpenPgpV4Fingerprint { fun fromUri(uri: URI): OpenPgpV4Fingerprint {

View File

@ -8,16 +8,18 @@ import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
/** /** This class represents a hex encoded uppercase OpenPGP v5 fingerprint. */
* This class represents a hex encoded uppercase OpenPGP v5 fingerprint. class OpenPgpV5Fingerprint : _64DigitFingerprint {
*/
class OpenPgpV5Fingerprint: _64DigitFingerprint {
constructor(fingerprint: String): super(fingerprint) constructor(fingerprint: String) : super(fingerprint)
constructor(key: PGPPublicKey): super(key)
constructor(key: PGPSecretKey): super(key) constructor(key: PGPPublicKey) : super(key)
constructor(keys: PGPKeyRing): super(keys)
constructor(bytes: ByteArray): super(bytes) constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
constructor(bytes: ByteArray) : super(bytes)
override fun getVersion(): Int { override fun getVersion(): Int {
return 5 return 5

View File

@ -8,16 +8,18 @@ import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
/** /** This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. */
* This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. class OpenPgpV6Fingerprint : _64DigitFingerprint {
*/
class OpenPgpV6Fingerprint: _64DigitFingerprint {
constructor(fingerprint: String): super(fingerprint) constructor(fingerprint: String) : super(fingerprint)
constructor(key: PGPPublicKey): super(key)
constructor(key: PGPSecretKey): super(key) constructor(key: PGPPublicKey) : super(key)
constructor(keys: PGPKeyRing): super(keys)
constructor(bytes: ByteArray): super(bytes) constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
constructor(bytes: ByteArray) : super(bytes)
override fun getVersion(): Int { override fun getVersion(): Int {
return 6 return 6

View File

@ -14,16 +14,29 @@ import org.bouncycastle.openpgp.PGPPublicKey
*/ */
class SubkeyIdentifier( class SubkeyIdentifier(
val primaryKeyFingerprint: OpenPgpFingerprint, val primaryKeyFingerprint: OpenPgpFingerprint,
val subkeyFingerprint: OpenPgpFingerprint) { val subkeyFingerprint: OpenPgpFingerprint
) {
constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint) constructor(fingerprint: OpenPgpFingerprint) : this(fingerprint, fingerprint)
constructor(keys: PGPKeyRing): this(keys.publicKey)
constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key)) constructor(keys: PGPKeyRing) : this(keys.publicKey)
constructor(keys: PGPKeyRing, keyId: Long): this(
constructor(key: PGPPublicKey) : this(OpenPgpFingerprint.of(key))
constructor(
keys: PGPKeyRing,
keyId: Long
) : this(
OpenPgpFingerprint.of(keys.publicKey), OpenPgpFingerprint.of(keys.publicKey),
OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?: OpenPgpFingerprint.of(
throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}"))) keys.getPublicKey(keyId)
constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint) ?: throw NoSuchElementException(
"OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}")))
constructor(
keys: PGPKeyRing,
subkeyFingerprint: OpenPgpFingerprint
) : this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
val keyId = subkeyFingerprint.keyId val keyId = subkeyFingerprint.keyId
val fingerprint = subkeyFingerprint val fingerprint = subkeyFingerprint
@ -47,7 +60,8 @@ class SubkeyIdentifier(
return false return false
} }
return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint return primaryKeyFingerprint == other.primaryKeyFingerprint &&
subkeyFingerprint == other.subkeyFingerprint
} }
override fun hashCode(): Int { override fun hashCode(): Int {

View File

@ -2,47 +2,47 @@
// //
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key; package org.pgpainless.key
import java.nio.Buffer; import java.nio.Buffer
import java.nio.ByteBuffer; import java.nio.ByteBuffer
import java.nio.charset.Charset; import java.nio.charset.Charset
import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.util.encoders.Hex
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
/** /**
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. Since both
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the * fingerprints use the same format, this class is used when parsing the fingerprint without knowing
* key version. * the key version.
*/ */
open class _64DigitFingerprint: OpenPgpFingerprint { open class _64DigitFingerprint : OpenPgpFingerprint {
/** /**
* Create an {@link _64DigitFingerprint}. * Create an {@link _64DigitFingerprint}.
* *
* @param fingerprint uppercase hexadecimal fingerprint of length 64 * @param fingerprint uppercase hexadecimal fingerprint of length 64
*/ */
constructor(fingerprint: String): super(fingerprint) constructor(fingerprint: String) : super(fingerprint)
constructor(bytes: ByteArray): super(bytes)
constructor(key: PGPPublicKey): super(key) constructor(bytes: ByteArray) : super(bytes)
constructor(key: PGPSecretKey): super(key)
constructor(keys: PGPKeyRing): super(keys) constructor(key: PGPPublicKey) : super(key)
constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
override val keyId: Long override val keyId: Long
get() { get() {
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8"))) val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
val buf = ByteBuffer.wrap(bytes); val buf = ByteBuffer.wrap(bytes)
// The key id is the left-most 8 bytes (conveniently a long). // The key id is the left-most 8 bytes (conveniently a long).
// We have to cast here in order to be compatible with java 8 // We have to cast here in order to be compatible with java 8
// https://github.com/eclipse/jetty.project/issues/3244 // https://github.com/eclipse/jetty.project/issues/3244
(buf as Buffer).position(0); (buf as Buffer).position(0)
return buf.getLong() return buf.getLong()
} }
@ -62,13 +62,13 @@ open class _64DigitFingerprint: OpenPgpFingerprint {
override fun prettyPrint(): String { override fun prettyPrint(): String {
return buildString { return buildString {
for (i in 0 until 4) { for (i in 0 until 4) {
append(fingerprint, i * 8, (i + 1) * 8).append(' '); append(fingerprint, i * 8, (i + 1) * 8).append(' ')
} }
append(' '); append(' ')
for (i in 4 until 7) { for (i in 4 until 7) {
append(fingerprint, i * 8, (i + 1) * 8).append(' '); append(fingerprint, i * 8, (i + 1) * 8).append(' ')
} }
append(fingerprint, 56, 64); append(fingerprint, 56, 64)
} }
} }
} }

View File

@ -4,6 +4,7 @@
package org.pgpainless.key.certification package org.pgpainless.key.certification
import java.util.*
import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
@ -22,21 +23,20 @@ import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
import org.pgpainless.signature.subpackets.CertificationSubpackets import org.pgpainless.signature.subpackets.CertificationSubpackets
import java.util.*
/** /**
* API for creating certifications and delegations (Signatures) on keys. * API for creating certifications and delegations (Signatures) on keys. This API can be used to
* This API can be used to sign another persons OpenPGP key. * sign another persons OpenPGP key.
* *
* A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs * A certification over a user-id is thereby used to attest, that the user believes that the user-id
* to the owner of the certificate. * really belongs to the owner of the certificate. A delegation over a key can be used to delegate
* A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer. * trust by marking the certificate as a trusted introducer.
*/ */
class CertifyCertificate { class CertifyCertificate {
/** /**
* Create a certification over a User-Id. * Create a certification over a User-Id. By default, this method will use
* By default, this method will use [CertificationType.GENERIC] to create the signature. * [CertificationType.GENERIC] to create the signature.
* *
* @param userId user-id to certify * @param userId user-id to certify
* @param certificate certificate * @param certificate certificate
@ -53,13 +53,16 @@ class CertifyCertificate {
* @param certificationType type of signature * @param certificationType type of signature
* @return API * @return API
*/ */
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) = fun userIdOnCertificate(
CertificationOnUserId(userId, certificate, certificationType) userId: String,
certificate: PGPPublicKeyRing,
certificationType: CertificationType
) = CertificationOnUserId(userId, certificate, certificationType)
/** /**
* Create a delegation (direct key signature) over a certificate. * Create a delegation (direct key signature) over a certificate. This can be used to mark a
* This can be used to mark a certificate as a trusted introducer * certificate as a trusted introducer (see [certificate] method with [Trustworthiness]
* (see [certificate] method with [Trustworthiness] argument). * argument).
* *
* @param certificate certificate * @param certificate certificate
* @return API * @return API
@ -68,9 +71,9 @@ class CertifyCertificate {
certificate(certificate, null) certificate(certificate, null)
/** /**
* Create a delegation (direct key signature) containing a [org.bouncycastle.bcpg.sig.TrustSignature] packet * Create a delegation (direct key signature) containing a
* over a certificate. * [org.bouncycastle.bcpg.sig.TrustSignature] packet over a certificate. This can be used to
* This can be used to mark a certificate as a trusted introducer. * mark a certificate as a trusted introducer.
* *
* @param certificate certificate * @param certificate certificate
* @param trustworthiness trustworthiness of the certificate * @param trustworthiness trustworthiness of the certificate
@ -82,7 +85,8 @@ class CertifyCertificate {
class CertificationOnUserId( class CertificationOnUserId(
val userId: String, val userId: String,
val certificate: PGPPublicKeyRing, val certificate: PGPPublicKeyRing,
val certificationType: CertificationType) { val certificationType: CertificationType
) {
/** /**
* Create the certification using the given key. * Create the certification using the given key.
@ -92,11 +96,14 @@ class CertifyCertificate {
* @return API * @return API
* @throws PGPException in case of an OpenPGP related error * @throws PGPException in case of an OpenPGP related error
*/ */
fun withKey(certificationKey: PGPSecretKeyRing, fun withKey(
protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets { certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector
): CertificationOnUserIdWithSubpackets {
val secretKey = getCertifyingSecretKey(certificationKey) val secretKey = getCertifyingSecretKey(certificationKey)
val sigBuilder = ThirdPartyCertificationSignatureBuilder( val sigBuilder =
ThirdPartyCertificationSignatureBuilder(
certificationType.asSignatureType(), secretKey, protector) certificationType.asSignatureType(), secretKey, protector)
return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder) return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder)
@ -116,7 +123,9 @@ class CertifyCertificate {
* @return result * @return result
* @throws PGPException in case of an OpenPGP related error * @throws PGPException in case of an OpenPGP related error
*/ */
fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult { fun buildWithSubpackets(
subpacketCallback: CertificationSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketCallback) sigBuilder.applyCallback(subpacketCallback)
return build() return build()
} }
@ -129,14 +138,16 @@ class CertifyCertificate {
*/ */
fun build(): CertificationResult { fun build(): CertificationResult {
val signature = sigBuilder.build(certificate, userId) val signature = sigBuilder.build(certificate, userId)
val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature) val certifiedCertificate =
KeyRingUtils.injectCertification(certificate, userId, signature)
return CertificationResult(certifiedCertificate, signature) return CertificationResult(certifiedCertificate, signature)
} }
} }
class DelegationOnCertificate( class DelegationOnCertificate(
val certificate: PGPPublicKeyRing, val certificate: PGPPublicKeyRing,
val trustworthiness: Trustworthiness?) { val trustworthiness: Trustworthiness?
) {
/** /**
* Build the delegation using the given certification key. * Build the delegation using the given certification key.
@ -146,12 +157,15 @@ class CertifyCertificate {
* @return API * @return API
* @throws PGPException in case of an OpenPGP related error * @throws PGPException in case of an OpenPGP related error
*/ */
fun withKey(certificationKey: PGPSecretKeyRing, fun withKey(
protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets { certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector
): DelegationOnCertificateWithSubpackets {
val secretKey = getCertifyingSecretKey(certificationKey) val secretKey = getCertifyingSecretKey(certificationKey)
val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector) val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector)
if (trustworthiness != null) { if (trustworthiness != null) {
sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount) sigBuilder.hashedSubpackets.setTrust(
true, trustworthiness.depth, trustworthiness.amount)
} }
return DelegationOnCertificateWithSubpackets(certificate, sigBuilder) return DelegationOnCertificateWithSubpackets(certificate, sigBuilder)
} }
@ -159,7 +173,8 @@ class CertifyCertificate {
class DelegationOnCertificateWithSubpackets( class DelegationOnCertificateWithSubpackets(
val certificate: PGPPublicKeyRing, val certificate: PGPPublicKeyRing,
val sigBuilder: ThirdPartyDirectKeySignatureBuilder) { val sigBuilder: ThirdPartyDirectKeySignatureBuilder
) {
/** /**
* Apply the given signature subpackets and build the delegation signature. * Apply the given signature subpackets and build the delegation signature.
@ -168,7 +183,9 @@ class CertifyCertificate {
* @return result * @return result
* @throws PGPException in case of an OpenPGP related error * @throws PGPException in case of an OpenPGP related error
*/ */
fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult { fun buildWithSubpackets(
subpacketsCallback: CertificationSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketsCallback) sigBuilder.applyCallback(subpacketsCallback)
return build() return build()
} }
@ -182,7 +199,8 @@ class CertifyCertificate {
fun build(): CertificationResult { fun build(): CertificationResult {
val delegatedKey = certificate.publicKey val delegatedKey = certificate.publicKey
val delegation = sigBuilder.build(delegatedKey) val delegation = sigBuilder.build(delegatedKey)
val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation) val delegatedCertificate =
KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
return CertificationResult(delegatedCertificate, delegation) return CertificationResult(delegatedCertificate, delegation)
} }
} }
@ -195,7 +213,8 @@ class CertifyCertificate {
*/ */
data class CertificationResult( data class CertificationResult(
val certifiedCertificate: PGPPublicKeyRing, val certifiedCertificate: PGPPublicKeyRing,
val certification: PGPSignature) val certification: PGPSignature
)
companion object { companion object {
@JvmStatic @JvmStatic
@ -205,9 +224,7 @@ class CertifyCertificate {
val fingerprint = info.fingerprint val fingerprint = info.fingerprint
val certificationPubKey = info.getPublicKey(fingerprint) val certificationPubKey = info.getPublicKey(fingerprint)
requireNotNull(certificationPubKey) { requireNotNull(certificationPubKey) { "Primary key cannot be null." }
"Primary key cannot be null."
}
if (!info.isKeyValidlyBound(certificationPubKey.keyID)) { if (!info.isKeyValidlyBound(certificationPubKey.keyID)) {
throw RevokedKeyException(fingerprint) throw RevokedKeyException(fingerprint)
} }

View File

@ -4,32 +4,38 @@
package org.pgpainless.key.collection package org.pgpainless.key.collection
import java.io.InputStream
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmorUtils import org.pgpainless.util.ArmorUtils
import java.io.InputStream
/** /**
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was inspired by * This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was
* [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection]. * inspired by [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
*/ */
class PGPKeyRingCollection( class PGPKeyRingCollection(
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection, val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
) { ) {
constructor(encoding: ByteArray, isSilent: Boolean): this(encoding.inputStream(), isSilent) constructor(encoding: ByteArray, isSilent: Boolean) : this(encoding.inputStream(), isSilent)
constructor(inputStream: InputStream, isSilent: Boolean): this(parse(inputStream, isSilent)) constructor(inputStream: InputStream, isSilent: Boolean) : this(parse(inputStream, isSilent))
constructor(collection: Collection<PGPKeyRing>, isSilent: Boolean): this(segment(collection, isSilent)) constructor(
collection: Collection<PGPKeyRing>,
isSilent: Boolean
) : this(segment(collection, isSilent))
private constructor(arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>): this(arguments.first, arguments.second) private constructor(
arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>
) : this(arguments.first, arguments.second)
/** /**
* The number of rings in this collection. * The number of rings in this collection.
* *
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection * @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this
* collection
*/ */
val size: Int val size: Int
get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size() get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size()
@ -41,11 +47,16 @@ class PGPKeyRingCollection(
companion object { companion object {
@JvmStatic @JvmStatic
private fun parse(inputStream: InputStream, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> { private fun parse(
inputStream: InputStream,
isSilent: Boolean
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
val secretKeyRings = mutableListOf<PGPSecretKeyRing>() val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
val certificates = mutableListOf<PGPPublicKeyRing>() val certificates = mutableListOf<PGPPublicKeyRing>()
// Double getDecoderStream because of #96 // Double getDecoderStream because of #96
val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream)) val objectFactory =
ImplementationFactory.getInstance()
.getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream))
for (obj in objectFactory) { for (obj in objectFactory) {
if (obj == null) { if (obj == null) {
@ -68,16 +79,21 @@ class PGPKeyRingCollection(
} }
if (!isSilent) { if (!isSilent) {
throw PGPException("${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + throw PGPException(
"${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
" or ${PGPPublicKeyRing::class.java.simpleName} expected") " or ${PGPPublicKeyRing::class.java.simpleName} expected")
} }
} }
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) return PGPSecretKeyRingCollection(secretKeyRings) to
PGPPublicKeyRingCollection(certificates)
} }
@JvmStatic @JvmStatic
private fun segment(collection: Collection<PGPKeyRing>, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> { private fun segment(
collection: Collection<PGPKeyRing>,
isSilent: Boolean
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
val secretKeyRings = mutableListOf<PGPSecretKeyRing>() val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
val certificates = mutableListOf<PGPPublicKeyRing>() val certificates = mutableListOf<PGPPublicKeyRing>()
@ -87,12 +103,14 @@ class PGPKeyRingCollection(
} else if (keyRing is PGPPublicKeyRing) { } else if (keyRing is PGPPublicKeyRing) {
certificates.add(keyRing) certificates.add(keyRing)
} else if (!isSilent) { } else if (!isSilent) {
throw PGPException("${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" + throw PGPException(
"${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
" or ${PGPPublicKeyRing::class.java.simpleName} expected") " or ${PGPPublicKeyRing::class.java.simpleName} expected")
} }
} }
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates) return PGPSecretKeyRingCollection(secretKeyRings) to
PGPPublicKeyRingCollection(certificates)
} }
} }
} }

View File

@ -4,6 +4,9 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import java.io.IOException
import java.security.KeyPairGenerator
import java.util.*
import org.bouncycastle.extensions.unlock import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
@ -21,10 +24,6 @@ import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import java.io.IOException
import java.security.KeyPairGenerator
import java.util.*
class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> { class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
@ -49,17 +48,17 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
userIds[userId.toString().trim()] = null userIds[userId.toString().trim()] = null
} }
override fun addUserId(userId: ByteArray): KeyRingBuilder = addUserId(Strings.fromUTF8ByteArray(userId)) override fun addUserId(userId: ByteArray): KeyRingBuilder =
addUserId(Strings.fromUTF8ByteArray(userId))
override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply { override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
if (expirationDate == null) { if (expirationDate == null) {
this.expirationDate = null this.expirationDate = null
return@apply return@apply
} }
this.expirationDate = expirationDate.let { this.expirationDate =
require(Date() < expirationDate) { expirationDate.let {
"Expiration date must be in the future." require(Date() < expirationDate) { "Expiration date must be in the future." }
}
expirationDate expirationDate
} }
} }
@ -85,17 +84,14 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify
override fun build(): PGPSecretKeyRing { override fun build(): PGPSecretKeyRing {
val keyFingerprintCalculator = ImplementationFactory.getInstance() val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator
.v4FingerprintCalculator
val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator) val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
val secretKeyDecryptor = buildSecretKeyDecryptor() val secretKeyDecryptor = buildSecretKeyDecryptor()
passphrase.clear() // Passphrase was used above, so we can get rid of it passphrase.clear() // Passphrase was used above, so we can get rid of it
// generate primary key // generate primary key
requireNotNull(primaryKeySpec) { requireNotNull(primaryKeySpec) { "Primary Key spec required." }
"Primary Key spec required."
}
val certKey = generateKeyPair(primaryKeySpec!!) val certKey = generateKeyPair(primaryKeySpec!!)
val signer = buildContentSigner(certKey) val signer = buildContentSigner(certKey)
val signatureGenerator = PGPSignatureGenerator(signer) val signatureGenerator = PGPSignatureGenerator(signer)
@ -110,13 +106,25 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
val generator = PGPSignatureSubpacketGenerator() val generator = PGPSignatureSubpacketGenerator()
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator) SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
val hashedSubPackets = generator.generate() val hashedSubPackets = generator.generate()
val ringGenerator = if (userIds.isEmpty()) { val ringGenerator =
PGPKeyRingGenerator(certKey, keyFingerprintCalculator, hashedSubPackets, null, signer, if (userIds.isEmpty()) {
PGPKeyRingGenerator(
certKey,
keyFingerprintCalculator,
hashedSubPackets,
null,
signer,
secretKeyEncryptor) secretKeyEncryptor)
} else { } else {
userIds.keys.first().let { primaryUserId -> userIds.keys.first().let { primaryUserId ->
PGPKeyRingGenerator(SignatureType.POSITIVE_CERTIFICATION.code, certKey, primaryUserId, PGPKeyRingGenerator(
keyFingerprintCalculator, hashedSubPackets, null, signer, SignatureType.POSITIVE_CERTIFICATION.code,
certKey,
primaryUserId,
keyFingerprintCalculator,
hashedSubPackets,
null,
signer,
secretKeyEncryptor) secretKeyEncryptor)
} }
} }
@ -138,20 +146,26 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
val additionalUserId = userIdIterator.next() val additionalUserId = userIdIterator.next()
val userIdString = additionalUserId.key val userIdString = additionalUserId.key
val callback = additionalUserId.value val callback = additionalUserId.value
val subpackets = if (callback == null) { val subpackets =
if (callback == null) {
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) } hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
} else { } else {
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) } SignatureSubpackets.createHashedSubpackets(primaryPubKey).also {
callback.modifyHashedSubpackets(it)
}
} }
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey) signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
signatureGenerator.setHashedSubpackets( signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets))
SignatureSubpacketsHelper.toVector(subpackets)) val additionalUserIdSignature =
val additionalUserIdSignature = signatureGenerator.generateCertification(userIdString, primaryPubKey) signatureGenerator.generateCertification(userIdString, primaryPubKey)
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, userIdString, additionalUserIdSignature) primaryPubKey =
PGPPublicKey.addCertification(
primaryPubKey, userIdString, additionalUserIdSignature)
} }
// Reassemble secret key ring with modified primary key // Reassemble secret key ring with modified primary key
val primarySecretKey = PGPSecretKey( val primarySecretKey =
PGPSecretKey(
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor) privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
val secretKeyList = mutableListOf(primarySecretKey) val secretKeyList = mutableListOf(primarySecretKey)
while (secretKeys.hasNext()) { while (secretKeys.hasNext()) {
@ -168,16 +182,24 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
} else { } else {
var hashedSubpackets = subKeySpec.subpackets var hashedSubpackets = subKeySpec.subpackets
try { try {
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets) hashedSubpackets =
addPrimaryKeyBindingSignatureIfNecessary(
primaryKey, subKey, hashedSubpackets)
} catch (e: IOException) { } catch (e: IOException) {
throw PGPException("Exception while adding primary key binding signature to signing subkey.", e) throw PGPException(
"Exception while adding primary key binding signature to signing subkey.",
e)
} }
ringGenerator.addSubKey(subKey, hashedSubpackets, null) ringGenerator.addSubKey(subKey, hashedSubpackets, null)
} }
} }
} }
private fun addPrimaryKeyBindingSignatureIfNecessary(primaryKey: PGPKeyPair, subKey: PGPKeyPair, hashedSubpackets: PGPSignatureSubpacketVector): PGPSignatureSubpacketVector { private fun addPrimaryKeyBindingSignatureIfNecessary(
primaryKey: PGPKeyPair,
subKey: PGPKeyPair,
hashedSubpackets: PGPSignatureSubpacketVector
): PGPSignatureSubpacketVector {
val keyFlagMask = hashedSubpackets.keyFlags val keyFlagMask = hashedSubpackets.keyFlags
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) { !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
@ -186,7 +208,8 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey)) val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey) bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
val primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey) val primaryKeyBindingSig =
bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets) val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig) subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
return subpacketGenerator.generate() return subpacketGenerator.generate()
@ -194,25 +217,29 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder { private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm
return ImplementationFactory.getInstance().getPGPContentSignerBuilder( return ImplementationFactory.getInstance()
certKey.publicKey.algorithm, hashAlgorithm.algorithmId) .getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
} }
private fun buildSecretKeyEncryptor(keyFingerprintCalculator: PGPDigestCalculator): PBESecretKeyEncryptor? { private fun buildSecretKeyEncryptor(
val keyEncryptionAlgorithm = PGPainless.getPolicy().symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm keyFingerprintCalculator: PGPDigestCalculator
check(passphrase.isValid) { ): PBESecretKeyEncryptor? {
"Passphrase was cleared." val keyEncryptionAlgorithm =
} PGPainless.getPolicy()
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance() .symmetricKeyEncryptionAlgorithmPolicy
.getPBESecretKeyEncryptor(keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase) .defaultSymmetricKeyAlgorithm
check(passphrase.isValid) { "Passphrase was cleared." }
return if (passphrase.isEmpty) null
else
ImplementationFactory.getInstance()
.getPBESecretKeyEncryptor(
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
} }
private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? { private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
check(passphrase.isValid) { check(passphrase.isValid) { "Passphrase was cleared." }
"Passphrase was cleared." return if (passphrase.isEmpty) null
} else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase)
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance()
.getPBESecretKeyDecryptor(passphrase)
} }
companion object { companion object {
@ -222,7 +249,8 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
fun generateKeyPair(spec: KeySpec): PGPKeyPair { fun generateKeyPair(spec: KeySpec): PGPKeyPair {
spec.keyType.let { type -> spec.keyType.let { type ->
// Create raw Key Pair // Create raw Key Pair
val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.provider) val keyPair =
KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
.also { it.initialize(type.algorithmSpec) } .also { it.initialize(type.algorithmSpec) }
.generateKeyPair() .generateKeyPair()
@ -233,5 +261,4 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
} }
} }
} }
} }

View File

@ -4,12 +4,12 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.pgpainless.util.Passphrase
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.pgpainless.util.Passphrase
interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> { interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
@ -29,6 +29,9 @@ interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
fun setPassphrase(passphrase: Passphrase): B fun setPassphrase(passphrase: Passphrase): B
@Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class) @Throws(
NoSuchAlgorithmException::class,
PGPException::class,
InvalidAlgorithmParameterException::class)
fun build(): PGPSecretKeyRing fun build(): PGPSecretKeyRing
} }

View File

@ -17,8 +17,8 @@ import org.pgpainless.util.Passphrase
class KeyRingTemplates { class KeyRingTemplates {
/** /**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. * dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
* *
* @param userId userId or null * @param userId userId or null
* @param length length of the RSA keys * @param length length of the RSA keys
@ -26,32 +26,36 @@ class KeyRingTemplates {
* @return key * @return key
*/ */
@JvmOverloads @JvmOverloads
fun rsaKeyRing(userId: CharSequence?, fun rsaKeyRing(
userId: CharSequence?,
length: RsaLength, length: RsaLength,
passphrase: Passphrase = Passphrase.emptyPassphrase() passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing = buildKeyRing().apply { ): PGPSecretKeyRing =
buildKeyRing()
.apply {
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) addSubkey(
getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
setPassphrase(passphrase) setPassphrase(passphrase)
if (userId != null) { if (userId != null) {
addUserId(userId) addUserId(userId)
} }
}.build() }
.build()
/** /**
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. * dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
* *
* @param userId userId or null * @param userId userId or null
* @param length length of the RSA keys * @param length length of the RSA keys
* @param password passphrase to encrypt the key with. Can be null or blank for unencrypted keys. * @param password passphrase to encrypt the key with. Can be null or blank for unencrypted
* keys.
* @return key * @return key
*/ */
fun rsaKeyRing(userId: CharSequence?, fun rsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?): PGPSecretKeyRing =
length: RsaLength, password.let {
password: String?
): PGPSecretKeyRing = password.let {
if (it.isNullOrBlank()) { if (it.isNullOrBlank()) {
rsaKeyRing(userId, length, Passphrase.emptyPassphrase()) rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
} else { } else {
@ -60,41 +64,48 @@ class KeyRingTemplates {
} }
/** /**
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. * KeyPair consists of a single RSA master key which is used for signing, encryption and
* certification.
* *
* @param userId user id. * @param userId user id.
* @param length length in bits. * @param length length in bits.
* @param password Password of the key. Can be empty for unencrypted keys. * @param password Password of the key. Can be empty for unencrypted keys.
*
* @return {@link PGPSecretKeyRing} containing the KeyPair. * @return {@link PGPSecretKeyRing} containing the KeyPair.
*/ */
@JvmOverloads @JvmOverloads
fun simpleRsaKeyRing(userId: CharSequence?, fun simpleRsaKeyRing(
userId: CharSequence?,
length: RsaLength, length: RsaLength,
passphrase: Passphrase = Passphrase.emptyPassphrase() passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing = buildKeyRing().apply { ): PGPSecretKeyRing =
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) buildKeyRing()
.apply {
setPrimaryKey(
getBuilder(
KeyType.RSA(length),
KeyFlag.CERTIFY_OTHER,
KeyFlag.SIGN_DATA,
KeyFlag.ENCRYPT_COMMS))
setPassphrase(passphrase) setPassphrase(passphrase)
if (userId != null) { if (userId != null) {
addUserId(userId.toString()) addUserId(userId.toString())
} }
}.build() }
.build()
/** /**
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. * KeyPair consists of a single RSA master key which is used for signing, encryption and
* certification.
* *
* @param userId user id. * @param userId user id.
* @param length length in bits. * @param length length in bits.
* @param password Password of the key. Can be null or blank for unencrypted keys. * @param password Password of the key. Can be null or blank for unencrypted keys.
*
* @return {@link PGPSecretKeyRing} containing the KeyPair. * @return {@link PGPSecretKeyRing} containing the KeyPair.
*/ */
fun simpleRsaKeyRing(userId: CharSequence?, fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?) =
length: RsaLength, password.let {
password: String?
) = password.let {
if (it.isNullOrBlank()) { if (it.isNullOrBlank()) {
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()) simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
} else { } else {
@ -103,40 +114,49 @@ class KeyRingTemplates {
} }
/** /**
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
* The EdDSA primary key is used for signing messages and certifying the sub key. * EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
* The XDH subkey is used for encryption and decryption of messages. * used for encryption and decryption of messages.
* *
* @param userId user-id * @param userId user-id
* @param passphrase Password of the private key. Can be empty for an unencrypted key. * @param passphrase Password of the private key. Can be empty for an unencrypted key.
*
* @return {@link PGPSecretKeyRing} containing the key pairs. * @return {@link PGPSecretKeyRing} containing the key pairs.
*/ */
@JvmOverloads @JvmOverloads
fun simpleEcKeyRing(userId: CharSequence?, fun simpleEcKeyRing(
userId: CharSequence?,
passphrase: Passphrase = Passphrase.emptyPassphrase() passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing = buildKeyRing().apply { ): PGPSecretKeyRing =
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) buildKeyRing()
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) .apply {
setPrimaryKey(
getBuilder(
KeyType.EDDSA(EdDSACurve._Ed25519),
KeyFlag.CERTIFY_OTHER,
KeyFlag.SIGN_DATA))
addSubkey(
getBuilder(
KeyType.XDH(XDHSpec._X25519),
KeyFlag.ENCRYPT_STORAGE,
KeyFlag.ENCRYPT_COMMS))
setPassphrase(passphrase) setPassphrase(passphrase)
if (userId != null) { if (userId != null) {
addUserId(userId.toString()) addUserId(userId.toString())
} }
}.build() }
.build()
/** /**
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
* The EdDSA primary key is used for signing messages and certifying the sub key. * EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
* The XDH subkey is used for encryption and decryption of messages. * used for encryption and decryption of messages.
* *
* @param userId user-id * @param userId user-id
* @param passphrase Password of the private key. Can be null or blank for an unencrypted key. * @param passphrase Password of the private key. Can be null or blank for an unencrypted key.
*
* @return {@link PGPSecretKeyRing} containing the key pairs. * @return {@link PGPSecretKeyRing} containing the key pairs.
*/ */
fun simpleEcKeyRing(userId: CharSequence?, fun simpleEcKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
password: String? password.let {
): PGPSecretKeyRing = password.let {
if (it.isNullOrBlank()) { if (it.isNullOrBlank()) {
simpleEcKeyRing(userId, Passphrase.emptyPassphrase()) simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
} else { } else {
@ -145,37 +165,44 @@ class KeyRingTemplates {
} }
/** /**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. * certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
* *
* @param userId primary user id * @param userId primary user id
* @param passphrase passphrase for the private key. Can be empty for an unencrypted key. * @param passphrase passphrase for the private key. Can be empty for an unencrypted key.
* @return key ring * @return key ring
*/ */
@JvmOverloads @JvmOverloads
fun modernKeyRing(userId: CharSequence?, fun modernKeyRing(
userId: CharSequence?,
passphrase: Passphrase = Passphrase.emptyPassphrase() passphrase: Passphrase = Passphrase.emptyPassphrase()
): PGPSecretKeyRing = buildKeyRing().apply { ): PGPSecretKeyRing =
buildKeyRing()
.apply {
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) addSubkey(
getBuilder(
KeyType.XDH(XDHSpec._X25519),
KeyFlag.ENCRYPT_COMMS,
KeyFlag.ENCRYPT_STORAGE))
addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
setPassphrase(passphrase) setPassphrase(passphrase)
if (userId != null) { if (userId != null) {
addUserId(userId) addUserId(userId)
} }
}.build() }
.build()
/** /**
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify * Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key. * certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
* *
* @param userId primary user id * @param userId primary user id
* @param password passphrase for the private key. Can be null or blank for an unencrypted key. * @param password passphrase for the private key. Can be null or blank for an unencrypted key.
* @return key ring * @return key ring
*/ */
fun modernKeyRing(userId: CharSequence?, fun modernKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
password: String? password.let {
): PGPSecretKeyRing = password.let {
if (it.isNullOrBlank()) { if (it.isNullOrBlank()) {
modernKeyRing(userId, Passphrase.emptyPassphrase()) modernKeyRing(userId, Passphrase.emptyPassphrase())
} else { } else {

View File

@ -4,12 +4,12 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import java.util.*
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector import org.bouncycastle.openpgp.PGPSignatureSubpacketVector
import org.pgpainless.algorithm.KeyFlag import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import java.util.*
data class KeySpec( data class KeySpec(
val keyType: KeyType, val keyType: KeyType,

View File

@ -4,41 +4,47 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import java.util.*
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.algorithm.* import org.pgpainless.algorithm.*
import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpackets import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import java.util.*
class KeySpecBuilder constructor( class KeySpecBuilder
constructor(
private val type: KeyType, private val type: KeyType,
private val keyFlags: List<KeyFlag>, private val keyFlags: List<KeyFlag>,
) : KeySpecBuilderInterface { ) : KeySpecBuilderInterface {
private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets() private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets()
private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> = algorithmSuite.compressionAlgorithms private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> =
algorithmSuite.compressionAlgorithms
private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> = algorithmSuite.symmetricKeyAlgorithms private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> =
algorithmSuite.symmetricKeyAlgorithms
private var keyCreationDate = Date() private var keyCreationDate = Date()
constructor(type: KeyType, vararg keyFlags: KeyFlag): this(type, listOf(*keyFlags)) constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags))
init { init {
SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray()) SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray())
} }
override fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder = apply { override fun overridePreferredCompressionAlgorithms(
this.preferredCompressionAlgorithms = algorithms.toSet() vararg algorithms: CompressionAlgorithm
} ): KeySpecBuilder = apply { this.preferredCompressionAlgorithms = algorithms.toSet() }
override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = apply { override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder =
apply {
this.preferredHashAlgorithms = algorithms.toSet() this.preferredHashAlgorithms = algorithms.toSet()
} }
override fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder = apply { override fun overridePreferredSymmetricKeyAlgorithms(
vararg algorithms: SymmetricKeyAlgorithm
): KeySpecBuilder = apply {
require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) { require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) {
"NULL (unencrypted) is an invalid symmetric key algorithm preference." "NULL (unencrypted) is an invalid symmetric key algorithm preference."
} }
@ -50,14 +56,14 @@ class KeySpecBuilder constructor(
} }
override fun build(): KeySpec { override fun build(): KeySpec {
return hashedSubpackets.apply { return hashedSubpackets
.apply {
setKeyFlags(keyFlags) setKeyFlags(keyFlags)
setPreferredCompressionAlgorithms(preferredCompressionAlgorithms) setPreferredCompressionAlgorithms(preferredCompressionAlgorithms)
setPreferredHashAlgorithms(preferredHashAlgorithms) setPreferredHashAlgorithms(preferredHashAlgorithms)
setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms) setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms)
setFeatures(Feature.MODIFICATION_DETECTION) setFeatures(Feature.MODIFICATION_DETECTION)
}.let {
KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate)
} }
.let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) }
} }
} }

View File

@ -4,18 +4,22 @@
package org.pgpainless.key.generation package org.pgpainless.key.generation
import java.util.*
import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.HashAlgorithm import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import java.util.*
interface KeySpecBuilderInterface { interface KeySpecBuilderInterface {
fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder fun overridePreferredCompressionAlgorithms(
vararg algorithms: CompressionAlgorithm
): KeySpecBuilder
fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder
fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder fun overridePreferredSymmetricKeyAlgorithms(
vararg algorithms: SymmetricKeyAlgorithm
): KeySpecBuilder
fun setKeyCreationDate(creationDate: Date): KeySpecBuilder fun setKeyCreationDate(creationDate: Date): KeySpecBuilder

View File

@ -4,6 +4,7 @@
package org.pgpainless.key.generation.type package org.pgpainless.key.generation.type
import java.security.spec.AlgorithmParameterSpec
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.generation.type.ecc.EllipticCurve import org.pgpainless.key.generation.type.ecc.EllipticCurve
import org.pgpainless.key.generation.type.ecc.ecdh.ECDH import org.pgpainless.key.generation.type.ecc.ecdh.ECDH
@ -14,7 +15,6 @@ import org.pgpainless.key.generation.type.rsa.RSA
import org.pgpainless.key.generation.type.rsa.RsaLength import org.pgpainless.key.generation.type.rsa.RsaLength
import org.pgpainless.key.generation.type.xdh.XDH import org.pgpainless.key.generation.type.xdh.XDH
import org.pgpainless.key.generation.type.xdh.XDHSpec import org.pgpainless.key.generation.type.xdh.XDHSpec
import java.security.spec.AlgorithmParameterSpec
@Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420 @Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420
interface KeyType { interface KeyType {
@ -35,20 +35,22 @@ interface KeyType {
/** /**
* Return the strength of the key in bits. * Return the strength of the key in bits.
*
* @return strength of the key in bits * @return strength of the key in bits
*/ */
val bitStrength: Int val bitStrength: Int
/** /**
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key. * Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the
* key.
* *
* @return algorithm parameter spec * @return algorithm parameter spec
*/ */
val algorithmSpec: AlgorithmParameterSpec val algorithmSpec: AlgorithmParameterSpec
/** /**
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag. * Return true if the key that is generated from this type is able to carry the SIGN_DATA key
* See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}. * flag. See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
* *
* @return true if the key can sign. * @return true if the key can sign.
*/ */
@ -56,8 +58,8 @@ interface KeyType {
@JvmName("canSign") get() = algorithm.signingCapable @JvmName("canSign") get() = algorithm.signingCapable
/** /**
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag. * Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER
* See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}. * key flag. See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
* *
* @return true if the key is able to certify other keys * @return true if the key is able to certify other keys
*/ */
@ -65,8 +67,8 @@ interface KeyType {
@JvmName("canCertify") get() = canSign @JvmName("canCertify") get() = canSign
/** /**
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag. * Return true if the key that is generated from this type is able to carry the AUTHENTICATION
* See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}. * key flag. See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
* *
* @return true if the key can be used for authentication purposes. * @return true if the key can be used for authentication purposes.
*/ */
@ -74,8 +76,8 @@ interface KeyType {
@JvmName("canAuthenticate") get() = canSign @JvmName("canAuthenticate") get() = canSign
/** /**
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag. * Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}. * key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
* *
* @return true if the key can encrypt communication * @return true if the key can encrypt communication
*/ */
@ -83,8 +85,8 @@ interface KeyType {
@JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable @JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable
/** /**
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag. * Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}. * key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
* *
* @return true if the key can encrypt for storage * @return true if the key can encrypt for storage
*/ */
@ -92,19 +94,14 @@ interface KeyType {
@JvmName("canEncryptStorage") get() = algorithm.encryptionCapable @JvmName("canEncryptStorage") get() = algorithm.encryptionCapable
companion object { companion object {
@JvmStatic @JvmStatic fun RSA(length: RsaLength): RSA = RSA.withLength(length)
fun RSA(length: RsaLength): RSA = RSA.withLength(length)
@JvmStatic @JvmStatic fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
@JvmStatic @JvmStatic fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
@JvmStatic @JvmStatic fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
@JvmStatic @JvmStatic fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
} }
} }

View File

@ -4,19 +4,20 @@
package org.pgpainless.key.generation.type.ecc package org.pgpainless.key.generation.type.ecc
/** /**
* Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and * Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and
* [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. * [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. For curve25519 related curve definitions
* For curve25519 related curve definitions see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve]. * see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve].
*/ */
enum class EllipticCurve( enum class EllipticCurve(val curveName: String, val bitStrength: Int) {
val curveName: String, _P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see
val bitStrength: Int // https://tools.ietf.org/search/rfc4492#page-32
) { _P384(
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32 "secp384r1",
_P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
_P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32 _P521(
"secp521r1",
521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
_SECP256K1("secp256k1", 256), _SECP256K1("secp256k1", 256),
_BRAINPOOLP256R1("brainpoolP256r1", 256), _BRAINPOOLP256R1("brainpoolP256r1", 256),
_BRAINPOOLP384R1("brainpoolP384r1", 384), _BRAINPOOLP384R1("brainpoolP384r1", 384),

View File

@ -16,7 +16,6 @@ class ECDH private constructor(val curve: EllipticCurve) : KeyType {
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
companion object { companion object {
@JvmStatic @JvmStatic fun fromCurve(curve: EllipticCurve) = ECDH(curve)
fun fromCurve(curve: EllipticCurve) = ECDH(curve)
} }
} }

View File

@ -16,7 +16,6 @@ class ECDSA private constructor(val curve: EllipticCurve) : KeyType {
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
companion object { companion object {
@JvmStatic @JvmStatic fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
} }
} }

View File

@ -15,7 +15,6 @@ class EdDSA private constructor(val curve: EdDSACurve) : KeyType {
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName) override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
companion object { companion object {
@JvmStatic @JvmStatic fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
} }
} }

View File

@ -4,9 +4,7 @@
package org.pgpainless.key.generation.type.eddsa package org.pgpainless.key.generation.type.eddsa
enum class EdDSACurve( enum class EdDSACurve(val curveName: String, val bitStrength: Int) {
val curveName: String,
val bitStrength: Int) {
_Ed25519("ed25519", 256), _Ed25519("ed25519", 256),
; ;

View File

@ -22,7 +22,6 @@ class ElGamal private constructor(length: ElGamalLength) : KeyType {
override val algorithmSpec = ElGamalParameterSpec(length.p, length.g) override val algorithmSpec = ElGamalParameterSpec(length.p, length.g)
companion object { companion object {
@JvmStatic @JvmStatic fun withLength(length: ElGamalLength) = ElGamal(length)
fun withLength(length: ElGamalLength) = ElGamal(length)
} }
} }

View File

@ -4,61 +4,54 @@
package org.pgpainless.key.generation.type.elgamal package org.pgpainless.key.generation.type.elgamal
import org.pgpainless.key.generation.type.KeyLength
import java.math.BigInteger import java.math.BigInteger
import org.pgpainless.key.generation.type.KeyLength
/** /**
* The following primes are taken from RFC-3526. * The following primes are taken from RFC-3526.
* *
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt"> * @see <a href="https://www.ietf.org/rfc/rfc3526.txt"> RFC-3526: More Modular Exponential (MODP)
* RFC-3526: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)</a> * Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
*
* @deprecated the use of ElGamal keys is no longer recommended. * @deprecated the use of ElGamal keys is no longer recommended.
*/ */
@Deprecated("The use of ElGamal keys is no longer recommended.") @Deprecated("The use of ElGamal keys is no longer recommended.")
enum class ElGamalLength( enum class ElGamalLength(override val length: Int, p: String, g: String) : KeyLength {
override val length: Int,
p: String,
g: String
) : KeyLength {
/** /** prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. generator: 2 */
* prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. _1536(
* generator: 2 1536,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
_1536(1536, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "2"), "2"),
/** /** prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. generator: 2 */
* prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. _2048(
* generator: 2 2048,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF",
_2048(2048, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "2"), "2"),
/** /** prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. generator: 2 */
* prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. _3072(
* generator: 2 3072,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
_3072(3072, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "2"), "2"),
/** /** prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. generator: 2 */
* prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. _4096(
* generator: 2 4096,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF",
_4096(4096, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", "2"), "2"),
/** /** prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. generator: 2 */
* prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. _6144(
* generator: 2 6144,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF",
_6144(6144, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", "2"), "2"),
/** /** prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. generator: 2 */
* prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. _8192(
* generator: 2 8192,
*/ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF",
_8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2") "2");
;
val p: BigInteger val p: BigInteger
val g: BigInteger val g: BigInteger
@ -67,6 +60,4 @@ enum class ElGamalLength(
this.p = BigInteger(p, 16) this.p = BigInteger(p, 16)
this.g = BigInteger(g, 16) this.g = BigInteger(g, 16)
} }
} }

View File

@ -4,14 +4,12 @@
package org.pgpainless.key.generation.type.rsa package org.pgpainless.key.generation.type.rsa
import java.security.spec.RSAKeyGenParameterSpec
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.KeyType
import java.security.spec.RSAKeyGenParameterSpec
/** /** Key type that specifies the RSA_GENERAL algorithm. */
* Key type that specifies the RSA_GENERAL algorithm. class RSA private constructor(length: RsaLength) : KeyType {
*/
class RSA private constructor(length: RsaLength): KeyType {
override val name = "RSA" override val name = "RSA"
override val algorithm = PublicKeyAlgorithm.RSA_GENERAL override val algorithm = PublicKeyAlgorithm.RSA_GENERAL
@ -19,7 +17,6 @@ class RSA private constructor(length: RsaLength): KeyType {
override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4) override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4)
companion object { companion object {
@JvmStatic @JvmStatic fun withLength(length: RsaLength) = RSA(length)
fun withLength(length: RsaLength) = RSA(length)
} }
} }

View File

@ -8,14 +8,13 @@ import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec
import org.pgpainless.algorithm.PublicKeyAlgorithm import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.key.generation.type.KeyType import org.pgpainless.key.generation.type.KeyType
class XDH private constructor(spec: XDHSpec): KeyType { class XDH private constructor(spec: XDHSpec) : KeyType {
override val name = "XDH" override val name = "XDH"
override val algorithm = PublicKeyAlgorithm.ECDH override val algorithm = PublicKeyAlgorithm.ECDH
override val bitStrength = spec.bitStrength override val bitStrength = spec.bitStrength
override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName) override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName)
companion object { companion object {
@JvmStatic @JvmStatic fun fromSpec(spec: XDHSpec) = XDH(spec)
fun fromSpec(spec: XDHSpec) = XDH(spec)
} }
} }

View File

@ -4,10 +4,7 @@
package org.pgpainless.key.generation.type.xdh package org.pgpainless.key.generation.type.xdh
enum class XDHSpec( enum class XDHSpec(val algorithmName: String, val curveName: String, val bitStrength: Int) {
val algorithmName: String,
val curveName: String,
val bitStrength: Int) {
_X25519("X25519", "curve25519", 256), _X25519("X25519", "curve25519", 256),
; ;

View File

@ -11,78 +11,79 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.key.SubkeyIdentifier import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
abstract class KeyAccessor( abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: SubkeyIdentifier) {
protected val info: KeyRingInfo,
protected val key: SubkeyIdentifier
) {
/** /**
* Depending on the way we address the key (key-id or user-id), return the respective [PGPSignature] * Depending on the way we address the key (key-id or user-id), return the respective
* which contains the algorithm preferences we are going to use. * [PGPSignature] which contains the algorithm preferences we are going to use.
*
* <p> * <p>
* If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification, * If we address a key via its user-id, we want to rely on the algorithm preferences in the
* while we would instead rely on those in the direct-key signature if we'd address the key by key-id. * user-id certification, while we would instead rely on those in the direct-key signature if
* we'd address the key by key-id.
* *
* @return signature * @return signature
*/ */
abstract val signatureWithPreferences: PGPSignature abstract val signatureWithPreferences: PGPSignature
/** /** Preferred symmetric key encryption algorithms. */
* Preferred symmetric key encryption algorithms.
*/
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
get() = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences) get() =
SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences)
/** /** Preferred hash algorithms. */
* Preferred hash algorithms.
*/
val preferredHashAlgorithms: Set<HashAlgorithm> val preferredHashAlgorithms: Set<HashAlgorithm>
get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences) get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences)
/** /** Preferred compression algorithms. */
* Preferred compression algorithms.
*/
val preferredCompressionAlgorithms: Set<CompressionAlgorithm> val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences) get() =
SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences)
/** /**
* Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`). * Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`). In this case we are
* In this case we are sourcing preferred algorithms from the user-id certification first. * sourcing preferred algorithms from the user-id certification first.
*/ */
class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence): KeyAccessor(info, key) { class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence) :
KeyAccessor(info, key) {
override val signatureWithPreferences: PGPSignature override val signatureWithPreferences: PGPSignature
get() = checkNotNull(info.getLatestUserIdCertification(userId.toString())) { get() =
checkNotNull(info.getLatestUserIdCertification(userId.toString())) {
"No valid user-id certification signature found for '$userId'." "No valid user-id certification signature found for '$userId'."
} }
} }
/** /**
* Address the key via key-id. * Address the key via key-id. In this case we are sourcing preferred algorithms from the keys
* In this case we are sourcing preferred algorithms from the keys direct-key signature first. * direct-key signature first.
*/ */
class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) { class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
override val signatureWithPreferences: PGPSignature override val signatureWithPreferences: PGPSignature
get() { get() {
// If the key is located by Key ID, the algorithm of the primary User ID of the key provides the // If the key is located by Key ID, the algorithm of the primary User ID of the key
// provides the
// preferred symmetric algorithm. // preferred symmetric algorithm.
info.primaryUserId?.let { info.primaryUserId?.let { userId ->
userId -> info.getLatestUserIdCertification(userId).let { if (it != null) return it } info.getLatestUserIdCertification(userId).let { if (it != null) return it }
} }
return checkNotNull(info.latestDirectKeySelfSignature) { "No valid signature found." } return checkNotNull(info.latestDirectKeySelfSignature) {
"No valid signature found."
}
} }
} }
class SubKey(info: KeyRingInfo, key: SubkeyIdentifier): KeyAccessor(info, key) { class SubKey(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
override val signatureWithPreferences: PGPSignature override val signatureWithPreferences: PGPSignature
get() = checkNotNull( get() =
checkNotNull(
if (key.isPrimaryKey) { if (key.isPrimaryKey) {
info.latestDirectKeySelfSignature ?: info.latestDirectKeySelfSignature
info.primaryUserId?.let { info.getLatestUserIdCertification(it) } ?: info.primaryUserId?.let { info.getLatestUserIdCertification(it) }
} else { } else {
info.getCurrentSubkeyBindingSignature(key.subkeyId) info.getCurrentSubkeyBindingSignature(key.subkeyId)
}) {
"No valid signature found."
} }
) { "No valid signature found." }
} }
} }

View File

@ -12,63 +12,69 @@ import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKey
@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.") @Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.")
class KeyInfo private constructor( class KeyInfo private constructor(val secretKey: PGPSecretKey?, val publicKey: PGPPublicKey) {
val secretKey: PGPSecretKey?,
val publicKey: PGPPublicKey) {
constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey) constructor(secretKey: PGPSecretKey) : this(secretKey, secretKey.publicKey)
constructor(publicKey: PGPPublicKey): this(null, publicKey)
constructor(publicKey: PGPPublicKey) : this(null, publicKey)
/** /**
* Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key * Return the name of the elliptic curve used by this key, or throw an
* is not based on elliptic curves, or on an unknown curve. * [IllegalArgumentException] if the key is not based on elliptic curves, or on an unknown
* curve.
*/ */
@Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.", @Deprecated(
"Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.",
ReplaceWith("publicKey.getCurveName()")) ReplaceWith("publicKey.getCurveName()"))
val curveName: String val curveName: String
get() = publicKey.getCurveName() get() = publicKey.getCurveName()
/** /**
* Return true, if the secret key is encrypted. * Return true, if the secret key is encrypted. This method returns false, if the secret key is
* This method returns false, if the secret key is null. * null.
*/ */
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
ReplaceWith("secretKey.isEncrypted()")) ReplaceWith("secretKey.isEncrypted()"))
val isEncrypted: Boolean val isEncrypted: Boolean
get() = secretKey?.isEncrypted() ?: false get() = secretKey?.isEncrypted() ?: false
/** /**
* Return true, if the secret key is decrypted. * Return true, if the secret key is decrypted. This method returns true, if the secret key is
* This method returns true, if the secret key is null. * null.
*/ */
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
ReplaceWith("secretKey.isDecrypted()")) ReplaceWith("secretKey.isDecrypted()"))
val isDecrypted: Boolean val isDecrypted: Boolean
get() = secretKey?.isDecrypted() ?: true get() = secretKey?.isDecrypted() ?: true
/** /**
* Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. * Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. This method returns
* This method returns false, if the secret key is null. * false, if the secret key is null.
*/ */
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
ReplaceWith("secretKey.hasDummyS2K()")) ReplaceWith("secretKey.hasDummyS2K()"))
val hasDummyS2K: Boolean val hasDummyS2K: Boolean
@JvmName("hasDummyS2K") @JvmName("hasDummyS2K") get() = secretKey?.hasDummyS2K() ?: false
get() = secretKey?.hasDummyS2K() ?: false
companion object { companion object {
@JvmStatic @JvmStatic
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
ReplaceWith("secretKey.isEncrypted()")) ReplaceWith("secretKey.isEncrypted()"))
fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted() fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted()
@JvmStatic @JvmStatic
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
ReplaceWith("secretKey.isDecrypted()")) ReplaceWith("secretKey.isDecrypted()"))
fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted() fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted()
@JvmStatic @JvmStatic
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.", @Deprecated(
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
ReplaceWith("secretKey.hasDummyS2K()")) ReplaceWith("secretKey.hasDummyS2K()"))
fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K() fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K()
} }

View File

@ -4,6 +4,7 @@
package org.pgpainless.key.info package org.pgpainless.key.info
import java.util.*
import openpgp.openPgpKeyId import openpgp.openPgpKeyId
import org.bouncycastle.extensions.* import org.bouncycastle.extensions.*
import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.*
@ -19,162 +20,132 @@ import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil.Companion.getKeyExpirationTimeAsDate
import org.pgpainless.util.DateUtil import org.pgpainless.util.DateUtil
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.*
class KeyRingInfo( class KeyRingInfo(
val keys: PGPKeyRing, val keys: PGPKeyRing,
val policy: Policy = PGPainless.getPolicy(), val policy: Policy = PGPainless.getPolicy(),
val referenceDate: Date = Date()) { val referenceDate: Date = Date()
) {
@JvmOverloads @JvmOverloads
constructor(keys: PGPKeyRing, referenceDate: Date = Date()): this(keys, PGPainless.getPolicy(), referenceDate) constructor(
keys: PGPKeyRing,
referenceDate: Date = Date()
) : this(keys, PGPainless.getPolicy(), referenceDate)
private val signatures: Signatures = Signatures(keys, referenceDate, policy) private val signatures: Signatures = Signatures(keys, referenceDate, policy)
/** /** Primary {@link PGPPublicKey}.´ */
* Primary {@link PGPPublicKey}.´
*/
val publicKey: PGPPublicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys) val publicKey: PGPPublicKey = KeyRingUtils.requirePrimaryPublicKeyFrom(keys)
/** /** Primary key ID. */
* Primary key ID.
*/
val keyId: Long = publicKey.keyID val keyId: Long = publicKey.keyID
/** /** Primary key fingerprint. */
* Primary key fingerprint.
*/
val fingerprint: OpenPgpFingerprint = OpenPgpFingerprint.of(keys) val fingerprint: OpenPgpFingerprint = OpenPgpFingerprint.of(keys)
/** /** All User-IDs (valid, expired, revoked). */
* All User-IDs (valid, expired, revoked).
*/
val userIds: List<String> = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) val userIds: List<String> = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey)
/** /** Primary User-ID. */
* Primary User-ID.
*/
val primaryUserId = findPrimaryUserId() val primaryUserId = findPrimaryUserId()
/** /** Revocation State. */
* Revocation State.
*/
val revocationState = signatures.primaryKeyRevocation.toRevocationState() 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 the date on which the primary key was revoked, or null if it has not yet been revoked.
* *
* @return revocation date or null * @return revocation date or null
*/ */
val revocationDate: Date? = if (revocationState.isSoftRevocation()) revocationState.date else 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]. * Primary [PGPSecretKey] of this key ring or null if the key ring is not a [PGPSecretKeyRing].
*/ */
val secretKey: PGPSecretKey? = when(keys) { val secretKey: PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.secretKey!! is PGPSecretKeyRing -> keys.secretKey!!
else -> null else -> null
} }
/** /** OpenPGP key version. */
* OpenPGP key version.
*/
val version: Int = publicKey.version val version: Int = publicKey.version
/** /**
* Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. The first key in the list
* The first key in the list being the primary key. * being the primary key. Note that the list is unmodifiable.
* Note that the list is unmodifiable.
* *
* @return list of public keys * @return list of public keys
*/ */
val publicKeys: List<PGPPublicKey> = keys.publicKeys.asSequence().toList() val publicKeys: List<PGPPublicKey> = keys.publicKeys.asSequence().toList()
/** /** All secret keys. If the key ring is a [PGPPublicKeyRing], then return an empty list. */
* All secret keys. val secretKeys: List<PGPSecretKey> =
* If the key ring is a [PGPPublicKeyRing], then return an empty list. when (keys) {
*/
val secretKeys: List<PGPSecretKey> = when(keys) {
is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList() is PGPSecretKeyRing -> keys.secretKeys.asSequence().toList()
else -> listOf() else -> listOf()
} }
/** /** List of valid public subkeys. */
* List of valid public subkeys. val validSubkeys: List<PGPPublicKey> =
*/ keys.publicKeys.asSequence().filter { isKeyValidlyBound(it.keyID) }.toList()
val validSubkeys: List<PGPPublicKey> = keys.publicKeys.asSequence()
.filter { isKeyValidlyBound(it.keyID) }
.toList()
/** /** List of valid user-IDs. */
* List of valid user-IDs.
*/
val validUserIds: List<String> = userIds.filter { isUserIdBound(it) } val validUserIds: List<String> = userIds.filter { isUserIdBound(it) }
/** /** List of valid and expired user-IDs. */
* List of valid and expired user-IDs. val validAndExpiredUserIds: List<String> =
*/ userIds.filter {
val validAndExpiredUserIds: List<String> = userIds.filter {
val certification = signatures.userIdCertifications[it] ?: return@filter false val certification = signatures.userIdCertifications[it] ?: return@filter false
val revocation = signatures.userIdRevocations[it] ?: return@filter true val revocation = signatures.userIdRevocations[it] ?: return@filter true
return@filter !revocation.isHardRevocation && certification.creationTime > revocation.creationTime return@filter !revocation.isHardRevocation &&
certification.creationTime > revocation.creationTime
} }
/** /** List of email addresses that can be extracted from the user-IDs. */
* List of email addresses that can be extracted from the user-IDs. val emailAddresses: List<String> =
*/ userIds.mapNotNull {
val emailAddresses: List<String> = userIds.mapNotNull {
PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 -> PATTERN_EMAIL_FROM_USERID.matcher(it).let { m1 ->
if (m1.find()) m1.group(1) if (m1.find()) m1.group(1)
else PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 -> else
if(m2.find()) m2.group(1) else null PATTERN_EMAIL_EXPLICIT.matcher(it).let { m2 ->
if (m2.find()) m2.group(1) else null
} }
} }
} }
/** /** Newest direct-key self-signature on the primary key. */
* Newest direct-key self-signature on the primary key.
*/
val latestDirectKeySelfSignature: PGPSignature? = signatures.primaryKeySelfSignature val latestDirectKeySelfSignature: PGPSignature? = signatures.primaryKeySelfSignature
/** /** Newest primary-key revocation self-signature. */
* Newest primary-key revocation self-signature.
*/
val revocationSelfSignature: PGPSignature? = signatures.primaryKeyRevocation val revocationSelfSignature: PGPSignature? = signatures.primaryKeyRevocation
/** /** Public-key encryption-algorithm of the primary key. */
* Public-key encryption-algorithm of the primary key.
*/
val algorithm: PublicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.algorithm) val algorithm: PublicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(publicKey.algorithm)
/** /** Creation date of the primary key. */
* Creation date of the primary key.
*/
val creationDate: Date = publicKey.creationTime!! val creationDate: Date = publicKey.creationTime!!
/** /** Latest date at which the key was modified (either by adding a subkey or self-signature). */
* Latest date at which the key was modified (either by adding a subkey or self-signature).
*/
val lastModified: Date = getMostRecentSignature()?.creationTime ?: getLatestKeyCreationDate() val lastModified: Date = getMostRecentSignature()?.creationTime ?: getLatestKeyCreationDate()
/** /** True, if the underlying keyring is a [PGPSecretKeyRing]. */
* True, if the underlying keyring is a [PGPSecretKeyRing].
*/
val isSecretKey: Boolean = keys is PGPSecretKeyRing val isSecretKey: Boolean = keys is PGPSecretKeyRing
/** /** True, if there are no encrypted secret keys. */
* True, if there are no encrypted secret keys. val isFullyDecrypted: Boolean =
*/ !isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() }
val isFullyDecrypted: Boolean = !isSecretKey || secretKeys.all { it.hasDummyS2K() || it.isDecrypted() }
/** /** True, if there are only encrypted secret keys. */
* True, if there are only encrypted secret keys. val isFullyEncrypted: Boolean =
*/ isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() }
val isFullyEncrypted: Boolean = isSecretKey && secretKeys.none { !it.hasDummyS2K() && it.isDecrypted() }
/** /** List of public keys, whose secret key counterparts can be used to decrypt messages. */
* List of public keys, whose secret key counterparts can be used to decrypt messages. val decryptionSubkeys: List<PGPPublicKey> =
*/ keys.publicKeys
val decryptionSubkeys: List<PGPPublicKey> = keys.publicKeys.asSequence().filter { .asSequence()
.filter {
if (it.keyID != keyId) { if (it.keyID != keyId) {
if (signatures.subkeyBindings[it.keyID] == null) { if (signatures.subkeyBindings[it.keyID] == null) {
LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.") LOGGER.debug("Subkey ${it.keyID.openPgpKeyId()} has no binding signature.")
@ -186,20 +157,23 @@ class KeyRingInfo(
return@filter false return@filter false
} }
return@filter true return@filter true
}.toList() }
.toList()
/** /** Expiration date of the primary key. */
* Expiration date of the primary key.
*/
val primaryKeyExpirationDate: Date? val primaryKeyExpirationDate: Date?
get() { get() {
val directKeyExpirationDate: Date? = latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) } val directKeyExpirationDate: Date? =
latestDirectKeySelfSignature?.let { getKeyExpirationTimeAsDate(it, publicKey) }
val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId() val possiblyExpiredPrimaryUserId = getPossiblyExpiredPrimaryUserId()
val primaryUserIdCertification = possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) } val primaryUserIdCertification =
val userIdExpirationDate: Date? = primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) } possiblyExpiredPrimaryUserId?.let { getLatestUserIdCertification(it) }
val userIdExpirationDate: Date? =
primaryUserIdCertification?.let { getKeyExpirationTimeAsDate(it, publicKey) }
if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) { if (latestDirectKeySelfSignature == null && primaryUserIdCertification == null) {
throw NoSuchElementException("No direct-key signature and no user-id signature found.") throw NoSuchElementException(
"No direct-key signature and no user-id signature found.")
} }
if (directKeyExpirationDate != null && userIdExpirationDate == null) { if (directKeyExpirationDate != null && userIdExpirationDate == null) {
return directKeyExpirationDate return directKeyExpirationDate
@ -207,52 +181,49 @@ class KeyRingInfo(
if (directKeyExpirationDate == null) { if (directKeyExpirationDate == null) {
return userIdExpirationDate return userIdExpirationDate
} }
return if (directKeyExpirationDate < userIdExpirationDate) return if (directKeyExpirationDate < userIdExpirationDate) directKeyExpirationDate
directKeyExpirationDate
else userIdExpirationDate else userIdExpirationDate
} }
/** /** List of all subkeys that can be used to sign a message. */
* 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) }
val signingSubkeys: List<PGPPublicKey> = validSubkeys.filter { getKeyFlagsOf(it.keyID).contains(KeyFlag.SIGN_DATA) }
/** /** Whether the key is usable for encryption. */
* Whether the key is usable for encryption.
*/
val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY) val isUsableForEncryption: Boolean = isUsableForEncryption(EncryptionPurpose.ANY)
/** /**
* Whether the key is capable of signing messages. * Whether the key is capable of signing messages. This field is also true, if the key contains
* This field is also true, if the key contains a subkey that is capable of signing messages, but where the secret * a subkey that is capable of signing messages, but where the secret key is unavailable, e.g.
* key is unavailable, e.g. because it was moved to a smart-card. * because it was moved to a smart-card.
* *
* To check for keys that are actually usable to sign messages, use [isUsableForSigning]. * To check for keys that are actually usable to sign messages, use [isUsableForSigning].
*/ */
val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty() val isSigningCapable: Boolean = isKeyValidlyBound(keyId) && signingSubkeys.isNotEmpty()
/** /** Whether the key is actually usable to sign messages. */
* Whether the key is actually usable to sign messages. val isUsableForSigning: Boolean =
*/ isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) }
val isUsableForSigning: Boolean = isSigningCapable && signingSubkeys.any { isSecretKeyAvailable(it.keyID) }
/** /** [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key. */
* [HashAlgorithm] preferences of the primary user-ID or if absent, of the primary key.
*/
val preferredHashAlgorithms: Set<HashAlgorithm> val preferredHashAlgorithms: Set<HashAlgorithm>
get() = primaryUserId?.let { getPreferredHashAlgorithms(it) } ?: getPreferredHashAlgorithms(keyId) get() =
primaryUserId?.let { getPreferredHashAlgorithms(it) }
?: getPreferredHashAlgorithms(keyId)
/** /**
* [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key. * [SymmetricKeyAlgorithm] preferences of the primary user-ID or if absent of the primary key.
*/ */
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
get() = primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) } ?: getPreferredSymmetricKeyAlgorithms(keyId) get() =
primaryUserId?.let { getPreferredSymmetricKeyAlgorithms(it) }
?: getPreferredSymmetricKeyAlgorithms(keyId)
/** /** [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key. */
* [CompressionAlgorithm] preferences of the primary user-ID or if absent, the primary key.
*/
val preferredCompressionAlgorithms: Set<CompressionAlgorithm> val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
get() = primaryUserId?.let { getPreferredCompressionAlgorithms(it) } ?: getPreferredCompressionAlgorithms(keyId) get() =
primaryUserId?.let { getPreferredCompressionAlgorithms(it) }
?: getPreferredCompressionAlgorithms(keyId)
/** /**
* Return the expiration date of the subkey with the provided fingerprint. * Return the expiration date of the subkey with the provided fingerprint.
@ -272,13 +243,19 @@ class KeyRingInfo(
*/ */
fun getSubkeyExpirationDate(keyId: Long): Date? { fun getSubkeyExpirationDate(keyId: Long): Date? {
if (publicKey.keyID == keyId) return primaryKeyExpirationDate if (publicKey.keyID == keyId) return primaryKeyExpirationDate
val subkey = getPublicKey(keyId) ?: throw NoSuchElementException("No subkey with key-ID ${keyId.openPgpKeyId()} found.") val subkey =
val bindingSig = getCurrentSubkeyBindingSignature(keyId) ?: throw AssertionError("Subkey has no valid binding signature.") 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 bindingSig.getKeyExpirationDate(subkey.creationTime) return bindingSig.getKeyExpirationDate(subkey.creationTime)
} }
/** /**
* Return the date after which the key can no longer be used to perform the given use-case, caused by expiration. * 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 * @return expiration date for the given use-case
*/ */
@ -289,17 +266,21 @@ class KeyRingInfo(
val primaryKeyExpiration = primaryKeyExpirationDate val primaryKeyExpiration = primaryKeyExpirationDate
val keysWithFlag: List<PGPPublicKey> = getKeysWithKeyFlag(use) val keysWithFlag: List<PGPPublicKey> = getKeysWithKeyFlag(use)
if (keysWithFlag.isEmpty()) throw NoSuchElementException("No key with the required key flag found.") if (keysWithFlag.isEmpty())
throw NoSuchElementException("No key with the required key flag found.")
var nonExpiring = false var nonExpiring = false
val latestSubkeyExpiration = keysWithFlag.map { key -> val latestSubkeyExpiration =
keysWithFlag
.map { key ->
getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true } getSubkeyExpirationDate(key.keyID).also { if (it == null) nonExpiring = true }
}.filterNotNull().maxByOrNull { it } }
.filterNotNull()
.maxByOrNull { it }
if (nonExpiring) return primaryKeyExpiration if (nonExpiring) return primaryKeyExpiration
return if (primaryKeyExpiration == null) latestSubkeyExpiration return if (primaryKeyExpiration == null) latestSubkeyExpiration
else if (latestSubkeyExpiration == null) else if (latestSubkeyExpiration == null) primaryKeyExpiration
primaryKeyExpiration
else minOf(primaryKeyExpiration, latestSubkeyExpiration) else minOf(primaryKeyExpiration, latestSubkeyExpiration)
} }
@ -318,17 +299,24 @@ class KeyRingInfo(
* @param flag flag * @param flag flag
* @return keys with flag * @return keys with flag
*/ */
fun getKeysWithKeyFlag(flag: KeyFlag): List<PGPPublicKey> = publicKeys.filter { getKeyFlagsOf(it.keyID).contains(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 a list of all subkeys which can be used to encrypt a message for the given user-ID.
* *
* @return encryption subkeys * @return encryption subkeys
*/ */
fun getEncryptionSubkeys(userId: CharSequence?, purpose: EncryptionPurpose): List<PGPPublicKey> { fun getEncryptionSubkeys(
userId: CharSequence?,
purpose: EncryptionPurpose
): List<PGPPublicKey> {
if (userId != null && !isUserIdValid(userId)) { if (userId != null && !isUserIdValid(userId)) {
throw UnboundUserIdException(OpenPgpFingerprint.of(keys), userId.toString(), throw UnboundUserIdException(
getLatestUserIdCertification(userId), getUserIdRevocation(userId)) OpenPgpFingerprint.of(keys),
userId.toString(),
getLatestUserIdCertification(userId),
getUserIdRevocation(userId))
} }
return getEncryptionSubkeys(purpose) return getEncryptionSubkeys(purpose)
} }
@ -341,12 +329,15 @@ class KeyRingInfo(
fun getEncryptionSubkeys(purpose: EncryptionPurpose): List<PGPPublicKey> { fun getEncryptionSubkeys(purpose: EncryptionPurpose): List<PGPPublicKey> {
primaryKeyExpirationDate?.let { primaryKeyExpirationDate?.let {
if (it < referenceDate) { if (it < referenceDate) {
LOGGER.debug("Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}") LOGGER.debug(
"Certificate is expired: Primary key is expired on ${DateUtil.formatUTCDate(it)}")
return listOf() return listOf()
} }
} }
return keys.publicKeys.asSequence().filter { return keys.publicKeys
.asSequence()
.filter {
if (!isKeyValidlyBound(it.keyID)) { if (!isKeyValidlyBound(it.keyID)) {
LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.") LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is not validly bound.")
return@filter false return@filter false
@ -354,29 +345,37 @@ class KeyRingInfo(
getSubkeyExpirationDate(it.keyID)?.let { exp -> getSubkeyExpirationDate(it.keyID)?.let { exp ->
if (exp < referenceDate) { if (exp < referenceDate) {
LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.") LOGGER.debug(
"(Sub?)-Key ${it.keyID.openPgpKeyId()} is expired on ${DateUtil.formatUTCDate(exp)}.")
return@filter false return@filter false
} }
} }
if (!it.isEncryptionKey) { if (!it.isEncryptionKey) {
LOGGER.debug("(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.") LOGGER.debug(
"(Sub?)-Key ${it.keyID.openPgpKeyId()} algorithm is not capable of encryption.")
return@filter false return@filter false
} }
val keyFlags = getKeyFlagsOf(it.keyID) val keyFlags = getKeyFlagsOf(it.keyID)
when (purpose) { when (purpose) {
EncryptionPurpose.COMMUNICATIONS -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) EncryptionPurpose.COMMUNICATIONS ->
EncryptionPurpose.STORAGE -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS)
EncryptionPurpose.ANY -> return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) || keyFlags.contains(KeyFlag.ENCRYPT_STORAGE) EncryptionPurpose.STORAGE ->
return@filter keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)
EncryptionPurpose.ANY ->
return@filter keyFlags.contains(KeyFlag.ENCRYPT_COMMS) ||
keyFlags.contains(KeyFlag.ENCRYPT_STORAGE)
} }
}.toList() }
.toList()
} }
/** /**
* Return, whether the key is usable for encryption, given the purpose. * 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. * @return true, if the key can be used to encrypt a message according to the
* encryption-purpose.
*/ */
fun isUsableForEncryption(purpose: EncryptionPurpose): Boolean { fun isUsableForEncryption(purpose: EncryptionPurpose): Boolean {
return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty() return isKeyValidlyBound(keyId) && getEncryptionSubkeys(purpose).isNotEmpty()
@ -387,18 +386,18 @@ class KeyRingInfo(
* *
* @return possibly expired primary user-ID * @return possibly expired primary user-ID
*/ */
fun getPossiblyExpiredPrimaryUserId(): String? = primaryUserId ?: userIds fun getPossiblyExpiredPrimaryUserId(): String? =
.mapNotNull { userId -> primaryUserId
getLatestUserIdCertification(userId)?.let { userId to it } ?: userIds
} .mapNotNull { userId -> getLatestUserIdCertification(userId)?.let { userId to it } }
.sortedByDescending { it.second.creationTime } .sortedByDescending { it.second.creationTime }
.maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID }?.first .maxByOrNull { it.second.hashedSubPackets.isPrimaryUserID }
?.first
/** /** Return the most-recently created self-signature on the key. */
* Return the most-recently created self-signature on the key.
*/
private fun getMostRecentSignature(): PGPSignature? = private fun getMostRecentSignature(): PGPSignature? =
setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature).asSequence() setOfNotNull(latestDirectKeySelfSignature, revocationSelfSignature)
.asSequence()
.plus(signatures.userIdCertifications.values) .plus(signatures.userIdCertifications.values)
.plus(signatures.userIdRevocations.values) .plus(signatures.userIdRevocations.values)
.plus(signatures.subkeyBindings.values) .plus(signatures.subkeyBindings.values)
@ -409,7 +408,8 @@ class KeyRingInfo(
* *
* @return latest key creation time * @return latest key creation time
*/ */
fun getLatestKeyCreationDate(): Date = validSubkeys.maxByOrNull { creationDate }?.creationTime fun getLatestKeyCreationDate(): Date =
validSubkeys.maxByOrNull { creationDate }?.creationTime
?: throw AssertionError("Apparently there is no validly bound key in this key ring.") ?: throw AssertionError("Apparently there is no validly bound key in this key ring.")
/** /**
@ -417,31 +417,36 @@ class KeyRingInfo(
* *
* @return latest self-certification for the given user-ID. * @return latest self-certification for the given user-ID.
*/ */
fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? = signatures.userIdCertifications[userId] fun getLatestUserIdCertification(userId: CharSequence): PGPSignature? =
signatures.userIdCertifications[userId]
/** /**
* Return the latest revocation self-signature for the given user-ID * Return the latest revocation self-signature for the given user-ID
* *
* @return latest user-ID revocation for the given user-ID * @return latest user-ID revocation for the given user-ID
*/ */
fun getUserIdRevocation(userId: CharSequence): PGPSignature? = signatures.userIdRevocations[userId] fun getUserIdRevocation(userId: CharSequence): PGPSignature? =
signatures.userIdRevocations[userId]
/** /**
* Return the current binding signature for the subkey with the given key-ID. * Return the current binding signature for the subkey with the given key-ID.
* *
* @return current subkey binding signature * @return current subkey binding signature
*/ */
fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? = signatures.subkeyBindings[keyId] fun getCurrentSubkeyBindingSignature(keyId: Long): PGPSignature? =
signatures.subkeyBindings[keyId]
/** /**
* Return the current revocation signature for the subkey with the given key-ID. * Return the current revocation signature for the subkey with the given key-ID.
* *
* @return current subkey revocation signature * @return current subkey revocation signature
*/ */
fun getSubkeyRevocationSignature(keyId: Long): PGPSignature? = signatures.subkeyRevocations[keyId] 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. * Return a list of {@link KeyFlag KeyFlags} that apply to the subkey with the provided key id.
*
* @param keyId key-id * @param keyId key-id
* @return list of key flags * @return list of key flags
*/ */
@ -454,7 +459,8 @@ class KeyRingInfo(
} }
primaryUserId?.let { primaryUserId?.let {
SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags -> SignatureSubpacketsUtil.parseKeyFlags(getLatestUserIdCertification(it))?.let { flags
->
return flags return flags
} }
} }
@ -480,7 +486,9 @@ class KeyRingInfo(
} else { } else {
getLatestUserIdCertification(userId)?.let { getLatestUserIdCertification(userId)?.let {
SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf() SignatureSubpacketsUtil.parseKeyFlags(it) ?: listOf()
} ?: throw AssertionError("While user-id '$userId' was reported as valid, there appears to be no certification for it.") }
?: throw AssertionError(
"While user-id '$userId' was reported as valid, there appears to be no certification for it.")
} }
/** /**
@ -497,13 +505,15 @@ class KeyRingInfo(
* @param keyId key id * @param keyId key id
* @return secret key or null * @return secret key or null
*/ */
fun getSecretKey(keyId: Long): PGPSecretKey? = when(keys) { fun getSecretKey(keyId: Long): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.getSecretKey(keyId) is PGPSecretKeyRing -> keys.getSecretKey(keyId)
else -> null else -> null
} }
/** /**
* Return true, if the secret-key with the given key-ID is available (i.e. not moved to a smart-card). * 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 * @return availability of the secret key
*/ */
@ -511,7 +521,8 @@ class KeyRingInfo(
return getSecretKey(keyId)?.let { return getSecretKey(keyId)?.let {
return if (it.s2K == null) true // Unencrypted key return if (it.s2K == null) true // Unencrypted key
else it.s2K.type !in 100..110 // Secret key on smart-card else it.s2K.type !in 100..110 // Secret key on smart-card
} ?: false // Missing secret key }
?: false // Missing secret key
} }
/** /**
@ -520,7 +531,8 @@ class KeyRingInfo(
* @param fingerprint fingerprint * @param fingerprint fingerprint
* @return public key or null * @return public key or null
*/ */
fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? = keys.getPublicKey(fingerprint.keyId) fun getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
keys.getPublicKey(fingerprint.keyId)
/** /**
* Return the secret key with the given fingerprint. * Return the secret key with the given fingerprint.
@ -528,7 +540,8 @@ class KeyRingInfo(
* @param fingerprint fingerprint * @param fingerprint fingerprint
* @return secret key or null * @return secret key or null
*/ */
fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? = when(keys) { fun getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId) is PGPSecretKeyRing -> keys.getSecretKey(fingerprint.keyId)
else -> null else -> null
} }
@ -537,12 +550,11 @@ class KeyRingInfo(
* Return the public key matching the given [SubkeyIdentifier]. * Return the public key matching the given [SubkeyIdentifier].
* *
* @return public key * @return public key
* @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the key. * @throws IllegalArgumentException if the identifier's primary key does not match the primary
* key of the key.
*/ */
fun getPublicKey(identifier: SubkeyIdentifier): PGPPublicKey? { fun getPublicKey(identifier: SubkeyIdentifier): PGPPublicKey? {
require(identifier.primaryKeyId == publicKey.keyID) { require(identifier.primaryKeyId == publicKey.keyID) { "Mismatching primary key ID." }
"Mismatching primary key ID."
}
return getPublicKey(identifier.subkeyId) return getPublicKey(identifier.subkeyId)
} }
@ -550,9 +562,11 @@ class KeyRingInfo(
* Return the secret key matching the given [SubkeyIdentifier]. * Return the secret key matching the given [SubkeyIdentifier].
* *
* @return secret key * @return secret key
* @throws IllegalArgumentException if the identifier's primary key does not match the primary key of the 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) { fun getSecretKey(identifier: SubkeyIdentifier): PGPSecretKey? =
when (keys) {
is PGPSecretKeyRing -> { is PGPSecretKeyRing -> {
require(identifier.primaryKeyId == publicKey.keyID) { require(identifier.primaryKeyId == publicKey.keyID) {
"Mismatching primary key ID." "Mismatching primary key ID."
@ -573,7 +587,8 @@ class KeyRingInfo(
// Primary key -> Check Primary Key Revocation // Primary key -> Check Primary Key Revocation
if (publicKey.keyID == this.publicKey.keyID) { if (publicKey.keyID == this.publicKey.keyID) {
return if (signatures.primaryKeyRevocation != null && signatures.primaryKeyRevocation.isHardRevocation) { return if (signatures.primaryKeyRevocation != null &&
signatures.primaryKeyRevocation.isHardRevocation) {
false false
} else signatures.primaryKeyRevocation == null } else signatures.primaryKeyRevocation == null
} }
@ -594,16 +609,18 @@ class KeyRingInfo(
false false
} else { } else {
// Key is soft-revoked, not yet re-bound // Key is soft-revoked, not yet re-bound
(revocation.isExpired(referenceDate) || !revocation.creationTime.after(binding.creationTime)) (revocation.isExpired(referenceDate) ||
!revocation.creationTime.after(binding.creationTime))
} }
} else true } else true
} }
/** /**
* Return the current primary user-id of the key ring. * Return the current primary user-id of the key ring.
*
* <p> * <p>
* Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, * Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, this
* this method returns the first user-id on the key, otherwise null. * method returns the first user-id on the key, otherwise null.
* *
* @return primary user-id or null * @return primary user-id or null
*/ */
@ -612,26 +629,24 @@ class KeyRingInfo(
return null return null
} }
return signatures.userIdCertifications.filter { (_, certification) -> return signatures.userIdCertifications
certification.hashedSubPackets.isPrimaryUserID .filter { (_, certification) -> certification.hashedSubPackets.isPrimaryUserID }
}.entries.maxByOrNull { (_, certification) -> .entries
certification.creationTime .maxByOrNull { (_, certification) -> certification.creationTime }
}?.key ?: signatures.userIdCertifications.keys.firstOrNull() ?.key
?: signatures.userIdCertifications.keys.firstOrNull()
} }
/** /** Return true, if the primary user-ID, as well as the given user-ID are valid and bound. */
* Return true, if the primary user-ID, as well as the given user-ID are valid and bound.
*/
fun isUserIdValid(userId: CharSequence) = fun isUserIdValid(userId: CharSequence) =
if (primaryUserId == null) { if (primaryUserId == null) {
false false
} else { } else {
isUserIdBound(primaryUserId) && (if (userId == primaryUserId) true else isUserIdBound(userId)) isUserIdBound(primaryUserId) &&
(if (userId == primaryUserId) true else isUserIdBound(userId))
} }
/** /** Return true, if the given user-ID is validly bound. */
* Return true, if the given user-ID is validly bound.
*/
fun isUserIdBound(userId: CharSequence) = fun isUserIdBound(userId: CharSequence) =
signatures.userIdCertifications[userId]?.let { sig -> signatures.userIdCertifications[userId]?.let { sig ->
if (sig.isExpired(referenceDate)) { if (sig.isExpired(referenceDate)) {
@ -648,50 +663,42 @@ class KeyRingInfo(
if (rev.isHardRevocation) { if (rev.isHardRevocation) {
return false // hard revoked -> invalid return false // hard revoked -> invalid
} }
sig.creationTime > rev.creationTime// re-certification after soft revocation? sig.creationTime > rev.creationTime // re-certification after soft revocation?
} ?: true // certification, but no revocation }
} ?: false // no certification ?: true // certification, but no revocation
}
?: false // no certification
/** /** [HashAlgorithm] preferences of the given user-ID. */
* [HashAlgorithm] preferences of the given user-ID.
*/
fun getPreferredHashAlgorithms(userId: CharSequence): Set<HashAlgorithm> { fun getPreferredHashAlgorithms(userId: CharSequence): Set<HashAlgorithm> {
return getKeyAccessor(userId, keyId).preferredHashAlgorithms return getKeyAccessor(userId, keyId).preferredHashAlgorithms
} }
/** /** [HashAlgorithm] preferences of the given key. */
* [HashAlgorithm] preferences of the given key.
*/
fun getPreferredHashAlgorithms(keyId: Long): Set<HashAlgorithm> { fun getPreferredHashAlgorithms(keyId: Long): Set<HashAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredHashAlgorithms
} }
/** /** [SymmetricKeyAlgorithm] preferences of the given user-ID. */
* [SymmetricKeyAlgorithm] preferences of the given user-ID.
*/
fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set<SymmetricKeyAlgorithm> { fun getPreferredSymmetricKeyAlgorithms(userId: CharSequence): Set<SymmetricKeyAlgorithm> {
return getKeyAccessor(userId, keyId).preferredSymmetricKeyAlgorithms return getKeyAccessor(userId, keyId).preferredSymmetricKeyAlgorithms
} }
/** /** [SymmetricKeyAlgorithm] preferences of the given key. */
* [SymmetricKeyAlgorithm] preferences of the given key.
*/
fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set<SymmetricKeyAlgorithm> { fun getPreferredSymmetricKeyAlgorithms(keyId: Long): Set<SymmetricKeyAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredSymmetricKeyAlgorithms return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId))
.preferredSymmetricKeyAlgorithms
} }
/** /** [CompressionAlgorithm] preferences of the given user-ID. */
* [CompressionAlgorithm] preferences of the given user-ID.
*/
fun getPreferredCompressionAlgorithms(userId: CharSequence): Set<CompressionAlgorithm> { fun getPreferredCompressionAlgorithms(userId: CharSequence): Set<CompressionAlgorithm> {
return getKeyAccessor(userId, keyId).preferredCompressionAlgorithms return getKeyAccessor(userId, keyId).preferredCompressionAlgorithms
} }
/** /** [CompressionAlgorithm] preferences of the given key. */
* [CompressionAlgorithm] preferences of the given key.
*/
fun getPreferredCompressionAlgorithms(keyId: Long): Set<CompressionAlgorithm> { fun getPreferredCompressionAlgorithms(keyId: Long): Set<CompressionAlgorithm> {
return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId)).preferredCompressionAlgorithms return KeyAccessor.SubKey(this, SubkeyIdentifier(keys, keyId))
.preferredCompressionAlgorithms
} }
val isUsableForThirdPartyCertification: Boolean = val isUsableForThirdPartyCertification: Boolean =
@ -699,7 +706,8 @@ class KeyRingInfo(
private fun getKeyAccessor(userId: CharSequence?, keyId: Long): KeyAccessor { private fun getKeyAccessor(userId: CharSequence?, keyId: Long): KeyAccessor {
if (getPublicKey(keyId) == null) { if (getPublicKey(keyId) == null) {
throw NoSuchElementException("No subkey with key-id ${keyId.openPgpKeyId()} found on this key.") throw NoSuchElementException(
"No subkey with key-id ${keyId.openPgpKeyId()} found on this key.")
} }
if (userId != null && !userIds.contains(userId)) { if (userId != null && !userIds.contains(userId)) {
throw NoSuchElementException("No user-id '$userId' found on this key.") throw NoSuchElementException("No user-id '$userId' found on this key.")
@ -713,26 +721,24 @@ class KeyRingInfo(
companion object { companion object {
/** /** Evaluate the key for the given signature. */
* Evaluate the key for the given signature.
*/
@JvmStatic @JvmStatic
fun evaluateForSignature(keys: PGPKeyRing, signature: PGPSignature) = KeyRingInfo(keys, signature.creationTime!!) 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_FROM_USERID =
private val PATTERN_EMAIL_EXPLICIT = "^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern() "<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>".toPattern()
private val PATTERN_EMAIL_EXPLICIT =
@JvmStatic "^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$".toPattern()
private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java)
@JvmStatic private val LOGGER = LoggerFactory.getLogger(KeyRingInfo::class.java)
} }
private class Signatures( private class Signatures(val keys: PGPKeyRing, val referenceDate: Date, val policy: Policy) {
val keys: PGPKeyRing, val primaryKeyRevocation: PGPSignature? =
val referenceDate: Date, SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate)
val policy: Policy) { val primaryKeySelfSignature: PGPSignature? =
val primaryKeyRevocation: PGPSignature? = SignaturePicker.pickCurrentRevocationSelfSignature(keys, policy, referenceDate) SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate)
val primaryKeySelfSignature: PGPSignature? = SignaturePicker.pickLatestDirectKeySignature(keys, policy, referenceDate)
val userIdRevocations = mutableMapOf<String, PGPSignature>() val userIdRevocations = mutableMapOf<String, PGPSignature>()
val userIdCertifications = mutableMapOf<String, PGPSignature>() val userIdCertifications = mutableMapOf<String, PGPSignature>()
val subkeyRevocations = mutableMapOf<Long, PGPSignature>() val subkeyRevocations = mutableMapOf<Long, PGPSignature>()
@ -740,20 +746,20 @@ class KeyRingInfo(
init { init {
KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.publicKey).forEach { userId -> KeyRingUtils.getUserIdsIgnoringInvalidUTF8(keys.publicKey).forEach { userId ->
SignaturePicker.pickCurrentUserIdRevocationSignature(keys, userId, policy, referenceDate)?.let { SignaturePicker.pickCurrentUserIdRevocationSignature(
userIdRevocations[userId] = it keys, userId, policy, referenceDate)
} ?.let { userIdRevocations[userId] = it }
SignaturePicker.pickLatestUserIdCertificationSignature(keys, userId, policy, referenceDate)?.let { SignaturePicker.pickLatestUserIdCertificationSignature(
userIdCertifications[userId] = it keys, userId, policy, referenceDate)
} ?.let { userIdCertifications[userId] = it }
} }
keys.publicKeys.asSequence().drop(1).forEach { subkey -> keys.publicKeys.asSequence().drop(1).forEach { subkey ->
SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(keys, subkey, policy, referenceDate)?.let { SignaturePicker.pickCurrentSubkeyBindingRevocationSignature(
subkeyRevocations[subkey.keyID] = it keys, subkey, policy, referenceDate)
} ?.let { subkeyRevocations[subkey.keyID] = it }
SignaturePicker.pickLatestSubkeyBindingSignature(keys, subkey, policy, referenceDate)?.let { SignaturePicker.pickLatestSubkeyBindingSignature(
subkeyBindings[subkey.keyID] = it keys, subkey, policy, referenceDate)
} ?.let { subkeyBindings[subkey.keyID] = it }
} }
} }
} }

View File

@ -4,6 +4,10 @@
package org.pgpainless.key.modification.secretkeyring package org.pgpainless.key.modification.secretkeyring
import java.util.*
import java.util.function.Predicate
import javax.annotation.Nonnull
import kotlin.NoSuchElementException
import org.bouncycastle.bcpg.sig.KeyExpirationTime import org.bouncycastle.bcpg.sig.KeyExpirationTime
import org.bouncycastle.extensions.getKeyExpirationDate import org.bouncycastle.extensions.getKeyExpirationDate
import org.bouncycastle.extensions.publicKeyAlgorithm import org.bouncycastle.extensions.publicKeyAlgorithm
@ -30,19 +34,17 @@ import org.pgpainless.signature.builder.*
import org.pgpainless.signature.subpackets.* import org.pgpainless.signature.subpackets.*
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import org.pgpainless.util.selection.userid.SelectUserId import org.pgpainless.util.selection.userid.SelectUserId
import java.util.*
import java.util.function.Predicate
import javax.annotation.Nonnull
import kotlin.NoSuchElementException
class SecretKeyRingEditor( class SecretKeyRingEditor(
var secretKeyRing: PGPSecretKeyRing, var secretKeyRing: PGPSecretKeyRing,
override val referenceTime: Date = Date() override val referenceTime: Date = Date()
) : SecretKeyRingEditorInterface { ) : SecretKeyRingEditorInterface {
override fun addUserId(userId: CharSequence, override fun addUserId(
userId: CharSequence,
callback: SelfSignatureSubpackets.Callback?, callback: SelfSignatureSubpackets.Callback?,
protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
val sanitizedUserId = sanitizeUserId(userId).toString() val sanitizedUserId = sanitizeUserId(userId).toString()
val primaryKey = secretKeyRing.secretKey val primaryKey = secretKeyRing.secretKey
@ -51,14 +53,25 @@ class SecretKeyRingEditor(
"User-ID $userId is hard revoked and cannot be re-certified." "User-ID $userId is hard revoked and cannot be re-certified."
} }
val (hashAlgorithmPreferences, symmetricKeyAlgorithmPreferences, compressionAlgorithmPreferences) = try { val (
Triple(info.preferredHashAlgorithms, info.preferredSymmetricKeyAlgorithms, info.preferredCompressionAlgorithms) hashAlgorithmPreferences,
} catch (e : IllegalStateException) { // missing user-id sig symmetricKeyAlgorithmPreferences,
compressionAlgorithmPreferences) =
try {
Triple(
info.preferredHashAlgorithms,
info.preferredSymmetricKeyAlgorithms,
info.preferredCompressionAlgorithms)
} catch (e: IllegalStateException) { // missing user-id sig
val algorithmSuite = AlgorithmSuite.defaultAlgorithmSuite val algorithmSuite = AlgorithmSuite.defaultAlgorithmSuite
Triple(algorithmSuite.hashAlgorithms, algorithmSuite.symmetricKeyAlgorithms, algorithmSuite.compressionAlgorithms) Triple(
algorithmSuite.hashAlgorithms,
algorithmSuite.symmetricKeyAlgorithms,
algorithmSuite.compressionAlgorithms)
} }
val builder = SelfSignatureBuilder(primaryKey, protector).apply { val builder =
SelfSignatureBuilder(primaryKey, protector).apply {
hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setSignatureCreationTime(referenceTime)
setSignatureType(SignatureType.POSITIVE_CERTIFICATION) setSignatureType(SignatureType.POSITIVE_CERTIFICATION)
} }
@ -70,66 +83,109 @@ class SecretKeyRingEditor(
setFeatures(Feature.MODIFICATION_DETECTION) setFeatures(Feature.MODIFICATION_DETECTION)
} }
builder.applyCallback(callback) builder.applyCallback(callback)
secretKeyRing = injectCertification(secretKeyRing, sanitizedUserId, builder.build(primaryKey.publicKey, sanitizedUserId)) secretKeyRing =
injectCertification(
secretKeyRing,
sanitizedUserId,
builder.build(primaryKey.publicKey, sanitizedUserId))
return this return this
} }
override fun addPrimaryUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { override fun addPrimaryUserId(
userId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
val uid = sanitizeUserId(userId) val uid = sanitizeUserId(userId)
val primaryKey = secretKeyRing.publicKey val primaryKey = secretKeyRing.publicKey
var info = inspectKeyRing(secretKeyRing, referenceTime) var info = inspectKeyRing(secretKeyRing, referenceTime)
val primaryUserId = info.primaryUserId val primaryUserId = info.primaryUserId
val signature = if (primaryUserId == null) info.latestDirectKeySelfSignature else info.getLatestUserIdCertification(primaryUserId) val signature =
if (primaryUserId == null) info.latestDirectKeySelfSignature
else info.getLatestUserIdCertification(primaryUserId)
val previousKeyExpiration = signature?.getKeyExpirationDate(primaryKey.creationTime) val previousKeyExpiration = signature?.getKeyExpirationDate(primaryKey.creationTime)
// Add new primary user-id signature // Add new primary user-id signature
addUserId(uid, object : SelfSignatureSubpackets.Callback { addUserId(
uid,
object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) {
hashedSubpackets.apply { hashedSubpackets.apply {
setPrimaryUserId() setPrimaryUserId()
if (previousKeyExpiration != null) setKeyExpirationTime(primaryKey, previousKeyExpiration) if (previousKeyExpiration != null)
setKeyExpirationTime(primaryKey, previousKeyExpiration)
else setKeyExpirationTime(null) else setKeyExpirationTime(null)
} }
} }
}, protector) },
protector)
// unmark previous primary user-ids to be non-primary // unmark previous primary user-ids to be non-primary
info = inspectKeyRing(secretKeyRing, referenceTime) info = inspectKeyRing(secretKeyRing, referenceTime)
info.validAndExpiredUserIds.filterNot { it == uid }.forEach { otherUserId -> info.validAndExpiredUserIds
if (info.getLatestUserIdCertification(otherUserId)!!.hashedSubPackets.isPrimaryUserID) { .filterNot { it == uid }
.forEach { otherUserId ->
if (info
.getLatestUserIdCertification(otherUserId)!!
.hashedSubPackets
.isPrimaryUserID) {
// We need to unmark this user-id as primary // We need to unmark this user-id as primary
addUserId(otherUserId, object : SelfSignatureSubpackets.Callback { addUserId(
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { otherUserId,
object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(
hashedSubpackets: SelfSignatureSubpackets
) {
hashedSubpackets.apply { hashedSubpackets.apply {
setPrimaryUserId(null) setPrimaryUserId(null)
setKeyExpirationTime(null) // non-primary setKeyExpirationTime(null) // non-primary
} }
} }
}, protector) },
protector)
} }
} }
return this return this
} }
@Deprecated("Use of SelectUserId class is deprecated.", replaceWith = ReplaceWith("removeUserId(protector, predicate)")) @Deprecated(
override fun removeUserId(selector: SelectUserId, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { "Use of SelectUserId class is deprecated.",
return revokeUserIds(selector, protector, RevocationAttributes.createCertificateRevocation() replaceWith = ReplaceWith("removeUserId(protector, predicate)"))
override fun removeUserId(
selector: SelectUserId,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
return revokeUserIds(
selector,
protector,
RevocationAttributes.createCertificateRevocation()
.withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID)
.withoutDescription()) .withoutDescription())
} }
override fun removeUserId(protector: SecretKeyRingProtector, predicate: (String) -> Boolean): SecretKeyRingEditorInterface { override fun removeUserId(
return revokeUserIds(protector, RevocationAttributes.createCertificateRevocation() protector: SecretKeyRingProtector,
predicate: (String) -> Boolean
): SecretKeyRingEditorInterface {
return revokeUserIds(
protector,
RevocationAttributes.createCertificateRevocation()
.withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) .withReason(RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID)
.withoutDescription(), .withoutDescription(),
predicate) predicate)
} }
override fun removeUserId(userId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { override fun removeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
return removeUserId(protector) { uid -> userId == uid } return removeUserId(protector) { uid -> userId == uid }
} }
override fun replaceUserId(oldUserId: CharSequence, newUserId: CharSequence, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { override fun replaceUserId(
oldUserId: CharSequence,
newUserId: CharSequence,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
val oldUID = sanitizeUserId(oldUserId) val oldUID = sanitizeUserId(oldUserId)
val newUID = sanitizeUserId(newUserId) val newUID = sanitizeUserId(newUserId)
require(oldUID.isNotBlank()) { "Old user-ID cannot be empty." } require(oldUID.isNotBlank()) { "Old user-ID cannot be empty." }
@ -137,117 +193,179 @@ class SecretKeyRingEditor(
val info = inspectKeyRing(secretKeyRing, referenceTime) val info = inspectKeyRing(secretKeyRing, referenceTime)
if (!info.isUserIdValid(oldUID)) { if (!info.isUserIdValid(oldUID)) {
throw NoSuchElementException("Key does not carry user-ID '$oldUID', or it is not valid.") throw NoSuchElementException(
"Key does not carry user-ID '$oldUID', or it is not valid.")
} }
val oldCertification = info.getLatestUserIdCertification(oldUID) val oldCertification =
info.getLatestUserIdCertification(oldUID)
?: throw AssertionError("Certification for old user-ID MUST NOT be null.") ?: throw AssertionError("Certification for old user-ID MUST NOT be null.")
addUserId(newUID, object : SelfSignatureSubpackets.Callback { addUserId(
newUID,
object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) {
SignatureSubpacketsHelper.applyFrom(oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets) SignatureSubpacketsHelper.applyFrom(
if (oldUID == info.primaryUserId && !oldCertification.hashedSubPackets.isPrimaryUserID) { oldCertification.hashedSubPackets, hashedSubpackets as SignatureSubpackets)
if (oldUID == info.primaryUserId &&
!oldCertification.hashedSubPackets.isPrimaryUserID) {
hashedSubpackets.setPrimaryUserId() hashedSubpackets.setPrimaryUserId()
} }
} }
override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets) { override fun modifyUnhashedSubpackets(unhashedSubpackets: SelfSignatureSubpackets) {
SignatureSubpacketsHelper.applyFrom(oldCertification.unhashedSubPackets, unhashedSubpackets as SignatureSubpackets) SignatureSubpacketsHelper.applyFrom(
oldCertification.unhashedSubPackets,
unhashedSubpackets as SignatureSubpackets)
} }
}, protector) },
protector)
return revokeUserId(oldUID, protector) return revokeUserId(oldUID, protector)
} }
override fun addSubKey(keySpec: KeySpec, override fun addSubKey(
keySpec: KeySpec,
subkeyPassphrase: Passphrase, subkeyPassphrase: Passphrase,
protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { protector: SecretKeyRingProtector
val callback = object : SelfSignatureSubpackets.Callback { ): SecretKeyRingEditorInterface {
val callback =
object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) {
SignatureSubpacketsHelper.applyFrom(keySpec.subpackets, hashedSubpackets as SignatureSubpackets) SignatureSubpacketsHelper.applyFrom(
keySpec.subpackets, hashedSubpackets as SignatureSubpackets)
} }
} }
return addSubKey(keySpec, subkeyPassphrase, callback, protector) return addSubKey(keySpec, subkeyPassphrase, callback, protector)
} }
override fun addSubKey(keySpec: KeySpec, override fun addSubKey(
keySpec: KeySpec,
subkeyPassphrase: Passphrase, subkeyPassphrase: Passphrase,
callback: SelfSignatureSubpackets.Callback?, callback: SelfSignatureSubpackets.Callback?,
protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
val keyPair = KeyRingBuilder.generateKeyPair(keySpec) val keyPair = KeyRingBuilder.generateKeyPair(keySpec)
val subkeyProtector = PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyID, subkeyPassphrase) val subkeyProtector =
PasswordBasedSecretKeyRingProtector.forKeyId(keyPair.keyID, subkeyPassphrase)
val keyFlags = KeyFlag.fromBitmask(keySpec.subpackets.keyFlags).toMutableList() val keyFlags = KeyFlag.fromBitmask(keySpec.subpackets.keyFlags).toMutableList()
return addSubKey(keyPair, callback, subkeyProtector, protector, keyFlags.removeFirst(), *keyFlags.toTypedArray()) return addSubKey(
keyPair,
callback,
subkeyProtector,
protector,
keyFlags.removeFirst(),
*keyFlags.toTypedArray())
} }
override fun addSubKey(subkey: PGPKeyPair, override fun addSubKey(
subkey: PGPKeyPair,
callback: SelfSignatureSubpackets.Callback?, callback: SelfSignatureSubpackets.Callback?,
subkeyProtector: SecretKeyRingProtector, subkeyProtector: SecretKeyRingProtector,
primaryKeyProtector: SecretKeyRingProtector, primaryKeyProtector: SecretKeyRingProtector,
keyFlag: KeyFlag, keyFlag: KeyFlag,
vararg keyFlags: KeyFlag): SecretKeyRingEditorInterface { vararg keyFlags: KeyFlag
): SecretKeyRingEditorInterface {
val flags = listOf(keyFlag).plus(keyFlags) val flags = listOf(keyFlag).plus(keyFlags)
val subkeyAlgorithm = subkey.publicKey.publicKeyAlgorithm val subkeyAlgorithm = subkey.publicKey.publicKeyAlgorithm
SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm) SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm)
val bitStrength = subkey.publicKey.bitStrength val bitStrength = subkey.publicKey.bitStrength
require(PGPainless.getPolicy().publicKeyAlgorithmPolicy.isAcceptable(subkeyAlgorithm, bitStrength)) { require(
PGPainless.getPolicy()
.publicKeyAlgorithmPolicy
.isAcceptable(subkeyAlgorithm, bitStrength)) {
"Public key algorithm policy violation: $subkeyAlgorithm with bit strength $bitStrength is not acceptable." "Public key algorithm policy violation: $subkeyAlgorithm with bit strength $bitStrength is not acceptable."
} }
val primaryKey = secretKeyRing.secretKey val primaryKey = secretKeyRing.secretKey
val info = inspectKeyRing(secretKeyRing, referenceTime) val info = inspectKeyRing(secretKeyRing, referenceTime)
val hashAlgorithm = HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) val hashAlgorithm =
HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy())
.negotiateHashAlgorithm(info.preferredHashAlgorithms) .negotiateHashAlgorithm(info.preferredHashAlgorithms)
var secretSubkey = PGPSecretKey(subkey.privateKey, subkey.publicKey, var secretSubkey =
PGPSecretKey(
subkey.privateKey,
subkey.publicKey,
ImplementationFactory.getInstance().v4FingerprintCalculator, ImplementationFactory.getInstance().v4FingerprintCalculator,
false, subkeyProtector.getEncryptor(subkey.keyID)) false,
val skBindingBuilder = SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm) subkeyProtector.getEncryptor(subkey.keyID))
val skBindingBuilder =
SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm)
skBindingBuilder.apply { skBindingBuilder.apply {
hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setSignatureCreationTime(referenceTime)
hashedSubpackets.setKeyFlags(flags) hashedSubpackets.setKeyFlags(flags)
if (subkeyAlgorithm.isSigningCapable()) { if (subkeyAlgorithm.isSigningCapable()) {
val pkBindingBuilder = PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm) val pkBindingBuilder =
PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm)
pkBindingBuilder.hashedSubpackets.setSignatureCreationTime(referenceTime) pkBindingBuilder.hashedSubpackets.setSignatureCreationTime(referenceTime)
hashedSubpackets.addEmbeddedSignature(pkBindingBuilder.build(primaryKey.publicKey)) hashedSubpackets.addEmbeddedSignature(pkBindingBuilder.build(primaryKey.publicKey))
} }
applyCallback(callback) applyCallback(callback)
} }
secretSubkey = KeyRingUtils.secretKeyPlusSignature(secretSubkey, skBindingBuilder.build(secretSubkey.publicKey)) secretSubkey =
KeyRingUtils.secretKeyPlusSignature(
secretSubkey, skBindingBuilder.build(secretSubkey.publicKey))
secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey) secretKeyRing = KeyRingUtils.keysPlusSecretKey(secretKeyRing, secretSubkey)
return this return this
} }
override fun revoke(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { override fun revoke(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): SecretKeyRingEditorInterface {
return revoke(protector, callbackFromRevocationAttributes(revocationAttributes)) return revoke(protector, callbackFromRevocationAttributes(revocationAttributes))
} }
override fun revoke(protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { override fun revoke(
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface {
return revokeSubKey(secretKeyRing.secretKey.keyID, protector, callback) return revokeSubKey(secretKeyRing.secretKey.keyID, protector, callback)
} }
override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { override fun revokeSubKey(
return revokeSubKey(subkeyId, protector, callbackFromRevocationAttributes(revocationAttributes)) subkeyId: Long,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): SecretKeyRingEditorInterface {
return revokeSubKey(
subkeyId, protector, callbackFromRevocationAttributes(revocationAttributes))
} }
override fun revokeSubKey(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { override fun revokeSubKey(
subkeyId: Long,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface {
val revokeeSubKey = secretKeyRing.requirePublicKey(subkeyId) val revokeeSubKey = secretKeyRing.requirePublicKey(subkeyId)
val subkeyRevocation = generateRevocation(protector, revokeeSubKey, callback) val subkeyRevocation = generateRevocation(protector, revokeeSubKey, callback)
secretKeyRing = injectCertification(secretKeyRing, revokeeSubKey, subkeyRevocation) secretKeyRing = injectCertification(secretKeyRing, revokeeSubKey, subkeyRevocation)
return this return this
} }
override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): SecretKeyRingEditorInterface { override fun revokeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): SecretKeyRingEditorInterface {
if (revocationAttributes != null) { if (revocationAttributes != null) {
require(revocationAttributes.reason == RevocationAttributes.Reason.NO_REASON || require(
revocationAttributes.reason == RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) { revocationAttributes.reason == RevocationAttributes.Reason.NO_REASON ||
revocationAttributes.reason ==
RevocationAttributes.Reason.USER_ID_NO_LONGER_VALID) {
"Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID" "Revocation reason must either be NO_REASON or USER_ID_NO_LONGER_VALID"
} }
} }
return revokeUserId(userId, protector, object : RevocationSignatureSubpackets.Callback { return revokeUserId(
override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { userId,
protector,
object : RevocationSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(
hashedSubpackets: RevocationSignatureSubpackets
) {
if (revocationAttributes != null) { if (revocationAttributes != null) {
hashedSubpackets.setRevocationReason(false, revocationAttributes) hashedSubpackets.setRevocationReason(false, revocationAttributes)
} }
@ -255,30 +373,50 @@ class SecretKeyRingEditor(
}) })
} }
override fun revokeUserId(userId: CharSequence, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { override fun revokeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): SecretKeyRingEditorInterface {
return revokeUserIds(protector, callback, SelectUserId.exactMatch(sanitizeUserId(userId))) return revokeUserIds(protector, callback, SelectUserId.exactMatch(sanitizeUserId(userId)))
} }
override fun revokeUserIds(protector: SecretKeyRingProtector, override fun revokeUserIds(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?, revocationAttributes: RevocationAttributes?,
predicate: (String) -> Boolean): SecretKeyRingEditorInterface { predicate: (String) -> Boolean
return revokeUserIds(protector, object : RevocationSignatureSubpackets.Callback { ): SecretKeyRingEditorInterface {
override fun modifyHashedSubpackets(hashedSubpackets: RevocationSignatureSubpackets) { return revokeUserIds(
if (revocationAttributes != null) hashedSubpackets.setRevocationReason(revocationAttributes) protector,
object : RevocationSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(
hashedSubpackets: RevocationSignatureSubpackets
) {
if (revocationAttributes != null)
hashedSubpackets.setRevocationReason(revocationAttributes)
} }
}, predicate) },
predicate)
} }
override fun revokeUserIds(protector: SecretKeyRingProtector, override fun revokeUserIds(
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?, callback: RevocationSignatureSubpackets.Callback?,
predicate: (String) -> Boolean): SecretKeyRingEditorInterface { predicate: (String) -> Boolean
selectUserIds(predicate).also { ): SecretKeyRingEditorInterface {
if (it.isEmpty()) throw NoSuchElementException("No matching user-ids found on the key.") selectUserIds(predicate)
}.forEach { userId -> doRevokeUserId(userId, protector, callback) } .also {
if (it.isEmpty())
throw NoSuchElementException("No matching user-ids found on the key.")
}
.forEach { userId -> doRevokeUserId(userId, protector, callback) }
return this return this
} }
override fun setExpirationDate(expiration: Date?, protector: SecretKeyRingProtector): SecretKeyRingEditorInterface { override fun setExpirationDate(
expiration: Date?,
protector: SecretKeyRingProtector
): SecretKeyRingEditorInterface {
require(secretKeyRing.secretKey.isMasterKey) { require(secretKeyRing.secretKey.isMasterKey) {
"OpenPGP key does not appear to contain a primary secret key." "OpenPGP key does not appear to contain a primary secret key."
} }
@ -286,14 +424,19 @@ class SecretKeyRingEditor(
val prevDirectKeySig = getPreviousDirectKeySignature() val prevDirectKeySig = getPreviousDirectKeySignature()
// reissue direct key sig // reissue direct key sig
if (prevDirectKeySig != null) { if (prevDirectKeySig != null) {
secretKeyRing = injectCertification(secretKeyRing, secretKeyRing.publicKey, secretKeyRing =
injectCertification(
secretKeyRing,
secretKeyRing.publicKey,
reissueDirectKeySignature(expiration, protector, prevDirectKeySig)) reissueDirectKeySignature(expiration, protector, prevDirectKeySig))
} }
val primaryUserId = inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId() val primaryUserId =
inspectKeyRing(secretKeyRing, referenceTime).getPossiblyExpiredPrimaryUserId()
if (primaryUserId != null) { if (primaryUserId != null) {
val prevUserIdSig = getPreviousUserIdSignatures(primaryUserId) val prevUserIdSig = getPreviousUserIdSignatures(primaryUserId)
val userIdSig = reissuePrimaryUserIdSig(expiration, protector, primaryUserId, prevUserIdSig!!) val userIdSig =
reissuePrimaryUserIdSig(expiration, protector, primaryUserId, prevUserIdSig!!)
secretKeyRing = injectCertification(secretKeyRing, primaryUserId, userIdSig) secretKeyRing = injectCertification(secretKeyRing, primaryUserId, userIdSig)
} }
@ -303,9 +446,15 @@ class SecretKeyRingEditor(
continue continue
} }
val prevUserIdSig = info.getLatestUserIdCertification(userId) ?: throw AssertionError("A valid user-id shall never have no user-id signature.") val prevUserIdSig =
info.getLatestUserIdCertification(userId)
?: throw AssertionError(
"A valid user-id shall never have no user-id signature.")
if (prevUserIdSig.hashedSubPackets.isPrimaryUserID) { if (prevUserIdSig.hashedSubPackets.isPrimaryUserID) {
secretKeyRing = injectCertification(secretKeyRing, primaryUserId!!, secretKeyRing =
injectCertification(
secretKeyRing,
primaryUserId!!,
reissueNonPrimaryUserId(protector, userId, prevUserIdSig)) reissueNonPrimaryUserId(protector, userId, prevUserIdSig))
} }
} }
@ -313,7 +462,10 @@ class SecretKeyRingEditor(
return this return this
} }
override fun createMinimalRevocationCertificate(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPPublicKeyRing { override fun createMinimalRevocationCertificate(
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPPublicKeyRing {
// Check reason // Check reason
if (revocationAttributes != null) { if (revocationAttributes != null) {
require(RevocationAttributes.Reason.isKeyRevocation(revocationAttributes.reason)) { require(RevocationAttributes.Reason.isKeyRevocation(revocationAttributes.reason)) {
@ -328,30 +480,67 @@ class SecretKeyRingEditor(
return PGPPublicKeyRing(listOf(primaryKey)) return PGPPublicKeyRing(listOf(primaryKey))
} }
override fun createRevocation(protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { override fun createRevocation(
return generateRevocation(protector, secretKeyRing.publicKey, callbackFromRevocationAttributes(revocationAttributes)) protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature {
return generateRevocation(
protector,
secretKeyRing.publicKey,
callbackFromRevocationAttributes(revocationAttributes))
} }
override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { override fun createRevocation(
return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callbackFromRevocationAttributes(revocationAttributes)) subkeyId: Long,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature {
return generateRevocation(
protector,
secretKeyRing.requirePublicKey(subkeyId),
callbackFromRevocationAttributes(revocationAttributes))
} }
override fun createRevocation(subkeyId: Long, protector: SecretKeyRingProtector, callback: RevocationSignatureSubpackets.Callback?): PGPSignature { override fun createRevocation(
subkeyId: Long,
protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?
): PGPSignature {
return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callback) return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyId), callback)
} }
override fun createRevocation(subkeyFingerprint: OpenPgpFingerprint, protector: SecretKeyRingProtector, revocationAttributes: RevocationAttributes?): PGPSignature { override fun createRevocation(
return generateRevocation(protector, secretKeyRing.requirePublicKey(subkeyFingerprint), callbackFromRevocationAttributes(revocationAttributes)) subkeyFingerprint: OpenPgpFingerprint,
protector: SecretKeyRingProtector,
revocationAttributes: RevocationAttributes?
): PGPSignature {
return generateRevocation(
protector,
secretKeyRing.requirePublicKey(subkeyFingerprint),
callbackFromRevocationAttributes(revocationAttributes))
} }
override fun changePassphraseFromOldPassphrase(oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { override fun changePassphraseFromOldPassphrase(
return WithKeyRingEncryptionSettingsImpl(this, null, oldPassphrase: Passphrase,
PasswordBasedSecretKeyRingProtector(oldProtectionSettings, SolitaryPassphraseProvider(oldPassphrase))) oldProtectionSettings: KeyRingProtectionSettings
): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings {
return WithKeyRingEncryptionSettingsImpl(
this,
null,
PasswordBasedSecretKeyRingProtector(
oldProtectionSettings, SolitaryPassphraseProvider(oldPassphrase)))
} }
override fun changeSubKeyPassphraseFromOldPassphrase(keyId: Long, oldPassphrase: Passphrase, oldProtectionSettings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { override fun changeSubKeyPassphraseFromOldPassphrase(
return WithKeyRingEncryptionSettingsImpl(this, keyId, keyId: Long,
CachingSecretKeyRingProtector(mapOf(keyId to oldPassphrase), oldProtectionSettings, null)) oldPassphrase: Passphrase,
oldProtectionSettings: KeyRingProtectionSettings
): SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings {
return WithKeyRingEncryptionSettingsImpl(
this,
keyId,
CachingSecretKeyRingProtector(
mapOf(keyId to oldPassphrase), oldProtectionSettings, null))
} }
override fun done(): PGPSecretKeyRing { override fun done(): PGPSecretKeyRing {
@ -372,9 +561,11 @@ class SecretKeyRingEditor(
} }
} }
private fun generateRevocation(protector: SecretKeyRingProtector, private fun generateRevocation(
protector: SecretKeyRingProtector,
revokeeSubkey: PGPPublicKey, revokeeSubkey: PGPPublicKey,
callback: RevocationSignatureSubpackets.Callback?): PGPSignature { callback: RevocationSignatureSubpackets.Callback?
): PGPSignature {
val primaryKey = secretKeyRing.secretKey val primaryKey = secretKeyRing.secretKey
val signatureType = val signatureType =
if (revokeeSubkey.isMasterKey) SignatureType.KEY_REVOCATION if (revokeeSubkey.isMasterKey) SignatureType.KEY_REVOCATION
@ -385,19 +576,24 @@ class SecretKeyRingEditor(
.build(revokeeSubkey) .build(revokeeSubkey)
} }
private fun doRevokeUserId(userId: CharSequence, private fun doRevokeUserId(
userId: CharSequence,
protector: SecretKeyRingProtector, protector: SecretKeyRingProtector,
callback: RevocationSignatureSubpackets.Callback?): SecretKeyRingEditorInterface { callback: RevocationSignatureSubpackets.Callback?
RevocationSignatureBuilder(SignatureType.CERTIFICATION_REVOCATION, secretKeyRing.secretKey, protector).apply { ): SecretKeyRingEditorInterface {
RevocationSignatureBuilder(
SignatureType.CERTIFICATION_REVOCATION, secretKeyRing.secretKey, protector)
.apply {
hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setSignatureCreationTime(referenceTime)
applyCallback(callback) applyCallback(callback)
}.let { }
secretKeyRing = injectCertification(secretKeyRing, userId, it.build(userId.toString())) .let {
secretKeyRing =
injectCertification(secretKeyRing, userId, it.build(userId.toString()))
} }
return this return this
} }
private fun getPreviousDirectKeySignature(): PGPSignature? { private fun getPreviousDirectKeySignature(): PGPSignature? {
val info = inspectKeyRing(secretKeyRing, referenceTime) val info = inspectKeyRing(secretKeyRing, referenceTime)
return info.latestDirectKeySelfSignature return info.latestDirectKeySelfSignature
@ -412,10 +608,13 @@ class SecretKeyRingEditor(
private fun reissueNonPrimaryUserId( private fun reissueNonPrimaryUserId(
secretKeyRingProtector: SecretKeyRingProtector, secretKeyRingProtector: SecretKeyRingProtector,
userId: String, userId: String,
prevUserIdSig: PGPSignature): PGPSignature { prevUserIdSig: PGPSignature
val builder = SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) ): PGPSignature {
val builder =
SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig)
builder.hashedSubpackets.setSignatureCreationTime(referenceTime) builder.hashedSubpackets.setSignatureCreationTime(referenceTime)
builder.applyCallback(object : SelfSignatureSubpackets.Callback { builder.applyCallback(
object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) {
// unmark as primary // unmark as primary
hashedSubpackets.setPrimaryUserId(null) hashedSubpackets.setPrimaryUserId(null)
@ -429,41 +628,54 @@ class SecretKeyRingEditor(
expiration: Date?, expiration: Date?,
@Nonnull secretKeyRingProtector: SecretKeyRingProtector, @Nonnull secretKeyRingProtector: SecretKeyRingProtector,
@Nonnull primaryUserId: String, @Nonnull primaryUserId: String,
@Nonnull prevUserIdSig: PGPSignature): PGPSignature { @Nonnull prevUserIdSig: PGPSignature
): PGPSignature {
return SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig) return SelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevUserIdSig)
.apply { .apply {
hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setSignatureCreationTime(referenceTime)
applyCallback(object : SelfSignatureSubpackets.Callback { applyCallback(
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(
hashedSubpackets: SelfSignatureSubpackets
) {
if (expiration != null) { if (expiration != null) {
hashedSubpackets.setKeyExpirationTime(true, secretKeyRing.publicKey.creationTime, expiration) hashedSubpackets.setKeyExpirationTime(
true, secretKeyRing.publicKey.creationTime, expiration)
} else { } else {
hashedSubpackets.setKeyExpirationTime(KeyExpirationTime(true, 0)) hashedSubpackets.setKeyExpirationTime(KeyExpirationTime(true, 0))
} }
hashedSubpackets.setPrimaryUserId() hashedSubpackets.setPrimaryUserId()
} }
}) })
}.build(secretKeyRing.publicKey, primaryUserId) }
.build(secretKeyRing.publicKey, primaryUserId)
} }
@Throws(PGPException::class) @Throws(PGPException::class)
private fun reissueDirectKeySignature( private fun reissueDirectKeySignature(
expiration: Date?, expiration: Date?,
secretKeyRingProtector: SecretKeyRingProtector, secretKeyRingProtector: SecretKeyRingProtector,
prevDirectKeySig: PGPSignature): PGPSignature { prevDirectKeySig: PGPSignature
return DirectKeySelfSignatureBuilder(secretKeyRing.secretKey, secretKeyRingProtector, prevDirectKeySig) ): PGPSignature {
return DirectKeySelfSignatureBuilder(
secretKeyRing.secretKey, secretKeyRingProtector, prevDirectKeySig)
.apply { .apply {
hashedSubpackets.setSignatureCreationTime(referenceTime) hashedSubpackets.setSignatureCreationTime(referenceTime)
applyCallback(object : SelfSignatureSubpackets.Callback { applyCallback(
override fun modifyHashedSubpackets(hashedSubpackets: SelfSignatureSubpackets) { object : SelfSignatureSubpackets.Callback {
override fun modifyHashedSubpackets(
hashedSubpackets: SelfSignatureSubpackets
) {
if (expiration != null) { if (expiration != null) {
hashedSubpackets.setKeyExpirationTime(secretKeyRing.publicKey.creationTime, expiration) hashedSubpackets.setKeyExpirationTime(
secretKeyRing.publicKey.creationTime, expiration)
} else { } else {
hashedSubpackets.setKeyExpirationTime(null) hashedSubpackets.setKeyExpirationTime(null)
} }
} }
}) })
}.build(secretKeyRing.publicKey) }
.build(secretKeyRing.publicKey)
} }
private fun selectUserIds(predicate: Predicate<String>): List<String> = private fun selectUserIds(predicate: Predicate<String>): List<String> =
@ -472,13 +684,16 @@ class SecretKeyRingEditor(
private class WithKeyRingEncryptionSettingsImpl( private class WithKeyRingEncryptionSettingsImpl(
private val editor: SecretKeyRingEditor, private val editor: SecretKeyRingEditor,
private val keyId: Long?, private val keyId: Long?,
private val oldProtector: SecretKeyRingProtector) : SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings { private val oldProtector: SecretKeyRingProtector
) : SecretKeyRingEditorInterface.WithKeyRingEncryptionSettings {
override fun withSecureDefaultSettings(): SecretKeyRingEditorInterface.WithPassphrase { override fun withSecureDefaultSettings(): SecretKeyRingEditorInterface.WithPassphrase {
return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()) return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings())
} }
override fun withCustomSettings(settings: KeyRingProtectionSettings): SecretKeyRingEditorInterface.WithPassphrase { override fun withCustomSettings(
settings: KeyRingProtectionSettings
): SecretKeyRingEditorInterface.WithPassphrase {
return WithPassphraseImpl(editor, keyId, oldProtector, settings) return WithPassphraseImpl(editor, keyId, oldProtector, settings)
} }
} }
@ -491,7 +706,9 @@ class SecretKeyRingEditor(
) : SecretKeyRingEditorInterface.WithPassphrase { ) : SecretKeyRingEditorInterface.WithPassphrase {
override fun toNewPassphrase(passphrase: Passphrase): SecretKeyRingEditorInterface { override fun toNewPassphrase(passphrase: Passphrase): SecretKeyRingEditorInterface {
val protector = PasswordBasedSecretKeyRingProtector(newProtectionSettings, SolitaryPassphraseProvider(passphrase)) val protector =
PasswordBasedSecretKeyRingProtector(
newProtectionSettings, SolitaryPassphraseProvider(passphrase))
val secretKeys = changePassphrase(keyId, editor.secretKeyRing, oldProtector, protector) val secretKeys = changePassphrase(keyId, editor.secretKeyRing, oldProtector, protector)
editor.secretKeyRing = secretKeys editor.secretKeyRing = secretKeys
return editor return editor
@ -504,5 +721,4 @@ class SecretKeyRingEditor(
return editor return editor
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More