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

View File

@ -4,22 +4,18 @@
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 {
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 {
require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) {
"Provided long key-id does not match expected format. " +
"A long key-id consists of 16 hexadecimal characters."
"A long key-id consists of 16 hexadecimal characters."
}
// Calling toLong() only fails with a NumberFormatException.
// Therefore, we call toULong(16).toLong(), which seems to work.
return hexKeyId.toULong(16).toLong()
}
}

View File

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

View File

@ -13,12 +13,10 @@ import org.pgpainless.PGPainless
import org.pgpainless.key.OpenPgpFingerprint
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 =
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
this.getPublicKey(subkeyIdentifier.subkeyId) != null
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
this.getPublicKey(subkeyIdentifier.subkeyId) != null
/**
* Return true, if the [PGPKeyRing] contains a public key with the given key-ID.
@ -26,8 +24,7 @@ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
* @param keyId keyId
* @return true if key with the given key-ID is present, false otherwise
*/
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
this.getPublicKey(keyId) != null
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = this.getPublicKey(keyId) != null
/**
* Return true, if the [PGPKeyRing] contains a public key with the given fingerprint.
@ -36,7 +33,7 @@ fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
* @return true if key with the given fingerprint is present, false otherwise
*/
fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean =
this.getPublicKey(fingerprint) != null
this.getPublicKey(fingerprint) != null
/**
* Return the [PGPPublicKey] with the given [OpenPgpFingerprint] or null, if no such key is present.
@ -45,36 +42,33 @@ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean =
* @return public key
*/
fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
this.getPublicKey(fingerprint.bytes)
this.getPublicKey(fingerprint.bytes)
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 =
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].
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to
* identify the [PGPPublicKey] via its key-ID.
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
* the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
* subpacket to identify the [PGPPublicKey] via its key-ID.
*/
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
signature.fingerprint?.let { this.getPublicKey(it) } ?:
this.getPublicKey(signature.keyID)
signature.fingerprint?.let { this.getPublicKey(it) } ?: 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? =
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
get() = OpenPgpFingerprint.of(this)
/**
* Return this OpenPGP key as an ASCII armored String.
*/
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)
/** Return this OpenPGP key as an ASCII armored String. */
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
/**
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA],
* this method returns the name of the underlying elliptic curve.
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and
* [PublicKeyAlgorithm.EDDSA], this method returns the name of the underlying elliptic curve.
*
* For other key types or unknown curves, this method throws an [IllegalArgumentException].
*
@ -24,27 +24,29 @@ import org.pgpainless.key.generation.type.eddsa.EdDSACurve
*/
fun PGPPublicKey.getCurveName(): String {
PublicKeyAlgorithm.requireFromId(algorithm)
.let {
when (it) {
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
}
.let {
when (it) {
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
}
.let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID}
.let { it to ECUtil.getCurveName(it) }
.let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") }
}
.let {
if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName
else it.curveOID
}
.let { it to ECUtil.getCurveName(it) }
.let {
if (it.second != null) return it.second
else throw IllegalArgumentException("Unknown curve: ${it.first}")
}
}
/**
* Return the [PublicKeyAlgorithm] of this key.
*/
/** Return the [PublicKeyAlgorithm] of this key. */
val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm
get() = PublicKeyAlgorithm.requireFromId(algorithm)
/**
* Return the [OpenPgpFingerprint] of this key.
*/
/** Return the [OpenPgpFingerprint] of this key. */
val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this)

View File

@ -7,7 +7,6 @@ package org.bouncycastle.extensions
import org.bouncycastle.bcpg.S2K
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPrivateKey
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPSecretKey
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
import org.pgpainless.algorithm.PublicKeyAlgorithm
@ -28,7 +27,7 @@ import org.pgpainless.util.Passphrase
*/
@Throws(PGPException::class, KeyIntegrityException::class)
fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
UnlockSecretKey.unlockSecretKey(this, passphrase)
UnlockSecretKey.unlockSecretKey(this, passphrase)
/**
* Unlock the secret key to get its [PGPPrivateKey].
@ -39,8 +38,9 @@ fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
*/
@Throws(PGPException::class, KeyIntegrityException::class)
@JvmOverloads
fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey =
UnlockSecretKey.unlockSecretKey(this, protector)
fun PGPSecretKey.unlock(
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
): PGPPrivateKey = UnlockSecretKey.unlockSecretKey(this, protector)
/**
* 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)
/**
* Return the [PublicKeyAlgorithm] of this key.
*/
/** Return the [PublicKeyAlgorithm] of this key. */
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
get() = publicKey.publicKeyAlgorithm
/**
* Return the [OpenPgpFingerprint] of this key.
*/
/** Return the [OpenPgpFingerprint] of this key. */
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
get() = OpenPgpFingerprint.of(this)

View File

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

View File

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

View File

@ -4,6 +4,8 @@
package org.pgpainless
import java.io.OutputStream
import java.util.*
import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKeyRing
@ -19,8 +21,6 @@ import org.pgpainless.key.parsing.KeyRingReader
import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.policy.Policy
import org.pgpainless.util.ArmorUtils
import java.io.OutputStream
import java.util.*
class PGPainless private constructor() {
@ -28,25 +28,24 @@ class PGPainless private constructor() {
/**
* Generate a fresh OpenPGP key ring from predefined templates.
*
* @return templates
*/
@JvmStatic
fun generateKeyRing() = KeyRingTemplates()
@JvmStatic fun generateKeyRing() = KeyRingTemplates()
/**
* Build a custom OpenPGP key ring.
*
* @return builder
*/
@JvmStatic
fun buildKeyRing() = KeyRingBuilder()
@JvmStatic fun buildKeyRing() = KeyRingBuilder()
/**
* Read an existing OpenPGP key ring.
*
* @return builder
*/
@JvmStatic
fun readKeyRing() = KeyRingReader()
@JvmStatic fun readKeyRing() = KeyRingReader()
/**
* Extract a public key certificate from a secret key.
@ -56,10 +55,11 @@ class PGPainless private constructor() {
*/
@JvmStatic
fun extractCertificate(secretKey: PGPSecretKeyRing) =
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 updatedCopy updated, newer copy of the cert
@ -67,31 +67,27 @@ class PGPainless private constructor() {
* @throws PGPException in case of an error
*/
@JvmStatic
fun mergeCertificate(originalCopy: PGPPublicKeyRing,
updatedCopy: PGPPublicKeyRing) =
PGPPublicKeyRing.join(originalCopy, updatedCopy)
fun mergeCertificate(originalCopy: PGPPublicKeyRing, updatedCopy: PGPPublicKeyRing) =
PGPPublicKeyRing.join(originalCopy, updatedCopy)
/**
* Wrap a key or certificate in ASCII armor.
*
* @param key key or certificate
* @return ascii armored string
*
* @throws IOException in case of an error during the armoring process
*/
@JvmStatic
fun asciiArmor(key: PGPKeyRing) =
if (key is PGPSecretKeyRing)
ArmorUtils.toAsciiArmoredString(key)
else
ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key)
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 outputStream output stream
*
* @throws IOException in case of an error during the armoring process
*/
@JvmStatic
@ -106,33 +102,34 @@ class PGPainless private constructor() {
*
* @param signature detached signature
* @return ascii armored string
*
* @throws IOException in case of an error during the armoring process
*/
@JvmStatic
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
*/
@JvmStatic
fun encryptAndOrSign() = EncryptionBuilder()
@JvmStatic 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
*/
@JvmStatic
fun decryptAndOrVerify() = DecryptionBuilder()
@JvmStatic fun decryptAndOrVerify() = DecryptionBuilder()
/**
* Make changes to a secret key at the given reference time.
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
* Make changes to a secret key at the given reference time. This method can be used to
* change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
*
* <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 referenceTime reference time used as signature creation date
@ -141,11 +138,12 @@ class PGPainless private constructor() {
@JvmStatic
@JvmOverloads
fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) =
SecretKeyRingEditor(secretKey, referenceTime)
SecretKeyRingEditor(secretKey, referenceTime)
/**
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing].
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time.
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] /
* [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 referenceTime date of inspection
@ -154,22 +152,20 @@ class PGPainless private constructor() {
@JvmStatic
@JvmOverloads
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
KeyRingInfo(key, referenceTime)
KeyRingInfo(key, referenceTime)
/**
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
*
* @return policy
*/
@JvmStatic
fun getPolicy() = Policy.getInstance()
@JvmStatic fun getPolicy() = Policy.getInstance()
/**
* Create different kinds of signatures on other keys.
*
* @return builder
*/
@JvmStatic
fun certify() = CertifyCertificate()
@JvmStatic fun certify() = CertifyCertificate()
}
}
}

View File

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

View File

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

View File

@ -4,18 +4,17 @@
package org.pgpainless.algorithm
enum class CertificationType(
val signatureType: SignatureType
) {
enum class CertificationType(val signatureType: SignatureType) {
/**
* The issuer of this certification does not make any particular assertion as to how well the certifier has
* checked that the owner of the key is in fact the person described by the User ID.
* The issuer of this certification does not make any particular assertion as to how well the
* certifier has checked that the owner of the key is in fact the person described by the User
* ID.
*/
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 User ID specified.
* The issuer of this certification has not done any verification of the claim that the owner of
* this key is the User ID specified.
*/
NONE(SignatureType.NO_CERTIFICATION),
@ -31,4 +30,4 @@ enum class CertificationType(
;
fun asSignatureType() = signatureType
}
}

View File

@ -20,22 +20,20 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
companion object {
/**
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
* If an invalid id is provided, null is returned.
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
* an invalid id is provided, null is returned.
*
* @param id id
* @return compression algorithm
*/
@JvmStatic
fun fromId(id: Int): CompressionAlgorithm? {
return values().firstOrNull {
c -> c.algorithmId == id
}
return values().firstOrNull { c -> c.algorithmId == id }
}
/**
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
* If an invalid id is provided, throw an [NoSuchElementException].
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
* an invalid id is provided, throw an [NoSuchElementException].
*
* @param id id
* @return compression algorithm
@ -43,8 +41,8 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
*/
@JvmStatic
fun requireFromId(id: Int): CompressionAlgorithm {
return fromId(id) ?:
throw NoSuchElementException("No CompressionAlgorithm found for id $id")
return fromId(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) {
/**
* Signature is calculated over the unchanged binary data.
*/
/** Signature is calculated over the unchanged binary data. */
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
/**
* The signature is calculated over the text data with its line endings
* converted to `<CR><LF>`.
* The signature is calculated over the text data with its line endings converted to `<CR><LF>`.
*/
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
;
}
}

View File

@ -5,18 +5,10 @@
package org.pgpainless.algorithm
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,
/**
* The stream will encrypt data at rest.
* E.g. Encrypted backup...
*/
/** The stream will encrypt data at rest. E.g. Encrypted backup... */
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
}
}

View File

@ -12,42 +12,44 @@ package org.pgpainless.algorithm
enum class Feature(val featureId: Byte) {
/**
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification
* Detection Code Packets.
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using
* 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),
/**
* Support for Authenticated Encryption with Additional Data (AEAD).
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets.
* Support for Authenticated Encryption with Additional Data (AEAD). If a key announces this
* feature, it signals support for consuming AEAD Encrypted Data Packets.
*
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
* NOTE: This value is currently RESERVED.
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! 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),
/**
* If a key announces this feature, it is a version 5 public key.
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material.
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case
* of an unknown algorithm.
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys.
* If a key announces this feature, it is a version 5 public key. The version 5 format is
* similar to the version 4 format except for the addition of a count for the key material. This
* count helps to parse secret key packets (which are an extension of the public key packet
* format) in the case of an unknown algorithm. In addition, fingerprints of version 5 keys are
* calculated differently from version 4 keys.
*
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
* NOTE: This value is currently RESERVED.
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! 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),
/**
* 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),
;
@ -55,29 +57,26 @@ enum class Feature(val featureId: Byte) {
companion object {
@JvmStatic
fun fromId(id: Byte): Feature? {
return values().firstOrNull {
f -> f.featureId == id
}
return values().firstOrNull { f -> f.featureId == id }
}
@JvmStatic
fun requireFromId(id: Byte): Feature {
return fromId(id) ?:
throw NoSuchElementException("Unknown feature id encountered: $id")
return fromId(id) ?: throw NoSuchElementException("Unknown feature id encountered: $id")
}
@JvmStatic
fun fromBitmask(bitmask: Int): List<Feature> {
return values().filter {
it.featureId.toInt() and bitmask != 0
}
return values().filter { it.featureId.toInt() and bitmask != 0 }
}
@JvmStatic
fun toBitmask(vararg features: Feature): Byte {
return features.map { it.featureId.toInt() }
.reduceOrNull { mask, f -> mask or f }?.toByte()
?: 0
return features
.map { it.featureId.toInt() }
.reduceOrNull { mask, f -> mask or f }
?.toByte()
?: 0
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -10,87 +10,73 @@ package org.pgpainless.algorithm
* See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1)
*/
enum class PublicKeyAlgorithm(
val algorithmId: Int,
val signingCapable: Boolean,
val encryptionCapable: Boolean) {
val algorithmId: Int,
val signingCapable: Boolean,
val encryptionCapable: Boolean
) {
/**
* RSA capable of encryption and signatures.
*/
RSA_GENERAL (1, true, true),
/** RSA capable of encryption and signatures. */
RSA_GENERAL(1, true, true),
/**
* 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",
ReplaceWith("RSA_GENERAL"))
RSA_ENCRYPT (2, false, true),
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
RSA_ENCRYPT(2, false, true),
/**
* 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",
ReplaceWith("RSA_GENERAL"))
RSA_SIGN (3, true, false),
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
RSA_SIGN(3, true, false),
/**
* ElGamal with usage encryption.
*/
ELGAMAL_ENCRYPT (16, false, true),
/** ElGamal with usage encryption. */
ELGAMAL_ENCRYPT(16, false, true),
/**
* Digital Signature Algorithm.
*/
DSA (17, true, false),
/** Digital Signature Algorithm. */
DSA(17, true, false),
/**
* Elliptic Curve Diffie-Hellman.
*/
ECDH (18, false, true),
/** Elliptic Curve Diffie-Hellman. */
ECDH(18, false, true),
/**
* Elliptic Curve Digital Signature Algorithm.
*/
ECDSA (19, true, false),
/** Elliptic Curve Digital Signature Algorithm. */
ECDSA(19, true, false),
/**
* 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")
ELGAMAL_GENERAL (20, true, true),
@Deprecated("ElGamal is deprecated") ELGAMAL_GENERAL(20, true, true),
/**
* Diffie-Hellman key exchange algorithm.
*/
DIFFIE_HELLMAN (21, false, true),
/** Diffie-Hellman key exchange algorithm. */
DIFFIE_HELLMAN(21, false, true),
/**
* Digital Signature Algorithm based on twisted Edwards Curves.
*/
EDDSA (22, true, false),
/** Digital Signature Algorithm based on twisted Edwards Curves. */
EDDSA(22, true, false),
;
fun isSigningCapable(): Boolean = signingCapable
fun isEncryptionCapable(): Boolean = encryptionCapable
companion object {
@JvmStatic
fun fromId(id: Int): PublicKeyAlgorithm? {
return values().firstOrNull {
it.algorithmId == id
}
return values().firstOrNull { it.algorithmId == id }
}
@JvmStatic
fun requireFromId(id: Int): PublicKeyAlgorithm {
return fromId(id) ?:
throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
return fromId(id)
?: throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -11,61 +11,52 @@ package org.pgpainless.algorithm
*/
enum class StreamEncoding(val code: Char) {
/**
* The Literal packet contains binary data.
*/
/** The Literal packet contains binary data. */
BINARY('b'),
/**
* The Literal packet contains text data, and thus may need line ends converted to local form, or other
* text-mode changes.
* The Literal packet contains text data, and thus may need line ends converted to local form,
* or other text-mode changes.
*/
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'),
/**
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions.
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one).
* Both of these local modes are deprecated.
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local
* conversions. RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral
* one). Both of these local modes are deprecated.
*/
@Deprecated("LOCAL is deprecated.")
LOCAL('l'),
@Deprecated("LOCAL is deprecated.") LOCAL('l'),
;
companion object {
/**
* Return the [StreamEncoding] corresponding to the provided code identifier.
* If no matching encoding is found, return null.
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
* encoding is found, return null.
*
* @param code identifier
* @return encoding enum
*/
@JvmStatic
fun fromCode(code: Int): StreamEncoding? {
return values().firstOrNull {
it.code == code.toChar()
} ?: if (code == 1) return LOCAL else null
return values().firstOrNull { it.code == code.toChar() }
?: if (code == 1) return LOCAL else null
}
/**
* Return the [StreamEncoding] corresponding to the provided code identifier.
* If no matching encoding is found, throw a [NoSuchElementException].
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
* encoding is found, throw a [NoSuchElementException].
*
* @param code identifier
* @return encoding enum
*
* @throws NoSuchElementException in case of an unmatched identifier
*/
@JvmStatic
fun requireFromCode(code: Int): StreamEncoding {
return fromCode(code) ?:
throw NoSuchElementException("No StreamEncoding found for code $code")
return fromCode(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) {
/**
* Plaintext or unencrypted data.
*/
NULL (0),
/** Plaintext or unencrypted data. */
NULL(0),
/**
* IDEA is deprecated.
*
* @deprecated use a different algorithm.
*/
@Deprecated("IDEA is deprecated.")
IDEA (1),
@Deprecated("IDEA is deprecated.") IDEA(1),
/**
* TripleDES (DES-EDE - 168 bit key derived from 192).
*/
TRIPLE_DES (2),
/** TripleDES (DES-EDE - 168 bit key derived from 192). */
TRIPLE_DES(2),
/**
* CAST5 (128-bit key, as per RFC2144).
*/
CAST5 (3),
/** CAST5 (128-bit key, as per RFC2144). */
CAST5(3),
/**
* Blowfish (128-bit key, 16 rounds).
*/
BLOWFISH (4),
/** Blowfish (128-bit key, 16 rounds). */
BLOWFISH(4),
/**
* Reserved in RFC4880.
* SAFER-SK128 (13 rounds)
*/
SAFER (5),
/** Reserved in RFC4880. SAFER-SK128 (13 rounds) */
SAFER(5),
/**
* Reserved in RFC4880.
* Reserved for DES/SK
*/
DES (6),
/** Reserved in RFC4880. Reserved for DES/SK */
DES(6),
/**
* AES with 128-bit key.
*/
AES_128 (7),
/** AES with 128-bit key. */
AES_128(7),
/**
* AES with 192-bit key.
*/
AES_192 (8),
/** AES with 192-bit key. */
AES_192(8),
/**
* AES with 256-bit key.
*/
AES_256 (9),
/** AES with 256-bit key. */
AES_256(9),
/**
* Twofish with 256-bit key.
*/
TWOFISH (10),
/** Twofish with 256-bit key. */
TWOFISH(10),
/**
* Reserved for Camellia with 128-bit key.
*/
CAMELLIA_128 (11),
/** Reserved for Camellia with 128-bit key. */
CAMELLIA_128(11),
/**
* Reserved for Camellia with 192-bit key.
*/
CAMELLIA_192 (12),
/** Reserved for Camellia with 192-bit key. */
CAMELLIA_192(12),
/**
* Reserved for Camellia with 256-bit key.
*/
CAMELLIA_256 (13),
/** Reserved for Camellia with 256-bit key. */
CAMELLIA_256(13),
;
companion object {
/**
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
* If an invalid id is provided, null is returned.
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
* an invalid id is provided, null is returned.
*
* @param id numeric algorithm id
* @return symmetric key algorithm enum
*/
@JvmStatic
fun fromId(id: Int): SymmetricKeyAlgorithm? {
return values().firstOrNull {
it.algorithmId == id
}
return values().firstOrNull { it.algorithmId == id }
}
/**
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
* If an invalid id is provided, throw a [NoSuchElementException].
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
* an invalid id is provided, throw a [NoSuchElementException].
*
* @param id numeric algorithm id
* @return symmetric key algorithm enum
*
* @throws NoSuchElementException if an unmatched id is provided
*/
@JvmStatic
fun requireFromId(id: Int): SymmetricKeyAlgorithm {
return fromId(id) ?:
throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
return fromId(id)
?: throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -6,19 +6,19 @@ package org.pgpainless.authentication
import org.bouncycastle.openpgp.PGPPublicKeyRing
class CertificateAuthenticity(val userId: String,
val certificate: PGPPublicKeyRing,
val certificationChains: Map<CertificationChain, Int>,
val targetAmount: Int) {
class CertificateAuthenticity(
val userId: String,
val certificate: PGPPublicKeyRing,
val certificationChains: Map<CertificationChain, Int>,
val targetAmount: Int
) {
val totalTrustAmount: Int
get() = certificationChains.values.sum()
/**
* Return the degree of authentication of the binding in percent.
* 100% means full authentication.
* Values smaller than 100% mean partial authentication.
* Return the degree of authentication of the binding in percent. 100% means full
* authentication. Values smaller than 100% mean partial authentication.
*
* @return authenticity in percent
*/
@ -42,16 +42,7 @@ class CertificateAuthenticity(val userId: String,
* @param trustAmount actual trust amount of the chain
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
*/
class CertificationChain(
val trustAmount: Int,
val chainLinks: List<ChainLink>) {
class CertificationChain(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
package org.pgpainless.authentication;
package org.pgpainless.authentication
import org.pgpainless.key.OpenPgpFingerprint
import java.util.*
import org.pgpainless.key.OpenPgpFingerprint
/**
* Interface for a CA that can authenticate trust-worthy certificates.
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
* Interface for a CA that can authenticate trust-worthy certificates. Such a CA might be a fixed
* 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://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
@ -17,52 +17,58 @@ import java.util.*
interface CertificateAuthority {
/**
* Determine the authenticity of the binding between the given fingerprint and the userId.
* In other words, determine, how much evidence can be gathered, that the certificate with the given
* fingerprint really belongs to the user with the given userId.
* Determine the authenticity of the binding between the given fingerprint and the userId. In
* other words, determine, how much evidence can be gathered, that the certificate with the
* given fingerprint really belongs to the user with the given userId.
*
* @param fingerprint fingerprint of the certificate
* @param userId userId
* @param email if true, the userId will be treated as an email address and all user-IDs containing
* the email address will be matched.
* @param email if true, the userId will be treated as an email address and all user-IDs
* containing the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* authenticated, 60 = partially authenticated...)
* @return information about the authenticity of the binding
*/
fun authenticateBinding(fingerprint: OpenPgpFingerprint,
userId: String,
email: Boolean,
referenceTime: Date,
targetAmount: Int): CertificateAuthenticity;
fun authenticateBinding(
fingerprint: OpenPgpFingerprint,
userId: String,
email: Boolean,
referenceTime: Date,
targetAmount: Int
): CertificateAuthenticity
/**
* Lookup certificates, which carry a trustworthy binding to the given userId.
*
* @param userId userId
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing
* the email address will be matched.
* @param email if true, the user-ID will be treated as an email address and all user-IDs
* containing the email address will be matched.
* @param referenceTime reference time at which the binding shall be evaluated
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* authenticated, 60 = partially authenticated...)
* @return list of identified bindings
*/
fun lookupByUserId(userId: String,
email: Boolean,
referenceTime: Date,
targetAmount: Int): List<CertificateAuthenticity>
fun lookupByUserId(
userId: String,
email: Boolean,
referenceTime: Date,
targetAmount: Int
): List<CertificateAuthenticity>
/**
* Identify trustworthy bindings for a certificate.
* The result is a list of authenticatable userIds on the certificate.
* Identify trustworthy bindings for a certificate. The result is a list of authenticatable
* userIds on the certificate.
*
* @param fingerprint fingerprint of the certificate
* @param referenceTime reference time for trust calculations
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
* 60 = partially authenticated...)
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
* authenticated, 60 = partially authenticated...)
* @return list of identified bindings
*/
fun identifyByFingerprint(fingerprint: OpenPgpFingerprint,
referenceTime: Date,
targetAmount: Int): List<CertificateAuthenticity>
fun identifyByFingerprint(
fingerprint: OpenPgpFingerprint,
referenceTime: Date,
targetAmount: Int
): List<CertificateAuthenticity>
}

View File

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

View File

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

View File

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

View File

@ -4,14 +4,15 @@
package org.pgpainless.decryption_verification
import org.bouncycastle.openpgp.PGPException
import java.io.IOException
import java.io.InputStream
import org.bouncycastle.openpgp.PGPException
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.
* @return api handle
@ -31,4 +32,4 @@ interface DecryptionBuilderInterface {
@Throws(PGPException::class, IOException::class)
fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream
}
}
}

View File

@ -9,13 +9,13 @@ import java.io.InputStream
/**
* 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.
* The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed.
* Return [MessageMetadata] about the decrypted / verified message. The [DecryptionStream] MUST
* be closed via [close] before the metadata object can be accessed.
*
* @return message metadata
*/
abstract val metadata: MessageMetadata
}
}

View File

@ -4,6 +4,7 @@
package org.pgpainless.decryption_verification
import kotlin.jvm.Throws
import org.bouncycastle.bcpg.AEADEncDataPacket
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
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.bc.BcPublicKeyDataDecryptorFactory
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 {
interface DecryptionCallback {
/**
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with
* hardware security modules such as smartcards or TPMs.
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for
* 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 keyAlgorithm algorithm
* @param sessionKeyData encrypted session key
*
* @return decrypted session key
* @throws HardwareSecurityException exception
*/
@ -39,38 +37,51 @@ class HardwareSecurity {
}
/**
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys
* to a [DecryptionCallback].
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs.
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted
* session keys to a [DecryptionCallback]. Users can provide such a callback to delegate
* decryption of messages to hardware security SDKs.
*/
class HardwareDataDecryptorFactory(
override val subkeyIdentifier: SubkeyIdentifier,
private val callback: DecryptionCallback,
override val subkeyIdentifier: SubkeyIdentifier,
private val callback: DecryptionCallback,
) : CustomPublicKeyDataDecryptorFactory {
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
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)
}
override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor {
override fun createDataDecryptor(
aeadEncDataPacket: AEADEncDataPacket?,
sessionKey: PGPSessionKey?
): PGPDataDecryptor {
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)
}
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray {
override fun recoverSessionData(
keyAlgorithm: Int,
secKeyData: Array<out ByteArray>
): ByteArray {
return try {
callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
} catch (e : HardwareSecurityException) {
} catch (e: HardwareSecurityException) {
throw PGPException("Hardware-backed decryption failed.", e)
}
}
}
class HardwareSecurityException : Exception()
}
}

View File

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

View File

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

View File

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

View File

@ -4,19 +4,18 @@
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 {
/**
* Try to interactively obtain key passphrases one-by-one via callbacks,
* eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
* Try to interactively obtain key passphrases one-by-one via callbacks, eg
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
*/
INTERACTIVE,
/**
* 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
}
}

View File

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

View File

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

View File

@ -11,43 +11,43 @@ import org.pgpainless.key.SubkeyIdentifier
import org.pgpainless.signature.SignatureUtils
/**
* Tuple of a signature and an identifier of its corresponding verification key.
* Semantic meaning of the signature verification (success, failure) is merely given by context.
* E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications,
* while the class [Failure] contains failed verifications.
* Tuple of a signature and an identifier of its corresponding verification key. Semantic meaning of
* the signature verification (success, failure) is merely given by context. E.g.
* [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, while the class
* [Failure] contains failed verifications.
*
* @param signature PGPSignature object
* @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(
val signature: PGPSignature,
val signingKey: SubkeyIdentifier
) {
data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) {
override fun toString(): String {
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
" Key: $signingKey;"
" Key: $signingKey;"
}
/**
* Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException]
* that caused the verification to fail.
* Tuple object of a [SignatureVerification] and the corresponding
* [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
*/
data class Failure(
val signature: PGPSignature,
val signingKey: SubkeyIdentifier?,
val validationException: SignatureValidationException
val signature: PGPSignature,
val signingKey: SubkeyIdentifier?,
val validationException: SignatureValidationException
) {
constructor(verification: SignatureVerification, validationException: SignatureValidationException):
this(verification.signature, verification.signingKey, validationException)
constructor(
verification: SignatureVerification,
validationException: SignatureValidationException
) : this(verification.signature, verification.signingKey, validationException)
override fun toString(): String {
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
}
}
}
}

View File

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

View File

@ -4,47 +4,49 @@
package org.pgpainless.decryption_verification.cleartext_signatures
import java.io.*
import kotlin.jvm.Throws
import org.bouncycastle.bcpg.ArmoredInputStream
import org.bouncycastle.openpgp.PGPSignatureList
import org.bouncycastle.util.Strings
import org.pgpainless.exception.WrongConsumingMethodException
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmoredInputStreamFactory
import java.io.*
import kotlin.jvm.Throws
/**
* Utility class to deal with cleartext-signed messages.
* Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
* Utility class to deal with cleartext-signed messages. Based on Bouncycastle's
* [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
*/
class ClearsignedMessageUtil {
companion object {
/**
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
* messageOutputStream.
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext
* message to the provided messageOutputStream.
*
* @param clearsignedInputStream input stream containing a clearsigned message
* @param messageOutputStream output stream to which the dearmored message shall be written
* @return signatures
*
* @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
*/
@JvmStatic
@Throws(WrongConsumingMethodException::class, IOException::class)
fun detachSignaturesFromInbandClearsignedMessage(
clearsignedInputStream: InputStream,
messageOutputStream: OutputStream): PGPSignatureList {
val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) {
clearsignedInputStream
} else {
ArmoredInputStreamFactory.get(clearsignedInputStream)
}
clearsignedInputStream: InputStream,
messageOutputStream: OutputStream
): PGPSignatureList {
val input: ArmoredInputStream =
if (clearsignedInputStream is ArmoredInputStream) {
clearsignedInputStream
} else {
ArmoredInputStreamFactory.get(clearsignedInputStream)
}
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 ->
@ -94,7 +96,11 @@ class ClearsignedMessageUtil {
}
@JvmStatic
private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int {
private fun readInputLine(
bOut: ByteArrayOutputStream,
lookAhead: Int,
fIn: InputStream
): Int {
var mLookAhead = lookAhead
bOut.reset()
var ch = mLookAhead
@ -150,4 +156,4 @@ class ClearsignedMessageUtil {
return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte()
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -5,35 +5,26 @@
package org.pgpainless.decryption_verification.syntax_check
enum class InputSymbol {
/**
* A [PGPLiteralData] packet.
*/
/** A [PGPLiteralData] packet. */
LITERAL_DATA,
/**
* A [PGPSignatureList] object.
*/
/** A [PGPSignatureList] object. */
SIGNATURE,
/**
* A [PGPOnePassSignatureList] object.
*/
/** A [PGPOnePassSignatureList] object. */
ONE_PASS_SIGNATURE,
/**
* A [PGPCompressedData] packet.
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
* its nested packet sequence.
* A [PGPCompressedData] packet. The contents of this packet MUST form a valid OpenPGP message,
* so a nested PDA is opened to verify its nested packet sequence.
*/
COMPRESSED_DATA,
/**
* A [PGPEncryptedDataList] object.
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
* (possibly Integrity Protected) Data packet.
* A [PGPEncryptedDataList] object. This object combines multiple ESKs and the corresponding
* Symmetrically Encrypted (possibly Integrity Protected) Data packet.
*/
ENCRYPTED_DATA,
/**
* Marks the end of a (sub-) sequence.
* This input is given if the end of an OpenPGP message is reached.
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
* (e.g. the end of a Compressed Data packet).
* Marks the end of a (sub-) sequence. This input is given if the end of an OpenPGP message is
* reached. This might be the case for the end of the whole ciphertext, or the end of a packet
* with nested contents (e.g. the end of a Compressed Data packet).
*/
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.
*
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
* 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/)
* 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 [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) 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/)
* 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 {
@ -33,10 +34,12 @@ class OpenPgpMessageSyntax : Syntax {
return when (input) {
InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE)
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.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)
}
}
@ -85,4 +88,4 @@ class OpenPgpMessageSyntax : Syntax {
}
throw MalformedOpenPgpMessageException(State.VALID, input, stackItem)
}
}
}

View File

@ -8,37 +8,41 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
import org.slf4j.LoggerFactory
/**
* Pushdown Automaton for validating context-free languages.
* In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax.
* Pushdown Automaton for validating context-free languages. In PGPainless, this class is used to
* validate OpenPGP message packet sequences against the allowed syntax.
*
* See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
*/
class PDA constructor(
private val syntax: Syntax,
private val stack: ArrayDeque<StackSymbol>,
private val inputs: MutableList<InputSymbol>,
private var state: State
class PDA
constructor(
private val syntax: Syntax,
private val stack: ArrayDeque<StackSymbol>,
private val inputs: MutableList<InputSymbol>,
private var state: State
) {
/**
* 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 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 (
syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
constructor(
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].
*/
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.
* 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
* @throws MalformedOpenPgpMessageException if the input symbol is rejected
@ -53,14 +57,17 @@ class PDA constructor(
}
inputs.add(input)
} catch (e: MalformedOpenPgpMessageException) {
val stackFormat = if (stackSymbol != null) {
"${stack.joinToString()}||$stackSymbol"
} else {
stack.joinToString()
}
val wrapped = MalformedOpenPgpMessageException(
val stackFormat =
if (stackSymbol != null) {
"${stack.joinToString()}||$stackSymbol"
} else {
stack.joinToString()
}
val wrapped =
MalformedOpenPgpMessageException(
"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)
throw wrapped
}
@ -87,7 +94,8 @@ class PDA constructor(
*/
fun assertValid() {
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 {
@JvmStatic
private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
@JvmStatic private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
}
}
}

View File

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

View File

@ -4,13 +4,11 @@
package org.pgpainless.decryption_verification.syntax_check
/**
* Set of states of the automaton.
*/
/** Set of states of the automaton. */
enum class State {
OPENPGP_MESSAGE,
LITERAL_MESSAGE,
COMPRESSED_MESSAGE,
ENCRYPTED_MESSAGE,
VALID
}
}

View File

@ -6,25 +6,24 @@ package org.pgpainless.decryption_verification.syntax_check
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 {
/**
* 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.
* The resulting [Transition] contains the new [State], as well as a list of
* [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule.
* If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case
* the [InputSymbol] must be considered illegal.
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack. The resulting
* [Transition] contains the new [State], as well as a list of [StackSymbols][StackSymbol] that
* get pushed onto the stack by the transition rule. If there is no applicable rule, a
* [MalformedOpenPgpMessageException] is thrown, since in this case the [InputSymbol] must be
* considered illegal.
*
* @param from current state of the PDA
* @param input input symbol
* @param stackItem item that got popped from the top of the stack
* @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)
fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition
}
}

View File

@ -5,18 +5,18 @@
package org.pgpainless.decryption_verification.syntax_check
/**
* Result of applying a transition rule.
* Transition rules can be described by implementing the [Syntax] interface.
* Result of applying a transition rule. Transition rules can be described by implementing the
* [Syntax] interface.
*
* @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.
* The list contains items in the order in which they are pushed onto the stack.
* The list may be empty.
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the
* transition. The list contains items in the order in which they are pushed onto the stack. The
* list may be empty.
*/
class Transition private constructor(
val pushedItems: List<StackSymbol>,
val newState: State
) {
class Transition private constructor(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
import java.security.MessageDigest
import org.bouncycastle.extensions.unlock
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPrivateKey
@ -13,20 +14,23 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator
import org.pgpainless.PGPainless
import org.pgpainless.algorithm.SignatureType
import org.pgpainless.key.protection.SecretKeyRingProtector
import java.security.MessageDigest
class BcHashContextSigner {
companion object {
@JvmStatic
fun signHashContext(hashContext: MessageDigest,
signatureType: SignatureType,
secretKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector): PGPSignature {
fun signHashContext(
hashContext: MessageDigest,
signatureType: SignatureType,
secretKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector
): PGPSignature {
val info = PGPainless.inspectKeyRing(secretKey)
return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull()
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
?: throw PGPException("Key does not contain suitable signing subkey.")
return info.signingSubkeys
.mapNotNull { info.getSecretKey(it.keyID) }
.firstOrNull()
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
?: throw PGPException("Key does not contain suitable signing subkey.")
}
/**
@ -38,12 +42,14 @@ class BcHashContextSigner {
* @throws PGPException in case of an OpenPGP error
*/
@JvmStatic
internal fun signHashContext(hashContext: MessageDigest,
signatureType: SignatureType,
privateKey: PGPPrivateKey): PGPSignature {
internal fun signHashContext(
hashContext: MessageDigest,
signatureType: SignatureType,
privateKey: PGPPrivateKey
): PGPSignature {
return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext))
.apply { init(signatureType.code, privateKey) }
.generate()
.apply { init(signatureType.code, privateKey) }
.generate()
}
}
}
}

View File

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

View File

@ -5,17 +5,15 @@
package org.pgpainless.encryption_signing
import org.pgpainless.algorithm.StreamEncoding
import java.io.OutputStream
import org.pgpainless.algorithm.StreamEncoding
/**
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding].
* This implementation originates from the Bouncy Castle library.
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired
* [StreamEncoding]. This implementation originates from the Bouncy Castle library.
*/
class CRLFGeneratorStream(
private val crlfOut: OutputStream,
encoding: StreamEncoding
) : OutputStream() {
class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEncoding) :
OutputStream() {
private val isBinary: Boolean
private var lastB = 0
@ -26,9 +24,9 @@ class CRLFGeneratorStream(
override fun write(b: Int) {
if (!isBinary) {
if (b == '\n'.code && lastB != '\r'.code) { // Unix
if (b == '\n'.code && lastB != '\r'.code) { // Unix
crlfOut.write('\r'.code)
} else if (lastB == '\r'.code) { // MAC
} else if (lastB == '\r'.code) { // MAC
if (b != '\n'.code) {
crlfOut.write('\n'.code)
}
@ -49,4 +47,4 @@ class CRLFGeneratorStream(
super.flush()
crlfOut.flush()
}
}
}

View File

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

View File

@ -4,9 +4,9 @@
package org.pgpainless.encryption_signing
import org.bouncycastle.openpgp.PGPException
import java.io.IOException
import java.io.OutputStream
import org.bouncycastle.openpgp.PGPException
fun interface EncryptionBuilderInterface {
@ -26,11 +26,11 @@ fun interface EncryptionBuilderInterface {
*
* @param options options
* @return encryption stream
*
* @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)
fun withOptions(options: ProducerOptions): EncryptionStream
}
}
}

View File

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

View File

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

View File

@ -4,6 +4,9 @@
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.BCPGOutputStream
import org.bouncycastle.openpgp.PGPCompressedDataGenerator
@ -16,9 +19,6 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.util.ArmoredOutputStreamFactory
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 << 9 fixes this.
@ -30,11 +30,13 @@ const val BUFFER_SIZE = 1 shl 9
* depending on its configuration.
*
* 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(
private var outermostStream: OutputStream,
private val options: ProducerOptions,
private var outermostStream: OutputStream,
private val options: ProducerOptions,
) : OutputStream() {
private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder()
@ -66,8 +68,8 @@ class EncryptionStream(
outermostStream = BufferedOutputStream(outermostStream)
LOGGER.debug("Wrap encryption output in ASCII armor.")
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options)
.also { outermostStream = it }
armorOutputStream =
ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it }
}
@Throws(IOException::class, PGPException::class)
@ -84,9 +86,11 @@ class EncryptionStream(
EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let {
resultBuilder.setEncryptionAlgorithm(it)
LOGGER.debug("Encrypt message using symmetric algorithm $it.")
val encryptedDataGenerator = PGPEncryptedDataGenerator(
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it)
.apply { setWithIntegrityPacket(true) })
val encryptedDataGenerator =
PGPEncryptedDataGenerator(
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it).apply {
setWithIntegrityPacket(true)
})
options.encryptionOptions.encryptionMethods.forEach { m ->
encryptedDataGenerator.addMethod(m)
}
@ -94,8 +98,11 @@ class EncryptionStream(
resultBuilder.addRecipient(r)
}
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE))
.also { stream -> outermostStream = stream }
publicKeyEncryptedStream =
encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream
->
outermostStream = stream
}
}
}
@ -107,8 +114,10 @@ class EncryptionStream(
if (it == CompressionAlgorithm.UNCOMPRESSED) return
LOGGER.debug("Compress using $it.")
basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream))
.also { stream -> outermostStream = stream }
basicCompressionStream =
BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream ->
outermostStream = stream
}
}
}
@ -138,12 +147,17 @@ class EncryptionStream(
return
}
literalDataGenerator = PGPLiteralDataGenerator().also { gen ->
literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName,
options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream ->
outermostStream = stream
literalDataGenerator =
PGPLiteralDataGenerator().also { gen ->
literalDataStream =
gen.open(
outermostStream,
options.encoding.code,
options.fileName,
options.modificationDate,
ByteArray(BUFFER_SIZE))
.also { stream -> outermostStream = stream }
}
}
resultBuilder.apply {
setFileName(options.fileName)
setModificationDate(options.modificationDate)
@ -156,39 +170,47 @@ class EncryptionStream(
}
private fun prepareInputEncoding() {
outermostStream = CRLFGeneratorStream(
outermostStream =
CRLFGeneratorStream(
// 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
BufferedOutputStream(outermostStream),
if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY)
}
private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> {
return options.signingOptions?.signingMethods?.values
?.map { it.hashAlgorithm }?.toSet()
?.map { it.algorithmId }?.toTypedArray()
?: arrayOf()
return options.signingOptions
?.signingMethods
?.values
?.map { it.hashAlgorithm }
?.toSet()
?.map { it.algorithmId }
?.toTypedArray()
?: arrayOf()
}
@Throws(IOException::class)
override fun write(data: Int) = outermostStream.write(data)
@Throws(IOException::class) override fun write(data: Int) = outermostStream.write(data)
@Throws(IOException::class)
override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size)
@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)
override fun flush() = outermostStream.flush()
@Throws(IOException::class) override fun flush() = outermostStream.flush()
@Throws(IOException::class)
override fun close() {
if (closed) return
outermostStream.close()
literalDataStream?.apply { flush(); close() }
literalDataStream?.apply {
flush()
close()
}
literalDataGenerator?.close()
if (options.isCleartextSigned) {
@ -201,7 +223,7 @@ class EncryptionStream(
try {
writeSignatures()
} catch (e : PGPException) {
} catch (e: PGPException) {
throw IOException("Exception while writing signatures.", e)
}
@ -238,14 +260,14 @@ class EncryptionStream(
}
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() }
val isClosed
get() = closed
companion object {
@JvmStatic
private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
@JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
}
}
}

View File

@ -4,39 +4,43 @@
package org.pgpainless.encryption_signing
import java.io.OutputStream
import java.security.MessageDigest
import org.bouncycastle.crypto.Digest
import org.bouncycastle.crypto.Signer
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
import java.io.OutputStream
import java.security.MessageDigest
abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder {
// Copied from BC, required since BCs class is package visible only
internal class SignerOutputStream(
private val signer: Signer
) : OutputStream() {
internal class SignerOutputStream(private val signer: Signer) : OutputStream() {
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, off: Int, len: Int) = signer.update(b, off, len)
}
internal class ExistingMessageDigest(
private val digest: MessageDigest
) : Digest {
internal class ExistingMessageDigest(private val digest: MessageDigest) : Digest {
override fun getAlgorithmName(): String = digest.algorithm
override fun getDigestSize(): Int = digest.digestLength
override fun update(b: Byte) = digest.update(b)
override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf)
override fun doFinal(out: ByteArray, outOff: Int): Int {
digest.digest().copyInto(out, outOff)
return digestSize
}
override fun reset() {
// 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.
}
}
}
}

View File

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

View File

@ -6,23 +6,20 @@ package org.pgpainless.encryption_signing
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(
private val wrapped: OutputStream,
private val options: SigningOptions?
private val wrapped: OutputStream,
private val options: SigningOptions?
) : OutputStream() {
override fun close() = wrapped.close()
override fun flush() = wrapped.flush()
override fun write(b: Int) {
wrapped.write(b)
options?.run {
signingMethods.values.forEach {
it.signatureGenerator.update((b and 0xff).toByte())
}
signingMethods.values.forEach { it.signatureGenerator.update((b and 0xff).toByte()) }
}
}
@ -30,10 +27,6 @@ class SignatureGenerationStream(
override fun write(b: ByteArray, off: Int, len: Int) {
wrapped.write(b, off, len)
options?.run {
signingMethods.values.forEach {
it.signatureGenerator.update(b, off, len)
}
}
options?.run { signingMethods.values.forEach { it.signatureGenerator.update(b, off, len) } }
}
}
}

View File

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

View File

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

View File

@ -4,6 +4,9 @@
package org.pgpainless.implementation
import java.io.InputStream
import java.security.KeyPair
import java.util.*
import org.bouncycastle.openpgp.*
import org.bouncycastle.openpgp.operator.*
import org.pgpainless.algorithm.HashAlgorithm
@ -11,18 +14,13 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.util.Passphrase
import org.pgpainless.util.SessionKey
import java.io.InputStream
import java.security.KeyPair
import java.util.*
abstract class ImplementationFactory {
companion object {
@JvmStatic
private var instance: ImplementationFactory = BcImplementationFactory()
@JvmStatic private var instance: ImplementationFactory = BcImplementationFactory()
@JvmStatic
fun getInstance() = instance
@JvmStatic fun getInstance() = instance
@JvmStatic
fun setFactoryImplementation(implementation: ImplementationFactory) = apply {
@ -38,56 +36,82 @@ abstract class ImplementationFactory {
get() = getPGPDigestCalculator(HashAlgorithm.SHA1)
@Throws(PGPException::class)
abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
digestCalculator: PGPDigestCalculator,
passphrase: Passphrase): PBESecretKeyEncryptor
abstract fun getPBESecretKeyEncryptor(
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
digestCalculator: PGPDigestCalculator,
passphrase: Passphrase
): PBESecretKeyEncryptor
@Throws(PGPException::class)
abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor
@Throws(PGPException::class)
abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm,
s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor
abstract fun getPBESecretKeyEncryptor(
encryptionAlgorithm: SymmetricKeyAlgorithm,
hashAlgorithm: HashAlgorithm,
s2kCount: Int,
passphrase: Passphrase
): PBESecretKeyEncryptor
fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator =
getPGPDigestCalculator(hashAlgorithm.algorithmId)
getPGPDigestCalculator(hashAlgorithm.algorithmId)
fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator =
pgpDigestCalculatorProvider.get(hashAlgorithm)
pgpDigestCalculatorProvider.get(hashAlgorithm)
fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder =
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
fun getPGPContentSignerBuilder(
keyAlgorithm: PublicKeyAlgorithm,
hashAlgorithm: HashAlgorithm
): PGPContentSignerBuilder =
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
abstract fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder
abstract fun getPGPContentSignerBuilder(
keyAlgorithm: Int,
hashAlgorithm: Int
): PGPContentSignerBuilder
@Throws(PGPException::class)
abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory
abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory
abstract fun getPublicKeyDataDecryptorFactory(
privateKey: PGPPrivateKey
): PublicKeyDataDecryptorFactory
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 =
getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
fun getPGPDataEncryptorBuilder(
symmetricKeyAlgorithm: SymmetricKeyAlgorithm
): PGPDataEncryptorBuilder = getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder
@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 =
getPGPObjectFactory(bytes.inputStream())
getPGPObjectFactory(bytes.inputStream())
abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory
override fun toString(): String {
return javaClass.simpleName
}
}
}

View File

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

View File

@ -4,16 +4,13 @@
package org.pgpainless.key
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
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> {
val fingerprint: String
val bytes: ByteArray
@ -26,39 +23,41 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
abstract fun getVersion(): Int
/**
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
* This method can be implemented for V4 and V5 fingerprints.
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. This
* method can be implemented for V4 and V5 fingerprints. V3 key-IDs cannot be derived from the
* 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
* @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
constructor(fingerprint: String) {
val prep = fingerprint.replace(" ", "").trim().uppercase()
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.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()) {
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.
*
* @param fp fingerprint to check.
* @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 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 {
return fingerprint.compareTo(other.fingerprint)
}
@ -87,49 +88,49 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
abstract fun prettyPrint(): String
companion object {
@JvmStatic
val utf8: Charset = Charset.forName("UTF-8")
@JvmStatic val utf8: Charset = Charset.forName("UTF-8")
/**
* Return the fingerprint of the given key.
* This method automatically matches key versions to fingerprint implementations.
* 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: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
/**
* 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: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
fun of(key: PGPPublicKey): OpenPgpFingerprint =
when (key.version) {
4 -> OpenPgpV4Fingerprint(key)
5 -> OpenPgpV5Fingerprint(key)
6 -> OpenPgpV6Fingerprint(key)
else ->
throw IllegalArgumentException(
"OpenPGP keys of version ${key.version} are not supported.")
}
/**
* 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)
5 -> OpenPgpV5Fingerprint(key)
6 -> OpenPgpV6Fingerprint(key)
else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.")
}
/**
* Return the fingerprint of the primary key of the given key ring.
* This method automatically matches key versions to fingerprint implementations.
* Return the fingerprint of the primary key of the given key ring. This method
* automatically matches key versions to fingerprint implementations.
*
* @param ring key ring
* @return fingerprint
*/
@JvmStatic
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
@JvmStatic fun of(keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
/**
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
* to know the version of the key beforehand.
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. If the
* trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6
* fingerprint. In this case, we return a {@link _64DigitFingerprint}. Since this is
* ambiguous, it is generally recommended to know the version of the key beforehand.
*
* @param fingerprint fingerprint
* @return parsed fingerprint
@ -146,7 +147,8 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
// Might be v5 or v6 :/
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.")
}
/**
@ -159,6 +161,6 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
@JvmStatic
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
parse(Hex.toHexString(binaryFingerprint))
parse(Hex.toHexString(binaryFingerprint))
}
}
}

View File

@ -4,22 +4,26 @@
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.nio.Buffer
import java.nio.ByteBuffer
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(bytes: ByteArray): super(bytes)
constructor(key: PGPPublicKey): super(key)
constructor(key: PGPSecretKey): super(key)
constructor(keys: PGPKeyRing): super(keys)
constructor(fingerprint: String) : super(fingerprint)
constructor(bytes: ByteArray) : super(bytes)
constructor(key: PGPPublicKey) : super(key)
constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
override fun getVersion() = 4
@ -47,7 +51,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
}
append(' ')
for (i in 5 .. 8) {
for (i in 5..8) {
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
}
append(fingerprint, 36, 40)
@ -55,8 +59,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
}
companion object {
@JvmStatic
val SCHEME = "openpgp4fpr"
@JvmStatic val SCHEME = "openpgp4fpr"
@JvmStatic
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
@ -66,4 +69,4 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
}
}
}
}

View File

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

View File

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

View File

@ -13,17 +13,30 @@ import org.bouncycastle.openpgp.PGPPublicKey
* as well as the subkeys fingerprint.
*/
class SubkeyIdentifier(
val primaryKeyFingerprint: OpenPgpFingerprint,
val subkeyFingerprint: OpenPgpFingerprint) {
val primaryKeyFingerprint: OpenPgpFingerprint,
val subkeyFingerprint: OpenPgpFingerprint
) {
constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint)
constructor(keys: PGPKeyRing): this(keys.publicKey)
constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key))
constructor(keys: PGPKeyRing, keyId: Long): this(
OpenPgpFingerprint.of(keys.publicKey),
OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?:
throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}")))
constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
constructor(fingerprint: OpenPgpFingerprint) : this(fingerprint, fingerprint)
constructor(keys: PGPKeyRing) : this(keys.publicKey)
constructor(key: PGPPublicKey) : this(OpenPgpFingerprint.of(key))
constructor(
keys: PGPKeyRing,
keyId: Long
) : this(
OpenPgpFingerprint.of(keys.publicKey),
OpenPgpFingerprint.of(
keys.getPublicKey(keyId)
?: 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 fingerprint = subkeyFingerprint
@ -34,7 +47,7 @@ class SubkeyIdentifier(
val isPrimaryKey = primaryKeyId == subkeyId
fun matches(fingerprint: OpenPgpFingerprint) =
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
override fun equals(other: Any?): Boolean {
if (other == null) {
@ -47,7 +60,8 @@ class SubkeyIdentifier(
return false
}
return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint
return primaryKeyFingerprint == other.primaryKeyFingerprint &&
subkeyFingerprint == other.subkeyFingerprint
}
override fun hashCode(): Int {
@ -55,4 +69,4 @@ class SubkeyIdentifier(
}
override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint"
}
}

View File

@ -2,47 +2,47 @@
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key;
package org.pgpainless.key
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
import java.nio.Buffer
import java.nio.ByteBuffer
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
/**
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
* key version.
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. Since both
* fingerprints use the same format, this class is used when parsing the fingerprint without knowing
* the key version.
*/
open class _64DigitFingerprint: OpenPgpFingerprint {
open class _64DigitFingerprint : OpenPgpFingerprint {
/**
* Create an {@link _64DigitFingerprint}.
*
* @param fingerprint uppercase hexadecimal fingerprint of length 64
*/
constructor(fingerprint: String): super(fingerprint)
constructor(bytes: ByteArray): super(bytes)
constructor(key: PGPPublicKey): super(key)
constructor(key: PGPSecretKey): super(key)
constructor(keys: PGPKeyRing): super(keys)
constructor(fingerprint: String) : super(fingerprint)
constructor(bytes: ByteArray) : super(bytes)
constructor(key: PGPPublicKey) : super(key)
constructor(key: PGPSecretKey) : super(key)
constructor(keys: PGPKeyRing) : super(keys)
override val keyId: Long
get() {
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).
// We have to cast here in order to be compatible with java 8
// https://github.com/eclipse/jetty.project/issues/3244
(buf as Buffer).position(0);
(buf as Buffer).position(0)
return buf.getLong()
}
@ -62,13 +62,13 @@ open class _64DigitFingerprint: OpenPgpFingerprint {
override fun prettyPrint(): String {
return buildString {
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) {
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
import java.util.*
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKey
@ -22,28 +23,27 @@ import org.pgpainless.key.util.KeyRingUtils
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
import org.pgpainless.signature.subpackets.CertificationSubpackets
import java.util.*
/**
* API for creating certifications and delegations (Signatures) on keys.
* This API can be used to sign another persons OpenPGP key.
* API for creating certifications and delegations (Signatures) on keys. This API can be used to
* 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
* to the owner of the certificate.
* A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer.
* A certification over a user-id is thereby used to attest, that the user believes that the user-id
* really belongs to the owner of the certificate. A delegation over a key can be used to delegate
* trust by marking the certificate as a trusted introducer.
*/
class CertifyCertificate {
/**
* Create a certification over a User-Id.
* By default, this method will use [CertificationType.GENERIC] to create the signature.
* Create a certification over a User-Id. By default, this method will use
* [CertificationType.GENERIC] to create the signature.
*
* @param userId user-id to certify
* @param certificate certificate
* @return API
*/
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId =
userIdOnCertificate(userId, certificate, CertificationType.GENERIC)
userIdOnCertificate(userId, certificate, CertificationType.GENERIC)
/**
* Create a certification of the given [CertificationType] over a User-Id.
@ -53,36 +53,40 @@ class CertifyCertificate {
* @param certificationType type of signature
* @return API
*/
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) =
CertificationOnUserId(userId, certificate, certificationType)
fun userIdOnCertificate(
userId: String,
certificate: PGPPublicKeyRing,
certificationType: CertificationType
) = CertificationOnUserId(userId, certificate, certificationType)
/**
* Create a delegation (direct key signature) over a certificate.
* This can be used to mark a certificate as a trusted introducer
* (see [certificate] method with [Trustworthiness] argument).
* Create a delegation (direct key signature) over a certificate. This can be used to mark a
* certificate as a trusted introducer (see [certificate] method with [Trustworthiness]
* argument).
*
* @param certificate certificate
* @return API
*/
fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate =
certificate(certificate, null)
certificate(certificate, null)
/**
* Create a delegation (direct key signature) containing a [org.bouncycastle.bcpg.sig.TrustSignature] packet
* over a certificate.
* This can be used to mark a certificate as a trusted introducer.
* Create a delegation (direct key signature) containing a
* [org.bouncycastle.bcpg.sig.TrustSignature] packet over a certificate. This can be used to
* mark a certificate as a trusted introducer.
*
* @param certificate certificate
* @param trustworthiness trustworthiness of the certificate
* @return API
*/
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
DelegationOnCertificate(certificate, trustworthiness)
DelegationOnCertificate(certificate, trustworthiness)
class CertificationOnUserId(
val userId: String,
val certificate: PGPPublicKeyRing,
val certificationType: CertificationType) {
val userId: String,
val certificate: PGPPublicKeyRing,
val certificationType: CertificationType
) {
/**
* Create the certification using the given key.
@ -92,11 +96,14 @@ class CertifyCertificate {
* @return API
* @throws PGPException in case of an OpenPGP related error
*/
fun withKey(certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets {
fun withKey(
certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector
): CertificationOnUserIdWithSubpackets {
val secretKey = getCertifyingSecretKey(certificationKey)
val sigBuilder = ThirdPartyCertificationSignatureBuilder(
val sigBuilder =
ThirdPartyCertificationSignatureBuilder(
certificationType.asSignatureType(), secretKey, protector)
return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder)
@ -104,9 +111,9 @@ class CertifyCertificate {
}
class CertificationOnUserIdWithSubpackets(
val certificate: PGPPublicKeyRing,
val userId: String,
val sigBuilder: ThirdPartyCertificationSignatureBuilder
val certificate: PGPPublicKeyRing,
val userId: String,
val sigBuilder: ThirdPartyCertificationSignatureBuilder
) {
/**
@ -116,7 +123,9 @@ class CertifyCertificate {
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult {
fun buildWithSubpackets(
subpacketCallback: CertificationSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketCallback)
return build()
}
@ -129,14 +138,16 @@ class CertifyCertificate {
*/
fun build(): CertificationResult {
val signature = sigBuilder.build(certificate, userId)
val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature)
val certifiedCertificate =
KeyRingUtils.injectCertification(certificate, userId, signature)
return CertificationResult(certifiedCertificate, signature)
}
}
class DelegationOnCertificate(
val certificate: PGPPublicKeyRing,
val trustworthiness: Trustworthiness?) {
val certificate: PGPPublicKeyRing,
val trustworthiness: Trustworthiness?
) {
/**
* Build the delegation using the given certification key.
@ -146,20 +157,24 @@ class CertifyCertificate {
* @return API
* @throws PGPException in case of an OpenPGP related error
*/
fun withKey(certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets {
fun withKey(
certificationKey: PGPSecretKeyRing,
protector: SecretKeyRingProtector
): DelegationOnCertificateWithSubpackets {
val secretKey = getCertifyingSecretKey(certificationKey)
val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector)
if (trustworthiness != null) {
sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount)
sigBuilder.hashedSubpackets.setTrust(
true, trustworthiness.depth, trustworthiness.amount)
}
return DelegationOnCertificateWithSubpackets(certificate, sigBuilder)
}
}
class DelegationOnCertificateWithSubpackets(
val certificate: PGPPublicKeyRing,
val sigBuilder: ThirdPartyDirectKeySignatureBuilder) {
val certificate: PGPPublicKeyRing,
val sigBuilder: ThirdPartyDirectKeySignatureBuilder
) {
/**
* Apply the given signature subpackets and build the delegation signature.
@ -168,7 +183,9 @@ class CertifyCertificate {
* @return result
* @throws PGPException in case of an OpenPGP related error
*/
fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult {
fun buildWithSubpackets(
subpacketsCallback: CertificationSubpackets.Callback
): CertificationResult {
sigBuilder.applyCallback(subpacketsCallback)
return build()
}
@ -182,7 +199,8 @@ class CertifyCertificate {
fun build(): CertificationResult {
val delegatedKey = certificate.publicKey
val delegation = sigBuilder.build(delegatedKey)
val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
val delegatedCertificate =
KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
return CertificationResult(delegatedCertificate, delegation)
}
}
@ -194,8 +212,9 @@ class CertifyCertificate {
* @param certification the newly created signature
*/
data class CertificationResult(
val certifiedCertificate: PGPPublicKeyRing,
val certification: PGPSignature)
val certifiedCertificate: PGPPublicKeyRing,
val certification: PGPSignature
)
companion object {
@JvmStatic
@ -205,9 +224,7 @@ class CertifyCertificate {
val fingerprint = info.fingerprint
val certificationPubKey = info.getPublicKey(fingerprint)
requireNotNull(certificationPubKey) {
"Primary key cannot be null."
}
requireNotNull(certificationPubKey) { "Primary key cannot be null." }
if (!info.isKeyValidlyBound(certificationPubKey.keyID)) {
throw RevokedKeyException(fingerprint)
}
@ -222,7 +239,7 @@ class CertifyCertificate {
}
return certificationKey.getSecretKey(certificationPubKey.keyID)
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
}
}
}
}

View File

@ -4,32 +4,38 @@
package org.pgpainless.key.collection
import java.io.InputStream
import org.bouncycastle.openpgp.*
import org.pgpainless.implementation.ImplementationFactory
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
* [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was
* inspired by [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
*/
class PGPKeyRingCollection(
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
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.
*
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this
* collection
*/
val size: Int
get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size()
@ -41,11 +47,16 @@ class PGPKeyRingCollection(
companion object {
@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 certificates = mutableListOf<PGPPublicKeyRing>()
// 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) {
if (obj == null) {
@ -68,16 +79,21 @@ class PGPKeyRingCollection(
}
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")
}
}
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
return PGPSecretKeyRingCollection(secretKeyRings) to
PGPPublicKeyRingCollection(certificates)
}
@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 certificates = mutableListOf<PGPPublicKeyRing>()
@ -87,12 +103,14 @@ class PGPKeyRingCollection(
} else if (keyRing is PGPPublicKeyRing) {
certificates.add(keyRing)
} 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")
}
}
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
return PGPSecretKeyRingCollection(secretKeyRings) to
PGPPublicKeyRingCollection(certificates)
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -4,18 +4,18 @@
package org.pgpainless.key.generation
import java.util.*
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector
import org.pgpainless.algorithm.KeyFlag
import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.signature.subpackets.SignatureSubpackets
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
import java.util.*
data class KeySpec(
val keyType: KeyType,
val subpacketGenerator: SignatureSubpackets,
val isInheritedSubPackets: Boolean,
val keyCreationDate: Date
val keyType: KeyType,
val subpacketGenerator: SignatureSubpackets,
val isInheritedSubPackets: Boolean,
val keyCreationDate: Date
) {
val subpackets: PGPSignatureSubpacketVector
@ -25,4 +25,4 @@ data class KeySpec(
@JvmStatic
fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags)
}
}
}

View File

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

View File

@ -4,20 +4,24 @@
package org.pgpainless.key.generation
import java.util.*
import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.HashAlgorithm
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import java.util.*
interface KeySpecBuilderInterface {
fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder
fun overridePreferredCompressionAlgorithms(
vararg algorithms: CompressionAlgorithm
): 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 build(): KeySpec
}
}

View File

@ -7,4 +7,4 @@ package org.pgpainless.key.generation.type
interface KeyLength {
val length: Int
}
}

View File

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

View File

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

View File

@ -16,7 +16,6 @@ class ECDH private constructor(val curve: EllipticCurve) : KeyType {
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
companion object {
@JvmStatic
fun fromCurve(curve: EllipticCurve) = ECDH(curve)
@JvmStatic 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)
companion object {
@JvmStatic
fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
@JvmStatic 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)
companion object {
@JvmStatic
fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
@JvmStatic fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,4 +14,4 @@ enum class RsaLength(override val length: Int) : KeyLength {
_3072(3072),
_4096(4096),
_8192(8192)
}
}

View File

@ -8,14 +8,13 @@ import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec
import org.pgpainless.algorithm.PublicKeyAlgorithm
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 algorithm = PublicKeyAlgorithm.ECDH
override val bitStrength = spec.bitStrength
override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName)
companion object {
@JvmStatic
fun fromSpec(spec: XDHSpec) = XDH(spec)
@JvmStatic fun fromSpec(spec: XDHSpec) = XDH(spec)
}
}
}

View File

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

View File

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

View File

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

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