Apply new formatting from 'gradle spotlessApply'
This commit is contained in:
parent
633048fd2a
commit
51e9bfc67f
|
@ -11,40 +11,38 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* Return a new date which represents this date plus the given amount of seconds added.
|
* Return a new date which represents this date plus the given amount of seconds added.
|
||||||
*
|
*
|
||||||
* Since '0' is a special date value in the OpenPGP specification
|
* Since '0' is a special date value in the OpenPGP specification (e.g. '0' means no expiration for
|
||||||
* (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0.
|
* expiration dates), this method will return 'null' if seconds is 0.
|
||||||
*
|
*
|
||||||
* @param date date
|
* @param date date
|
||||||
* @param seconds number of seconds to be added
|
* @param seconds number of seconds to be added
|
||||||
* @return date plus seconds or null if seconds is '0'
|
* @return date plus seconds or null if seconds is '0'
|
||||||
*/
|
*/
|
||||||
fun Date.plusSeconds(seconds: Long): Date? {
|
fun Date.plusSeconds(seconds: Long): Date? {
|
||||||
require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." }
|
require(Long.MAX_VALUE - time > seconds) {
|
||||||
return if (seconds == 0L) null
|
"Adding $seconds seconds to this date would cause time to overflow."
|
||||||
else Date(this.time + 1000 * seconds)
|
}
|
||||||
|
return if (seconds == 0L) null else Date(this.time + 1000 * seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
val Date.asSeconds: Long
|
val Date.asSeconds: Long
|
||||||
get() = time / 1000
|
get() = time / 1000
|
||||||
|
|
||||||
fun Date.secondsTill(later: Date): Long {
|
fun Date.secondsTill(later: Date): Long {
|
||||||
require(this <= later) {
|
require(this <= later) { "Timestamp MUST be before the later timestamp." }
|
||||||
"Timestamp MUST be before the later timestamp."
|
|
||||||
}
|
|
||||||
return later.asSeconds - this.asSeconds
|
return later.asSeconds - this.asSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Return a new [Date] instance with this instance's time floored down to seconds precision. */
|
||||||
* Return a new [Date] instance with this instance's time floored down to seconds precision.
|
|
||||||
*/
|
|
||||||
fun Date.toSecondsPrecision(): Date {
|
fun Date.toSecondsPrecision(): Date {
|
||||||
return Date(asSeconds * 1000)
|
return Date(asSeconds * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val parser: SimpleDateFormat
|
internal val parser: SimpleDateFormat
|
||||||
// Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation.
|
// Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every
|
||||||
get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z")
|
// invocation.
|
||||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
get() =
|
||||||
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format a date as UTC timestamp.
|
* Format a date as UTC timestamp.
|
||||||
|
@ -55,12 +53,13 @@ fun Date.formatUTC(): String = parser.format(this)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a UTC timestamp into a date.
|
* Parse a UTC timestamp into a date.
|
||||||
|
*
|
||||||
* @return date
|
* @return date
|
||||||
*/
|
*/
|
||||||
fun String.parseUTC(): Date {
|
fun String.parseUTC(): Date {
|
||||||
return try {
|
return try {
|
||||||
parser.parse(this)
|
parser.parse(this)
|
||||||
} catch (e : ParseException) {
|
} catch (e: ParseException) {
|
||||||
throw IllegalArgumentException("Malformed UTC timestamp: $this", e)
|
throw IllegalArgumentException("Malformed UTC timestamp: $this", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,18 @@
|
||||||
|
|
||||||
package openpgp
|
package openpgp
|
||||||
|
|
||||||
/**
|
/** Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). */
|
||||||
* Format this Long as an OpenPGP key-ID (16 digit uppercase hex number).
|
|
||||||
*/
|
|
||||||
fun Long.openPgpKeyId(): String {
|
fun Long.openPgpKeyId(): String {
|
||||||
return String.format("%016X", this).uppercase()
|
return String.format("%016X", this).uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Parse a Long form a 16 digit hex encoded OpenPgp key-ID. */
|
||||||
* Parse a Long form a 16 digit hex encoded OpenPgp key-ID.
|
|
||||||
*/
|
|
||||||
fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long {
|
fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long {
|
||||||
require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) {
|
require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) {
|
||||||
"Provided long key-id does not match expected format. " +
|
"Provided long key-id does not match expected format. " +
|
||||||
"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.
|
// Calling toLong() only fails with a NumberFormatException.
|
||||||
// Therefore, we call toULong(16).toLong(), which seems to work.
|
// Therefore, we call toULong(16).toLong(), which seems to work.
|
||||||
return hexKeyId.toULong(16).toLong()
|
return hexKeyId.toULong(16).toLong()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,40 +11,46 @@ import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactor
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys.
|
* Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. That
|
||||||
* That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted.
|
* way, if a message needs to be decrypted multiple times, expensive private key operations can be
|
||||||
|
* omitted.
|
||||||
*
|
*
|
||||||
* This implementation changes the behavior or [recoverSessionData] to first return any
|
* This implementation changes the behavior or [recoverSessionData] to first return any cache hits.
|
||||||
* cache hits.
|
* If no hit is found, the method call is delegated to the underlying
|
||||||
* If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory].
|
* [PublicKeyDataDecryptorFactory]. The result of that is then placed in the cache and returned.
|
||||||
* The result of that is then placed in the cache and returned.
|
|
||||||
*/
|
*/
|
||||||
class CachingBcPublicKeyDataDecryptorFactory(
|
class CachingBcPublicKeyDataDecryptorFactory(
|
||||||
privateKey: PGPPrivateKey,
|
privateKey: PGPPrivateKey,
|
||||||
override val subkeyIdentifier: SubkeyIdentifier
|
override val subkeyIdentifier: SubkeyIdentifier
|
||||||
) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory {
|
) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory {
|
||||||
|
|
||||||
private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf()
|
private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf()
|
||||||
|
|
||||||
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray =
|
override fun recoverSessionData(
|
||||||
lookupSessionKeyData(secKeyData) ?:
|
keyAlgorithm: Int,
|
||||||
costlyRecoverSessionData(keyAlgorithm, secKeyData)
|
secKeyData: Array<out ByteArray>
|
||||||
.also { cacheSessionKeyData(secKeyData, it) }
|
): ByteArray =
|
||||||
|
lookupSessionKeyData(secKeyData)
|
||||||
|
?: costlyRecoverSessionData(keyAlgorithm, secKeyData).also {
|
||||||
|
cacheSessionKeyData(secKeyData, it)
|
||||||
|
}
|
||||||
|
|
||||||
private fun lookupSessionKeyData(secKeyData: Array<out ByteArray>): ByteArray? =
|
private fun lookupSessionKeyData(secKeyData: Array<out ByteArray>): ByteArray? =
|
||||||
cachedSessions[toKey(secKeyData)]?.clone()
|
cachedSessions[toKey(secKeyData)]?.clone()
|
||||||
|
|
||||||
private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray =
|
private fun costlyRecoverSessionData(
|
||||||
super.recoverSessionData(keyAlgorithm, secKeyData)
|
keyAlgorithm: Int,
|
||||||
|
secKeyData: Array<out ByteArray>
|
||||||
|
): ByteArray = super.recoverSessionData(keyAlgorithm, secKeyData)
|
||||||
|
|
||||||
private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) {
|
private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) {
|
||||||
cachedSessions[toKey(secKeyData)] = sessionKey.clone()
|
cachedSessions[toKey(secKeyData)] = sessionKey.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toKey(secKeyData: Array<out ByteArray>): String =
|
private fun toKey(secKeyData: Array<out ByteArray>): String =
|
||||||
Base64.toBase64String(secKeyData[0])
|
Base64.toBase64String(secKeyData[0])
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
cachedSessions.clear()
|
cachedSessions.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,10 @@ import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
|
||||||
/**
|
/** Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. */
|
||||||
* Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier].
|
|
||||||
*/
|
|
||||||
fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
|
fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
|
||||||
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
|
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
|
||||||
this.getPublicKey(subkeyIdentifier.subkeyId) != null
|
this.getPublicKey(subkeyIdentifier.subkeyId) != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the [PGPKeyRing] contains a public key with the given key-ID.
|
* 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
|
* @param keyId keyId
|
||||||
* @return true if key with the given key-ID is present, false otherwise
|
* @return true if key with the given key-ID is present, false otherwise
|
||||||
*/
|
*/
|
||||||
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
|
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = this.getPublicKey(keyId) != null
|
||||||
this.getPublicKey(keyId) != null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the [PGPKeyRing] contains a public key with the given fingerprint.
|
* Return true, if the [PGPKeyRing] contains a public key with the given fingerprint.
|
||||||
|
@ -36,7 +33,7 @@ fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
|
||||||
* @return true if key with the given fingerprint is present, false otherwise
|
* @return true if key with the given fingerprint is present, false otherwise
|
||||||
*/
|
*/
|
||||||
fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean =
|
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.
|
* 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
|
* @return public key
|
||||||
*/
|
*/
|
||||||
fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
|
fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
|
||||||
this.getPublicKey(fingerprint.bytes)
|
this.getPublicKey(fingerprint.bytes)
|
||||||
|
|
||||||
fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey =
|
fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey =
|
||||||
getPublicKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
getPublicKey(keyId)
|
||||||
|
?: throw NoSuchElementException(
|
||||||
|
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||||
|
|
||||||
fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey =
|
fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey =
|
||||||
getPublicKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.")
|
getPublicKey(fingerprint)
|
||||||
|
?: throw NoSuchElementException(
|
||||||
|
"OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature].
|
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
|
||||||
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to
|
* the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
|
||||||
* identify the [PGPPublicKey] via its key-ID.
|
* subpacket to identify the [PGPPublicKey] via its key-ID.
|
||||||
*/
|
*/
|
||||||
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
|
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
|
||||||
signature.fingerprint?.let { this.getPublicKey(it) } ?:
|
signature.fingerprint?.let { this.getPublicKey(it) } ?: this.getPublicKey(signature.keyID)
|
||||||
this.getPublicKey(signature.keyID)
|
|
||||||
|
|
||||||
/**
|
/** Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
|
||||||
* Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
|
|
||||||
*/
|
|
||||||
fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? =
|
fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? =
|
||||||
this.getPublicKey(onePassSignature.keyID)
|
this.getPublicKey(onePassSignature.keyID)
|
||||||
|
|
||||||
/**
|
/** Return the [OpenPgpFingerprint] of this OpenPGP key. */
|
||||||
* Return the [OpenPgpFingerprint] of this OpenPGP key.
|
|
||||||
*/
|
|
||||||
val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint
|
val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint
|
||||||
get() = OpenPgpFingerprint.of(this)
|
get() = OpenPgpFingerprint.of(this)
|
||||||
|
|
||||||
/**
|
/** Return this OpenPGP key as an ASCII armored String. */
|
||||||
* Return this OpenPGP key as an ASCII armored String.
|
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)
|
||||||
*/
|
|
||||||
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA],
|
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and
|
||||||
* this method returns the name of the underlying elliptic curve.
|
* [PublicKeyAlgorithm.EDDSA], this method returns the name of the underlying elliptic curve.
|
||||||
*
|
*
|
||||||
* For other key types or unknown curves, this method throws an [IllegalArgumentException].
|
* For other key types or unknown curves, this method throws an [IllegalArgumentException].
|
||||||
*
|
*
|
||||||
|
@ -24,27 +24,29 @@ import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
||||||
*/
|
*/
|
||||||
fun PGPPublicKey.getCurveName(): String {
|
fun PGPPublicKey.getCurveName(): String {
|
||||||
PublicKeyAlgorithm.requireFromId(algorithm)
|
PublicKeyAlgorithm.requireFromId(algorithm)
|
||||||
.let {
|
.let {
|
||||||
when (it) {
|
when (it) {
|
||||||
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
|
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
|
||||||
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
|
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
|
||||||
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
|
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
|
||||||
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
|
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID}
|
}
|
||||||
.let { it to ECUtil.getCurveName(it) }
|
.let {
|
||||||
.let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") }
|
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
|
val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm
|
||||||
get() = PublicKeyAlgorithm.requireFromId(algorithm)
|
get() = PublicKeyAlgorithm.requireFromId(algorithm)
|
||||||
|
|
||||||
/**
|
/** Return the [OpenPgpFingerprint] of this key. */
|
||||||
* Return the [OpenPgpFingerprint] of this key.
|
|
||||||
*/
|
|
||||||
val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint
|
val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint
|
||||||
get() = OpenPgpFingerprint.of(this)
|
get() = OpenPgpFingerprint.of(this)
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.bouncycastle.extensions
|
||||||
import org.bouncycastle.bcpg.S2K
|
import org.bouncycastle.bcpg.S2K
|
||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey
|
import org.bouncycastle.openpgp.PGPPrivateKey
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
|
@ -28,7 +27,7 @@ import org.pgpainless.util.Passphrase
|
||||||
*/
|
*/
|
||||||
@Throws(PGPException::class, KeyIntegrityException::class)
|
@Throws(PGPException::class, KeyIntegrityException::class)
|
||||||
fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
|
fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
|
||||||
UnlockSecretKey.unlockSecretKey(this, passphrase)
|
UnlockSecretKey.unlockSecretKey(this, passphrase)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock the secret key to get its [PGPPrivateKey].
|
* Unlock the secret key to get its [PGPPrivateKey].
|
||||||
|
@ -39,8 +38,9 @@ fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
|
||||||
*/
|
*/
|
||||||
@Throws(PGPException::class, KeyIntegrityException::class)
|
@Throws(PGPException::class, KeyIntegrityException::class)
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey =
|
fun PGPSecretKey.unlock(
|
||||||
UnlockSecretKey.unlockSecretKey(this, protector)
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||||
|
): PGPPrivateKey = UnlockSecretKey.unlockSecretKey(this, protector)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unlock the secret key to get its [PGPPrivateKey].
|
* Unlock the secret key to get its [PGPPrivateKey].
|
||||||
|
@ -74,14 +74,10 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0)
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K)
|
fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K)
|
||||||
|
|
||||||
/**
|
/** Return the [PublicKeyAlgorithm] of this key. */
|
||||||
* Return the [PublicKeyAlgorithm] of this key.
|
|
||||||
*/
|
|
||||||
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
|
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
|
||||||
get() = publicKey.publicKeyAlgorithm
|
get() = publicKey.publicKeyAlgorithm
|
||||||
|
|
||||||
/**
|
/** Return the [OpenPgpFingerprint] of this key. */
|
||||||
* Return the [OpenPgpFingerprint] of this key.
|
|
||||||
*/
|
|
||||||
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
|
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
|
||||||
get() = OpenPgpFingerprint.of(this)
|
get() = OpenPgpFingerprint.of(this)
|
||||||
|
|
|
@ -8,9 +8,7 @@ import openpgp.openPgpKeyId
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
|
|
||||||
/**
|
/** OpenPGP certificate containing the public keys of this OpenPGP key. */
|
||||||
* OpenPGP certificate containing the public keys of this OpenPGP key.
|
|
||||||
*/
|
|
||||||
val PGPSecretKeyRing.certificate: PGPPublicKeyRing
|
val PGPSecretKeyRing.certificate: PGPPublicKeyRing
|
||||||
get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList())
|
get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList())
|
||||||
|
|
||||||
|
@ -20,8 +18,7 @@ val PGPSecretKeyRing.certificate: PGPPublicKeyRing
|
||||||
* @param keyId keyId of the secret key
|
* @param keyId keyId of the secret key
|
||||||
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean =
|
fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = this.getSecretKey(keyId) != null
|
||||||
this.getSecretKey(keyId) != null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint.
|
* Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint.
|
||||||
|
@ -30,7 +27,7 @@ fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean =
|
||||||
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
|
fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
|
||||||
this.getSecretKey(fingerprint) != null
|
this.getSecretKey(fingerprint) != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [PGPSecretKey] with the given [OpenPgpFingerprint].
|
* Return the [PGPSecretKey] with the given [OpenPgpFingerprint].
|
||||||
|
@ -39,41 +36,44 @@ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
|
||||||
* @return the secret key or null
|
* @return the secret key or null
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
|
fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
|
||||||
this.getSecretKey(fingerprint.bytes)
|
this.getSecretKey(fingerprint.bytes)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [PGPSecretKey] with the given key-ID.
|
* Return the [PGPSecretKey] with the given key-ID.
|
||||||
*
|
*
|
||||||
* @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given key-ID
|
* @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given
|
||||||
|
* key-ID
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey =
|
fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey =
|
||||||
getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
getSecretKey(keyId)
|
||||||
|
?: throw NoSuchElementException(
|
||||||
|
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [PGPSecretKey] with the given fingerprint.
|
* Return the [PGPSecretKey] with the given fingerprint.
|
||||||
*
|
*
|
||||||
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given fingerprint
|
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given
|
||||||
|
* fingerprint
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey =
|
fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey =
|
||||||
getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.")
|
getSecretKey(fingerprint)
|
||||||
|
?: throw NoSuchElementException(
|
||||||
|
"OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature].
|
* Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
|
||||||
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to
|
* the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
|
||||||
* identify the [PGPSecretKey] via its key-ID.
|
* subpacket to identify the [PGPSecretKey] via its key-ID.
|
||||||
*/
|
*/
|
||||||
fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? =
|
fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? =
|
||||||
signature.fingerprint?.let { this.getSecretKey(it) } ?:
|
signature.fingerprint?.let { this.getSecretKey(it) } ?: this.getSecretKey(signature.keyID)
|
||||||
this.getSecretKey(signature.keyID)
|
|
||||||
|
|
||||||
/**
|
/** Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
|
||||||
* Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
|
|
||||||
*/
|
|
||||||
fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? =
|
fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? =
|
||||||
this.getSecretKey(onePassSignature.keyID)
|
this.getSecretKey(onePassSignature.keyID)
|
||||||
|
|
||||||
fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? =
|
fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? =
|
||||||
when(pkesk.version) {
|
when (pkesk.version) {
|
||||||
3 -> this.getSecretKey(pkesk.keyID)
|
3 -> this.getSecretKey(pkesk.keyID)
|
||||||
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
|
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.bouncycastle.extensions
|
package org.bouncycastle.extensions
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import openpgp.plusSeconds
|
import openpgp.plusSeconds
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
import org.bouncycastle.openpgp.PGPSignature
|
||||||
|
@ -12,84 +13,84 @@ import org.pgpainless.algorithm.SignatureType
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.key.util.RevocationAttributes.Reason
|
import org.pgpainless.key.util.RevocationAttributes.Reason
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry
|
* Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry
|
||||||
* such a subpacket.
|
* such a subpacket.
|
||||||
*/
|
*/
|
||||||
fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? =
|
fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? =
|
||||||
SignatureSubpacketsUtil.getKeyExpirationTime(this)
|
SignatureSubpacketsUtil.getKeyExpirationTime(this)?.let { keyCreationDate.plusSeconds(it.time) }
|
||||||
?.let { keyCreationDate.plusSeconds(it.time) }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the value of the signature ExpirationTime subpacket, or null, if the signature
|
* Return the value of the signature ExpirationTime subpacket, or null, if the signature does not
|
||||||
* does not carry such a subpacket.
|
* carry such a subpacket.
|
||||||
*/
|
*/
|
||||||
val PGPSignature.signatureExpirationDate: Date?
|
val PGPSignature.signatureExpirationDate: Date?
|
||||||
get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this)
|
get() =
|
||||||
?.let { this.creationTime.plusSeconds(it.time) }
|
SignatureSubpacketsUtil.getSignatureExpirationTime(this)?.let {
|
||||||
|
this.creationTime.plusSeconds(it.time)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** Return true, if the signature is expired at the given reference time. */
|
||||||
* Return true, if the signature is expired at the given reference time.
|
|
||||||
*/
|
|
||||||
fun PGPSignature.isExpired(referenceTime: Date = Date()) =
|
fun PGPSignature.isExpired(referenceTime: Date = Date()) =
|
||||||
signatureExpirationDate?.let { referenceTime >= it } ?: false
|
signatureExpirationDate?.let { referenceTime >= it } ?: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint
|
* Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint
|
||||||
* subpackets of the signature.
|
* subpackets of the signature.
|
||||||
*/
|
*/
|
||||||
val PGPSignature.issuerKeyId: Long
|
val PGPSignature.issuerKeyId: Long
|
||||||
get() = when (version) {
|
get() =
|
||||||
2, 3 -> keyID
|
when (version) {
|
||||||
else -> {
|
2,
|
||||||
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)
|
3 -> keyID
|
||||||
?.let { if (it != 0L) it else null }
|
else -> {
|
||||||
?: fingerprint?.keyId
|
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)?.let {
|
||||||
?: 0L
|
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 =
|
fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean =
|
||||||
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId)
|
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the signature was likely issued by a key with the given fingerprint.
|
* Return true, if the signature was likely issued by a key with the given fingerprint.
|
||||||
|
*
|
||||||
* @param fingerprint fingerprint bytes
|
* @param fingerprint fingerprint bytes
|
||||||
*/
|
*/
|
||||||
@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.")
|
@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.")
|
||||||
fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean =
|
fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean =
|
||||||
try {
|
try {
|
||||||
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
|
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
|
||||||
} catch (e : IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// Unknown fingerprint length / format
|
// Unknown fingerprint length / format
|
||||||
false
|
false
|
||||||
}
|
|
||||||
|
|
||||||
fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean =
|
|
||||||
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.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() =
|
fun PGPSignature?.toRevocationState() =
|
||||||
if (this == null) RevocationState.notRevoked()
|
if (this == null) RevocationState.notRevoked()
|
||||||
else if (isHardRevocation) RevocationState.hardRevoked()
|
else if (isHardRevocation) RevocationState.hardRevoked()
|
||||||
else RevocationState.softRevoked(creationTime)
|
else RevocationState.softRevoked(creationTime)
|
||||||
|
|
||||||
val PGPSignature.fingerprint: OpenPgpFingerprint?
|
val PGPSignature.fingerprint: OpenPgpFingerprint?
|
||||||
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
|
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package org.pgpainless
|
package org.pgpainless
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||||
|
@ -19,8 +21,6 @@ import org.pgpainless.key.parsing.KeyRingReader
|
||||||
import org.pgpainless.key.util.KeyRingUtils
|
import org.pgpainless.key.util.KeyRingUtils
|
||||||
import org.pgpainless.policy.Policy
|
import org.pgpainless.policy.Policy
|
||||||
import org.pgpainless.util.ArmorUtils
|
import org.pgpainless.util.ArmorUtils
|
||||||
import java.io.OutputStream
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class PGPainless private constructor() {
|
class PGPainless private constructor() {
|
||||||
|
|
||||||
|
@ -28,25 +28,24 @@ class PGPainless private constructor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a fresh OpenPGP key ring from predefined templates.
|
* Generate a fresh OpenPGP key ring from predefined templates.
|
||||||
|
*
|
||||||
* @return templates
|
* @return templates
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun generateKeyRing() = KeyRingTemplates()
|
||||||
fun generateKeyRing() = KeyRingTemplates()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a custom OpenPGP key ring.
|
* Build a custom OpenPGP key ring.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun buildKeyRing() = KeyRingBuilder()
|
||||||
fun buildKeyRing() = KeyRingBuilder()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an existing OpenPGP key ring.
|
* Read an existing OpenPGP key ring.
|
||||||
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun readKeyRing() = KeyRingReader()
|
||||||
fun readKeyRing() = KeyRingReader()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract a public key certificate from a secret key.
|
* Extract a public key certificate from a secret key.
|
||||||
|
@ -56,10 +55,11 @@ class PGPainless private constructor() {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun extractCertificate(secretKey: PGPSecretKeyRing) =
|
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 originalCopy local, older copy of the cert
|
||||||
* @param updatedCopy updated, newer 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
|
* @throws PGPException in case of an error
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun mergeCertificate(originalCopy: PGPPublicKeyRing,
|
fun mergeCertificate(originalCopy: PGPPublicKeyRing, updatedCopy: PGPPublicKeyRing) =
|
||||||
updatedCopy: PGPPublicKeyRing) =
|
PGPPublicKeyRing.join(originalCopy, updatedCopy)
|
||||||
PGPPublicKeyRing.join(originalCopy, updatedCopy)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a key or certificate in ASCII armor.
|
* Wrap a key or certificate in ASCII armor.
|
||||||
*
|
*
|
||||||
* @param key key or certificate
|
* @param key key or certificate
|
||||||
* @return ascii armored string
|
* @return ascii armored string
|
||||||
*
|
|
||||||
* @throws IOException in case of an error during the armoring process
|
* @throws IOException in case of an error during the armoring process
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun asciiArmor(key: PGPKeyRing) =
|
fun asciiArmor(key: PGPKeyRing) =
|
||||||
if (key is PGPSecretKeyRing)
|
if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key)
|
||||||
ArmorUtils.toAsciiArmoredString(key)
|
else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
|
||||||
else
|
|
||||||
ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream].
|
* Wrap a key of certificate in ASCII armor and write the result into the given
|
||||||
|
* [OutputStream].
|
||||||
*
|
*
|
||||||
* @param key key or certificate
|
* @param key key or certificate
|
||||||
* @param outputStream output stream
|
* @param outputStream output stream
|
||||||
*
|
|
||||||
* @throws IOException in case of an error during the armoring process
|
* @throws IOException in case of an error during the armoring process
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -106,33 +102,34 @@ class PGPainless private constructor() {
|
||||||
*
|
*
|
||||||
* @param signature detached signature
|
* @param signature detached signature
|
||||||
* @return ascii armored string
|
* @return ascii armored string
|
||||||
*
|
|
||||||
* @throws IOException in case of an error during the armoring process
|
* @throws IOException in case of an error during the armoring process
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature)
|
fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP.
|
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using
|
||||||
|
* OpenPGP.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun encryptAndOrSign() = EncryptionBuilder()
|
||||||
fun encryptAndOrSign() = EncryptionBuilder()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP.
|
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using
|
||||||
|
* OpenPGP.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun decryptAndOrVerify() = DecryptionBuilder()
|
||||||
fun decryptAndOrVerify() = DecryptionBuilder()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make changes to a secret key at the given reference time.
|
* Make changes to a secret key at the given reference time. This method can be used to
|
||||||
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
|
* change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
|
||||||
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}.
|
* After making the desired changes in the builder, the modified key can be extracted using
|
||||||
|
* {@link SecretKeyRingEditorInterface#done()}.
|
||||||
*
|
*
|
||||||
* @param secretKeys secret key ring
|
* @param secretKeys secret key ring
|
||||||
* @param referenceTime reference time used as signature creation date
|
* @param referenceTime reference time used as signature creation date
|
||||||
|
@ -141,11 +138,12 @@ class PGPainless private constructor() {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) =
|
fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) =
|
||||||
SecretKeyRingEditor(secretKey, referenceTime)
|
SecretKeyRingEditor(secretKey, referenceTime)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing].
|
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] /
|
||||||
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time.
|
* [PGPSecretKeyRing]. This method can be used to determine expiration dates, key flags and
|
||||||
|
* other information about a key at a specific time.
|
||||||
*
|
*
|
||||||
* @param keyRing key ring
|
* @param keyRing key ring
|
||||||
* @param referenceTime date of inspection
|
* @param referenceTime date of inspection
|
||||||
|
@ -154,22 +152,20 @@ class PGPainless private constructor() {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
|
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.
|
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
|
||||||
*
|
*
|
||||||
* @return policy
|
* @return policy
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun getPolicy() = Policy.getInstance()
|
||||||
fun getPolicy() = Policy.getInstance()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create different kinds of signatures on other keys.
|
* Create different kinds of signatures on other keys.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun certify() = CertifyCertificate()
|
||||||
fun certify() = CertifyCertificate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class AEADAlgorithm(
|
enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: Int) {
|
||||||
val algorithmId: Int,
|
|
||||||
val ivLength: Int,
|
|
||||||
val tagLength: Int) {
|
|
||||||
EAX(1, 16, 16),
|
EAX(1, 16, 16),
|
||||||
OCB(2, 15, 16),
|
OCB(2, 15, 16),
|
||||||
GCM(3, 12, 16),
|
GCM(3, 12, 16),
|
||||||
|
@ -16,15 +13,12 @@ enum class AEADAlgorithm(
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Int): AEADAlgorithm? {
|
fun fromId(id: Int): AEADAlgorithm? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { algorithm -> algorithm.algorithmId == id }
|
||||||
algorithm -> algorithm.algorithmId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Int): AEADAlgorithm {
|
fun requireFromId(id: Int): AEADAlgorithm {
|
||||||
return fromId(id) ?:
|
return fromId(id) ?: throw NoSuchElementException("No AEADAlgorithm found for id $id")
|
||||||
throw NoSuchElementException("No AEADAlgorithm found for id $id")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
class AlgorithmSuite(
|
class AlgorithmSuite(
|
||||||
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
|
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
|
||||||
hashAlgorithms: List<HashAlgorithm>,
|
hashAlgorithms: List<HashAlgorithm>,
|
||||||
compressionAlgorithms: List<CompressionAlgorithm>) {
|
compressionAlgorithms: List<CompressionAlgorithm>
|
||||||
|
) {
|
||||||
|
|
||||||
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
|
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
|
||||||
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
|
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
|
||||||
|
@ -16,30 +17,31 @@ class AlgorithmSuite(
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val defaultSymmetricKeyAlgorithms = listOf(
|
val defaultSymmetricKeyAlgorithms =
|
||||||
|
listOf(
|
||||||
SymmetricKeyAlgorithm.AES_256,
|
SymmetricKeyAlgorithm.AES_256,
|
||||||
SymmetricKeyAlgorithm.AES_192,
|
SymmetricKeyAlgorithm.AES_192,
|
||||||
SymmetricKeyAlgorithm.AES_128)
|
SymmetricKeyAlgorithm.AES_128)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val defaultHashAlgorithms = listOf(
|
val defaultHashAlgorithms =
|
||||||
|
listOf(
|
||||||
HashAlgorithm.SHA512,
|
HashAlgorithm.SHA512,
|
||||||
HashAlgorithm.SHA384,
|
HashAlgorithm.SHA384,
|
||||||
HashAlgorithm.SHA256,
|
HashAlgorithm.SHA256,
|
||||||
HashAlgorithm.SHA224)
|
HashAlgorithm.SHA224)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val defaultCompressionAlgorithms = listOf(
|
val defaultCompressionAlgorithms =
|
||||||
|
listOf(
|
||||||
CompressionAlgorithm.ZLIB,
|
CompressionAlgorithm.ZLIB,
|
||||||
CompressionAlgorithm.BZIP2,
|
CompressionAlgorithm.BZIP2,
|
||||||
CompressionAlgorithm.ZIP,
|
CompressionAlgorithm.ZIP,
|
||||||
CompressionAlgorithm.UNCOMPRESSED)
|
CompressionAlgorithm.UNCOMPRESSED)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val defaultAlgorithmSuite = AlgorithmSuite(
|
val defaultAlgorithmSuite =
|
||||||
defaultSymmetricKeyAlgorithms,
|
AlgorithmSuite(
|
||||||
defaultHashAlgorithms,
|
defaultSymmetricKeyAlgorithms, defaultHashAlgorithms, defaultCompressionAlgorithms)
|
||||||
defaultCompressionAlgorithms)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,18 +4,17 @@
|
||||||
|
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class CertificationType(
|
enum class CertificationType(val signatureType: SignatureType) {
|
||||||
val signatureType: SignatureType
|
|
||||||
) {
|
|
||||||
/**
|
/**
|
||||||
* The issuer of this certification does not make any particular assertion as to how well the certifier has
|
* The issuer of this certification does not make any particular assertion as to how well the
|
||||||
* checked that the owner of the key is in fact the person described by the User ID.
|
* certifier has checked that the owner of the key is in fact the person described by the User
|
||||||
|
* ID.
|
||||||
*/
|
*/
|
||||||
GENERIC(SignatureType.GENERIC_CERTIFICATION),
|
GENERIC(SignatureType.GENERIC_CERTIFICATION),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The issuer of this certification has not done any verification of the claim that the owner of this key is
|
* The issuer of this certification has not done any verification of the claim that the owner of
|
||||||
* the User ID specified.
|
* this key is the User ID specified.
|
||||||
*/
|
*/
|
||||||
NONE(SignatureType.NO_CERTIFICATION),
|
NONE(SignatureType.NO_CERTIFICATION),
|
||||||
|
|
||||||
|
@ -31,4 +30,4 @@ enum class CertificationType(
|
||||||
;
|
;
|
||||||
|
|
||||||
fun asSignatureType() = signatureType
|
fun asSignatureType() = signatureType
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,22 +20,20 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
|
||||||
* If an invalid id is provided, null is returned.
|
* an invalid id is provided, null is returned.
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param id id
|
||||||
* @return compression algorithm
|
* @return compression algorithm
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Int): CompressionAlgorithm? {
|
fun fromId(id: Int): CompressionAlgorithm? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { c -> c.algorithmId == id }
|
||||||
c -> c.algorithmId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
|
||||||
* If an invalid id is provided, throw an [NoSuchElementException].
|
* an invalid id is provided, throw an [NoSuchElementException].
|
||||||
*
|
*
|
||||||
* @param id id
|
* @param id id
|
||||||
* @return compression algorithm
|
* @return compression algorithm
|
||||||
|
@ -43,8 +41,8 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Int): CompressionAlgorithm {
|
fun requireFromId(id: Int): CompressionAlgorithm {
|
||||||
return fromId(id) ?:
|
return fromId(id)
|
||||||
throw NoSuchElementException("No CompressionAlgorithm found for id $id")
|
?: throw NoSuchElementException("No CompressionAlgorithm found for id $id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,11 @@ package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class DocumentSignatureType(val signatureType: SignatureType) {
|
enum class DocumentSignatureType(val signatureType: SignatureType) {
|
||||||
|
|
||||||
/**
|
/** Signature is calculated over the unchanged binary data. */
|
||||||
* Signature is calculated over the unchanged binary data.
|
|
||||||
*/
|
|
||||||
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
|
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The signature is calculated over the text data with its line endings
|
* The signature is calculated over the text data with its line endings converted to `<CR><LF>`.
|
||||||
* converted to `<CR><LF>`.
|
|
||||||
*/
|
*/
|
||||||
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
|
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
|
||||||
;
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -5,18 +5,10 @@
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class EncryptionPurpose {
|
enum class EncryptionPurpose {
|
||||||
/**
|
/** The stream will encrypt communication that goes over the wire. E.g. EMail, Chat... */
|
||||||
* The stream will encrypt communication that goes over the wire.
|
|
||||||
* E.g. EMail, Chat...
|
|
||||||
*/
|
|
||||||
COMMUNICATIONS,
|
COMMUNICATIONS,
|
||||||
/**
|
/** The stream will encrypt data at rest. E.g. Encrypted backup... */
|
||||||
* The stream will encrypt data at rest.
|
|
||||||
* E.g. Encrypted backup...
|
|
||||||
*/
|
|
||||||
STORAGE,
|
STORAGE,
|
||||||
/**
|
/** The stream will use keys with either flags to encrypt the data. */
|
||||||
* The stream will use keys with either flags to encrypt the data.
|
|
||||||
*/
|
|
||||||
ANY
|
ANY
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,42 +12,44 @@ package org.pgpainless.algorithm
|
||||||
enum class Feature(val featureId: Byte) {
|
enum class Feature(val featureId: Byte) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification
|
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using
|
||||||
* Detection Code Packets.
|
* Modification Detection Code Packets.
|
||||||
*
|
*
|
||||||
* See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
|
* See
|
||||||
|
* [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
|
||||||
*/
|
*/
|
||||||
MODIFICATION_DETECTION(0x01),
|
MODIFICATION_DETECTION(0x01),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for Authenticated Encryption with Additional Data (AEAD).
|
* Support for Authenticated Encryption with Additional Data (AEAD). If a key announces this
|
||||||
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets.
|
* feature, it signals support for consuming AEAD Encrypted Data Packets.
|
||||||
*
|
*
|
||||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
|
||||||
* NOTE: This value is currently RESERVED.
|
|
||||||
*
|
*
|
||||||
* See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
|
* See
|
||||||
|
* [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
|
||||||
*/
|
*/
|
||||||
GNUPG_AEAD_ENCRYPTED_DATA(0x02),
|
GNUPG_AEAD_ENCRYPTED_DATA(0x02),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If a key announces this feature, it is a version 5 public key.
|
* If a key announces this feature, it is a version 5 public key. The version 5 format is
|
||||||
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material.
|
* similar to the version 4 format except for the addition of a count for the key material. This
|
||||||
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case
|
* count helps to parse secret key packets (which are an extension of the public key packet
|
||||||
* of an unknown algorithm.
|
* format) in the case of an unknown algorithm. In addition, fingerprints of version 5 keys are
|
||||||
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys.
|
* calculated differently from version 4 keys.
|
||||||
*
|
*
|
||||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
|
||||||
* NOTE: This value is currently RESERVED.
|
|
||||||
*
|
*
|
||||||
* See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
|
* See
|
||||||
|
* [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
|
||||||
*/
|
*/
|
||||||
GNUPG_VERSION_5_PUBLIC_KEY(0x04),
|
GNUPG_VERSION_5_PUBLIC_KEY(0x04),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
|
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
|
||||||
*
|
*
|
||||||
* See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
|
* See
|
||||||
|
* [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
|
||||||
*/
|
*/
|
||||||
MODIFICATION_DETECTION_2(0x08),
|
MODIFICATION_DETECTION_2(0x08),
|
||||||
;
|
;
|
||||||
|
@ -55,29 +57,26 @@ enum class Feature(val featureId: Byte) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Byte): Feature? {
|
fun fromId(id: Byte): Feature? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { f -> f.featureId == id }
|
||||||
f -> f.featureId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Byte): Feature {
|
fun requireFromId(id: Byte): Feature {
|
||||||
return fromId(id) ?:
|
return fromId(id) ?: throw NoSuchElementException("Unknown feature id encountered: $id")
|
||||||
throw NoSuchElementException("Unknown feature id encountered: $id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromBitmask(bitmask: Int): List<Feature> {
|
fun fromBitmask(bitmask: Int): List<Feature> {
|
||||||
return values().filter {
|
return values().filter { it.featureId.toInt() and bitmask != 0 }
|
||||||
it.featureId.toInt() and bitmask != 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun toBitmask(vararg features: Feature): Byte {
|
fun toBitmask(vararg features: Feature): Byte {
|
||||||
return features.map { it.featureId.toInt() }
|
return features
|
||||||
.reduceOrNull { mask, f -> mask or f }?.toByte()
|
.map { it.featureId.toInt() }
|
||||||
?: 0
|
.reduceOrNull { mask, f -> mask or f }
|
||||||
|
?.toByte()
|
||||||
|
?: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,36 +11,33 @@ package org.pgpainless.algorithm
|
||||||
*/
|
*/
|
||||||
enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
||||||
|
|
||||||
@Deprecated("MD5 is deprecated")
|
@Deprecated("MD5 is deprecated") MD5(1, "MD5"),
|
||||||
MD5 (1, "MD5"),
|
SHA1(2, "SHA1"),
|
||||||
SHA1 (2, "SHA1"),
|
RIPEMD160(3, "RIPEMD160"),
|
||||||
RIPEMD160 (3, "RIPEMD160"),
|
SHA256(8, "SHA256"),
|
||||||
SHA256 (8, "SHA256"),
|
SHA384(9, "SHA384"),
|
||||||
SHA384 (9, "SHA384"),
|
SHA512(10, "SHA512"),
|
||||||
SHA512 (10, "SHA512"),
|
SHA224(11, "SHA224"),
|
||||||
SHA224 (11, "SHA224"),
|
SHA3_256(12, "SHA3-256"),
|
||||||
SHA3_256 (12, "SHA3-256"),
|
SHA3_512(14, "SHA3-512"),
|
||||||
SHA3_512 (14, "SHA3-512"),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
|
||||||
* If an invalid algorithm id was provided, null is returned.
|
* invalid algorithm id was provided, null is returned.
|
||||||
*
|
*
|
||||||
* @param id numeric id
|
* @param id numeric id
|
||||||
* @return enum value
|
* @return enum value
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Int): HashAlgorithm? {
|
fun fromId(id: Int): HashAlgorithm? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { h -> h.algorithmId == id }
|
||||||
h -> h.algorithmId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
|
||||||
* If an invalid algorithm id was provided, throw a [NoSuchElementException].
|
* invalid algorithm id was provided, throw a [NoSuchElementException].
|
||||||
*
|
*
|
||||||
* @param id algorithm id
|
* @param id algorithm id
|
||||||
* @return enum value
|
* @return enum value
|
||||||
|
@ -48,15 +45,15 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Int): HashAlgorithm {
|
fun requireFromId(id: Int): HashAlgorithm {
|
||||||
return fromId(id) ?:
|
return fromId(id) ?: throw NoSuchElementException("No HashAlgorithm found for id $id")
|
||||||
throw NoSuchElementException("No HashAlgorithm found for id $id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [HashAlgorithm] value that corresponds to the provided name.
|
* Return the [HashAlgorithm] value that corresponds to the provided name. If an invalid
|
||||||
* If an invalid algorithm name was provided, null is returned.
|
* algorithm name was provided, null is returned.
|
||||||
*
|
*
|
||||||
* See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
|
* See
|
||||||
|
* [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
|
||||||
* for a list of algorithms and names.
|
* for a list of algorithms and names.
|
||||||
*
|
*
|
||||||
* @param name text name
|
* @param name text name
|
||||||
|
@ -65,12 +62,9 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromName(name: String): HashAlgorithm? {
|
fun fromName(name: String): HashAlgorithm? {
|
||||||
return name.uppercase().let { algoName ->
|
return name.uppercase().let { algoName ->
|
||||||
values().firstOrNull {
|
values().firstOrNull { it.algorithmName == algoName }
|
||||||
it.algorithmName == algoName
|
?: values().firstOrNull { it.algorithmName == algoName.replace("-", "") }
|
||||||
} ?: values().firstOrNull {
|
|
||||||
it.algorithmName == algoName.replace("-", "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,40 +6,26 @@ package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class KeyFlag(val flag: Int) {
|
enum class KeyFlag(val flag: Int) {
|
||||||
|
|
||||||
/**
|
/** This key may be used to certify third-party keys. */
|
||||||
* This key may be used to certify third-party keys.
|
CERTIFY_OTHER(1),
|
||||||
*/
|
|
||||||
CERTIFY_OTHER (1),
|
|
||||||
|
|
||||||
/**
|
/** This key may be used to sign data. */
|
||||||
* This key may be used to sign data.
|
SIGN_DATA(2),
|
||||||
*/
|
|
||||||
SIGN_DATA (2),
|
|
||||||
|
|
||||||
/**
|
/** This key may be used to encrypt communications. */
|
||||||
* This key may be used to encrypt communications.
|
ENCRYPT_COMMS(4),
|
||||||
*/
|
|
||||||
ENCRYPT_COMMS (4),
|
|
||||||
|
|
||||||
/**
|
/** This key may be used to encrypt storage. */
|
||||||
* This key may be used to encrypt storage.
|
|
||||||
*/
|
|
||||||
ENCRYPT_STORAGE(8),
|
ENCRYPT_STORAGE(8),
|
||||||
|
|
||||||
/**
|
/** The private component of this key may have been split by a secret-sharing mechanism. */
|
||||||
* The private component of this key may have been split by a secret-sharing mechanism.
|
SPLIT(16),
|
||||||
*/
|
|
||||||
SPLIT (16),
|
|
||||||
|
|
||||||
/**
|
/** This key may be used for authentication. */
|
||||||
* This key may be used for authentication.
|
AUTHENTICATION(32),
|
||||||
*/
|
|
||||||
AUTHENTICATION (32),
|
|
||||||
|
|
||||||
/**
|
/** The private component of this key may be in the possession of more than one person. */
|
||||||
* The private component of this key may be in the possession of more than one person.
|
SHARED(128),
|
||||||
*/
|
|
||||||
SHARED (128),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -52,9 +38,7 @@ enum class KeyFlag(val flag: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromBitmask(bitmask: Int): List<KeyFlag> {
|
fun fromBitmask(bitmask: Int): List<KeyFlag> {
|
||||||
return values().filter {
|
return values().filter { it.flag and bitmask != 0 }
|
||||||
it.flag and bitmask != 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,13 +49,12 @@ enum class KeyFlag(val flag: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun toBitmask(vararg flags: KeyFlag): Int {
|
fun toBitmask(vararg flags: KeyFlag): Int {
|
||||||
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f }
|
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } ?: 0
|
||||||
?: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the provided bitmask has the bit for the provided flag set.
|
* Return true if the provided bitmask has the bit for the provided flag set. Return false
|
||||||
* Return false if the mask does not contain the flag.
|
* if the mask does not contain the flag.
|
||||||
*
|
*
|
||||||
* @param mask bitmask
|
* @param mask bitmask
|
||||||
* @param flag flag to be tested for
|
* @param flag flag to be tested for
|
||||||
|
@ -84,9 +67,7 @@ enum class KeyFlag(val flag: Int) {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean {
|
fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean {
|
||||||
return flags.any {
|
return flags.any { hasKeyFlag(mask, it) }
|
||||||
hasKeyFlag(mask, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ enum class OpenPgpPacket(val tag: Int) {
|
||||||
UATTR(17),
|
UATTR(17),
|
||||||
SEIPD(18),
|
SEIPD(18),
|
||||||
MDC(19),
|
MDC(19),
|
||||||
|
|
||||||
EXP_1(60),
|
EXP_1(60),
|
||||||
EXP_2(61),
|
EXP_2(61),
|
||||||
EXP_3(62),
|
EXP_3(62),
|
||||||
|
@ -32,15 +31,13 @@ enum class OpenPgpPacket(val tag: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromTag(tag: Int): OpenPgpPacket? {
|
fun fromTag(tag: Int): OpenPgpPacket? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.tag == tag }
|
||||||
it.tag == tag
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromTag(tag: Int): OpenPgpPacket {
|
fun requireFromTag(tag: Int): OpenPgpPacket {
|
||||||
return fromTag(tag) ?:
|
return fromTag(tag)
|
||||||
throw NoSuchElementException("No OpenPGP packet known for tag $tag")
|
?: throw NoSuchElementException("No OpenPGP packet known for tag $tag")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,87 +10,73 @@ package org.pgpainless.algorithm
|
||||||
* See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1)
|
* See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1)
|
||||||
*/
|
*/
|
||||||
enum class PublicKeyAlgorithm(
|
enum class PublicKeyAlgorithm(
|
||||||
val algorithmId: Int,
|
val algorithmId: Int,
|
||||||
val signingCapable: Boolean,
|
val signingCapable: Boolean,
|
||||||
val encryptionCapable: Boolean) {
|
val encryptionCapable: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/** RSA capable of encryption and signatures. */
|
||||||
* RSA capable of encryption and signatures.
|
RSA_GENERAL(1, true, true),
|
||||||
*/
|
|
||||||
RSA_GENERAL (1, true, true),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSA with usage encryption.
|
* RSA with usage encryption.
|
||||||
*
|
*
|
||||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
|
||||||
|
* notice</a>
|
||||||
*/
|
*/
|
||||||
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL",
|
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
|
||||||
ReplaceWith("RSA_GENERAL"))
|
RSA_ENCRYPT(2, false, true),
|
||||||
RSA_ENCRYPT (2, false, true),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RSA with usage of creating signatures.
|
* RSA with usage of creating signatures.
|
||||||
*
|
*
|
||||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
|
||||||
|
* notice</a>
|
||||||
*/
|
*/
|
||||||
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL",
|
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
|
||||||
ReplaceWith("RSA_GENERAL"))
|
RSA_SIGN(3, true, false),
|
||||||
RSA_SIGN (3, true, false),
|
|
||||||
|
|
||||||
/**
|
/** ElGamal with usage encryption. */
|
||||||
* ElGamal with usage encryption.
|
ELGAMAL_ENCRYPT(16, false, true),
|
||||||
*/
|
|
||||||
ELGAMAL_ENCRYPT (16, false, true),
|
|
||||||
|
|
||||||
/**
|
/** Digital Signature Algorithm. */
|
||||||
* Digital Signature Algorithm.
|
DSA(17, true, false),
|
||||||
*/
|
|
||||||
DSA (17, true, false),
|
|
||||||
|
|
||||||
/**
|
/** Elliptic Curve Diffie-Hellman. */
|
||||||
* Elliptic Curve Diffie-Hellman.
|
ECDH(18, false, true),
|
||||||
*/
|
|
||||||
ECDH (18, false, true),
|
|
||||||
|
|
||||||
/**
|
/** Elliptic Curve Digital Signature Algorithm. */
|
||||||
* Elliptic Curve Digital Signature Algorithm.
|
ECDSA(19, true, false),
|
||||||
*/
|
|
||||||
ECDSA (19, true, false),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ElGamal General.
|
* ElGamal General.
|
||||||
*
|
*
|
||||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a>
|
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation
|
||||||
|
* notice</a>
|
||||||
*/
|
*/
|
||||||
@Deprecated("ElGamal is deprecated")
|
@Deprecated("ElGamal is deprecated") ELGAMAL_GENERAL(20, true, true),
|
||||||
ELGAMAL_GENERAL (20, true, true),
|
|
||||||
|
|
||||||
/**
|
/** Diffie-Hellman key exchange algorithm. */
|
||||||
* Diffie-Hellman key exchange algorithm.
|
DIFFIE_HELLMAN(21, false, true),
|
||||||
*/
|
|
||||||
DIFFIE_HELLMAN (21, false, true),
|
|
||||||
|
|
||||||
/**
|
/** Digital Signature Algorithm based on twisted Edwards Curves. */
|
||||||
* Digital Signature Algorithm based on twisted Edwards Curves.
|
EDDSA(22, true, false),
|
||||||
*/
|
|
||||||
EDDSA (22, true, false),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
fun isSigningCapable(): Boolean = signingCapable
|
fun isSigningCapable(): Boolean = signingCapable
|
||||||
|
|
||||||
fun isEncryptionCapable(): Boolean = encryptionCapable
|
fun isEncryptionCapable(): Boolean = encryptionCapable
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Int): PublicKeyAlgorithm? {
|
fun fromId(id: Int): PublicKeyAlgorithm? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.algorithmId == id }
|
||||||
it.algorithmId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Int): PublicKeyAlgorithm {
|
fun requireFromId(id: Int): PublicKeyAlgorithm {
|
||||||
return fromId(id) ?:
|
return fromId(id)
|
||||||
throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
|
?: throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,53 +4,48 @@
|
||||||
|
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
import org.pgpainless.util.DateUtil
|
|
||||||
import java.lang.AssertionError
|
import java.lang.AssertionError
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.NoSuchElementException
|
import kotlin.NoSuchElementException
|
||||||
|
import org.pgpainless.util.DateUtil
|
||||||
|
|
||||||
class RevocationState private constructor(
|
class RevocationState private constructor(val type: RevocationStateType, private val _date: Date?) :
|
||||||
val type: RevocationStateType,
|
Comparable<RevocationState> {
|
||||||
private val _date: Date?): Comparable<RevocationState> {
|
|
||||||
|
|
||||||
val date: Date
|
val date: Date
|
||||||
get() {
|
get() {
|
||||||
if (!isSoftRevocation()) {
|
if (!isSoftRevocation()) {
|
||||||
throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
|
throw NoSuchElementException(
|
||||||
|
"RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
|
||||||
}
|
}
|
||||||
return _date!!
|
return _date!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(type: RevocationStateType): this(type, null)
|
private constructor(type: RevocationStateType) : this(type, null)
|
||||||
|
|
||||||
fun isSoftRevocation() = type == RevocationStateType.softRevoked
|
fun isSoftRevocation() = type == RevocationStateType.softRevoked
|
||||||
|
|
||||||
fun isHardRevocation() = type == RevocationStateType.hardRevoked
|
fun isHardRevocation() = type == RevocationStateType.hardRevoked
|
||||||
|
|
||||||
fun isNotRevoked() = type == RevocationStateType.notRevoked
|
fun isNotRevoked() = type == RevocationStateType.notRevoked
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
|
||||||
fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date)
|
fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
|
||||||
fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: RevocationState): Int {
|
override fun compareTo(other: RevocationState): Int {
|
||||||
return when(type) {
|
return when (type) {
|
||||||
RevocationStateType.notRevoked ->
|
RevocationStateType.notRevoked -> if (other.isNotRevoked()) 0 else -1
|
||||||
if (other.isNotRevoked()) 0
|
|
||||||
else -1
|
|
||||||
RevocationStateType.softRevoked ->
|
RevocationStateType.softRevoked ->
|
||||||
if (other.isNotRevoked()) 1
|
if (other.isNotRevoked()) 1
|
||||||
// Compare soft dates in reverse
|
// Compare soft dates in reverse
|
||||||
else if (other.isSoftRevocation()) other.date.compareTo(date)
|
else if (other.isSoftRevocation()) other.date.compareTo(date) else -1
|
||||||
else -1
|
RevocationStateType.hardRevoked -> if (other.isHardRevocation()) 0 else 1
|
||||||
RevocationStateType.hardRevoked ->
|
|
||||||
if (other.isHardRevocation()) 0
|
|
||||||
else 1
|
|
||||||
else -> throw AssertionError("Unknown type: $type")
|
else -> throw AssertionError("Unknown type: $type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +75,9 @@ class RevocationState private constructor(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (isSoftRevocation()) {
|
if (isSoftRevocation()) {
|
||||||
return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time
|
return DateUtil.toSecondsPrecision(date).time ==
|
||||||
|
DateUtil.toSecondsPrecision(other.date).time
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,12 @@
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
enum class RevocationStateType {
|
enum class RevocationStateType {
|
||||||
/**
|
/** Certificate is not revoked. */
|
||||||
* Certificate is not revoked.
|
|
||||||
*/
|
|
||||||
notRevoked,
|
notRevoked,
|
||||||
|
|
||||||
/**
|
/** Certificate is revoked with a soft revocation. */
|
||||||
* Certificate is revoked with a soft revocation.
|
|
||||||
*/
|
|
||||||
softRevoked,
|
softRevoked,
|
||||||
|
|
||||||
/**
|
/** Certificate is revoked with a hard revocation. */
|
||||||
* Certificate is revoked with a hard revocation.
|
|
||||||
*/
|
|
||||||
hardRevoked
|
hardRevoked
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,23 +7,24 @@ package org.pgpainless.algorithm
|
||||||
import org.bouncycastle.bcpg.SignatureSubpacketTags.*
|
import org.bouncycastle.bcpg.SignatureSubpacketTags.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature.
|
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an
|
||||||
|
* OpenPGP signature.
|
||||||
*
|
*
|
||||||
* See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
|
* See
|
||||||
|
* [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
|
||||||
*/
|
*/
|
||||||
enum class SignatureSubpacket(val code: Int) {
|
enum class SignatureSubpacket(val code: Int) {
|
||||||
/**
|
/**
|
||||||
* The time the signature was made.
|
* The time the signature was made. MUST be present in the hashed area of the signature.
|
||||||
* MUST be present in the hashed area of the signature.
|
|
||||||
*
|
*
|
||||||
* See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4)
|
* See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4)
|
||||||
*/
|
*/
|
||||||
signatureCreationTime(2),
|
signatureCreationTime(2),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The validity period of the signature. This is the number of seconds
|
* The validity period of the signature. This is the number of seconds after the signature
|
||||||
* after the signature creation time that the signature expires. If
|
* creation time that the signature expires. If this is not present or has a value of zero, it
|
||||||
* this is not present or has a value of zero, it never expires.
|
* never expires.
|
||||||
*
|
*
|
||||||
* See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10)
|
* See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10)
|
||||||
*/
|
*/
|
||||||
|
@ -37,90 +38,76 @@ enum class SignatureSubpacket(val code: Int) {
|
||||||
exportableCertification(4),
|
exportableCertification(4),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signer asserts that the key is not only valid but also trustworthy at
|
* Signer asserts that the key is not only valid but also trustworthy at the specified level.
|
||||||
* the specified level. Level 0 has the same meaning as an ordinary
|
* Level 0 has the same meaning as an ordinary validity signature. Level 1 means that the signed
|
||||||
* validity signature. Level 1 means that the signed key is asserted to
|
* key is asserted to be a valid, trusted introducer, with the 2nd octet of the body specifying
|
||||||
* be a valid, trusted introducer, with the 2nd octet of the body
|
* the degree of trust. Level 2 means that the signed key is asserted to be trusted to issue
|
||||||
* specifying the degree of trust. Level 2 means that the signed key is
|
* level 1 trust signatures, i.e., that it is a "meta introducer". Generally, a level n trust
|
||||||
* asserted to be trusted to issue level 1 trust signatures, i.e., that
|
* signature asserts that a key is trusted to issue level n-1 trust signatures. The trust amount
|
||||||
* it is a "meta introducer". Generally, a level n trust signature
|
* is in a range from 0-255, interpreted such that values less than 120 indicate partial trust
|
||||||
* asserts that a key is trusted to issue level n-1 trust signatures.
|
* and values of 120 or greater indicate complete trust. Implementations SHOULD emit values of
|
||||||
* The trust amount is in a range from 0-255, interpreted such that
|
* 60 for partial trust and 120 for complete trust.
|
||||||
* values less than 120 indicate partial trust and values of 120 or
|
|
||||||
* greater indicate complete trust. Implementations SHOULD emit values
|
|
||||||
* of 60 for partial trust and 120 for complete trust.
|
|
||||||
*
|
*
|
||||||
* See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13)
|
* See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13)
|
||||||
*/
|
*/
|
||||||
trustSignature(5),
|
trustSignature(5),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in conjunction with trust Signature packets (of level greater 0) to
|
* Used in conjunction with trust Signature packets (of level greater 0) to limit the scope of
|
||||||
* limit the scope of trust that is extended. Only signatures by the
|
* trust that is extended. Only signatures by the target key on User IDs that match the regular
|
||||||
* target key on User IDs that match the regular expression in the body
|
* expression in the body of this packet have trust extended by the trust Signature subpacket.
|
||||||
* of this packet have trust extended by the trust Signature subpacket.
|
* The regular expression uses the same syntax as the Henry Spencer's "almost public domain"
|
||||||
* The regular expression uses the same syntax as the Henry Spencer's
|
* regular expression [REGEX] package. A description of the syntax is found in Section 8 below.
|
||||||
* "almost public domain" regular expression [REGEX] package. A
|
|
||||||
* description of the syntax is found in Section 8 below.
|
|
||||||
*
|
*
|
||||||
* See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14)
|
* See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14)
|
||||||
*/
|
*/
|
||||||
regularExpression(6),
|
regularExpression(6),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature's revocability status. The packet body contains a Boolean
|
* Signature's revocability status. The packet body contains a Boolean flag indicating whether
|
||||||
* flag indicating whether the signature is revocable. Signatures that
|
* the signature is revocable. Signatures that are not revocable have any later revocation
|
||||||
* are not revocable have any later revocation signatures ignored. They
|
* signatures ignored. They represent a commitment by the signer that he cannot revoke his
|
||||||
* represent a commitment by the signer that he cannot revoke his
|
* signature for the life of his key. If this packet is not present, the signature is revocable.
|
||||||
* signature for the life of his key. If this packet is not present,
|
|
||||||
* the signature is revocable.
|
|
||||||
*
|
*
|
||||||
* See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12)
|
* See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12)
|
||||||
*/
|
*/
|
||||||
revocable(7),
|
revocable(7),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The validity period of the key. This is the number of seconds after
|
* The validity period of the key. This is the number of seconds after the key creation time
|
||||||
* the key creation time that the key expires. If this is not present
|
* that the key expires. If this is not present or has a value of zero, the key never expires.
|
||||||
* or has a value of zero, the key never expires. This is found only on
|
* This is found only on a self-signature.
|
||||||
* a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6)
|
* See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6)
|
||||||
*/
|
*/
|
||||||
keyExpirationTime(9),
|
keyExpirationTime(9),
|
||||||
|
|
||||||
/**
|
/** Placeholder for backwards compatibility. */
|
||||||
* Placeholder for backwards compatibility.
|
|
||||||
*/
|
|
||||||
placeholder(10),
|
placeholder(10),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Symmetric algorithm numbers that indicate which algorithms the keyholder
|
* Symmetric algorithm numbers that indicate which algorithms the keyholder prefers to use. The
|
||||||
* prefers to use. The subpackets body is an ordered list of
|
* subpackets body is an ordered list of octets with the most preferred listed first. It is
|
||||||
* octets with the most preferred listed first. It is assumed that only
|
* assumed that only algorithms listed are supported by the recipient's software. This is only
|
||||||
* algorithms listed are supported by the recipient's software.
|
* found on a self-signature.
|
||||||
* This is only found on a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7)
|
* See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7)
|
||||||
*/
|
*/
|
||||||
preferredSymmetricAlgorithms(11),
|
preferredSymmetricAlgorithms(11),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authorizes the specified key to issue revocation signatures for this
|
* Authorizes the specified key to issue revocation signatures for this key. Class octet must
|
||||||
* key. Class octet must have bit 0x80 set. If the bit 0x40 is set,
|
* have bit 0x80 set. If the bit 0x40 is set, then this means that the revocation information is
|
||||||
* then this means that the revocation information is sensitive. Other
|
* sensitive. Other bits are for future expansion to other kinds of authorizations. This is
|
||||||
* bits are for future expansion to other kinds of authorizations. This
|
* found on a self-signature.
|
||||||
* is found on a self-signature.
|
|
||||||
*
|
*
|
||||||
* If the "sensitive" flag is set, the keyholder feels this subpacket
|
* If the "sensitive" flag is set, the keyholder feels this subpacket contains private trust
|
||||||
* contains private trust information that describes a real-world
|
* information that describes a real-world sensitive relationship. If this flag is set,
|
||||||
* sensitive relationship. If this flag is set, implementations SHOULD
|
* implementations SHOULD NOT export this signature to other users except in cases where the
|
||||||
* NOT export this signature to other users except in cases where the
|
* data needs to be available: when the signature is being sent to the designated revoker, or
|
||||||
* data needs to be available: when the signature is being sent to the
|
* when it is accompanied by a revocation signature from that revoker. Note that it may be
|
||||||
* designated revoker, or when it is accompanied by a revocation
|
* appropriate to isolate this subpacket within a separate signature so that it is not combined
|
||||||
* signature from that revoker. Note that it may be appropriate to
|
* with other subpackets that need to be exported.
|
||||||
* isolate this subpacket within a separate signature so that it is not
|
|
||||||
* combined with other subpackets that need to be exported.
|
|
||||||
*
|
*
|
||||||
* See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15)
|
* See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15)
|
||||||
*/
|
*/
|
||||||
|
@ -134,54 +121,48 @@ enum class SignatureSubpacket(val code: Int) {
|
||||||
issuerKeyId(16),
|
issuerKeyId(16),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket describes a "notation" on the signature that the
|
* This subpacket describes a "notation" on the signature that the issuer wishes to make. The
|
||||||
* issuer wishes to make. The notation has a name and a value, each of
|
* notation has a name and a value, each of which are strings of octets. There may be more than
|
||||||
* which are strings of octets. There may be more than one notation in
|
* one notation in a signature. Notations can be used for any extension the issuer of the
|
||||||
* a signature. Notations can be used for any extension the issuer of
|
* signature cares to make. The "flags" field holds four octets of flags.
|
||||||
* the signature cares to make. The "flags" field holds four octets of
|
|
||||||
* flags.
|
|
||||||
*
|
*
|
||||||
* See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16)
|
* See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16)
|
||||||
*/
|
*/
|
||||||
notationData(20),
|
notationData(20),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message digest algorithm numbers that indicate which algorithms the
|
* Message digest algorithm numbers that indicate which algorithms the keyholder prefers to
|
||||||
* keyholder prefers to receive. Like the preferred symmetric
|
* receive. Like the preferred symmetric algorithms, the list is ordered. This is only found on
|
||||||
* algorithms, the list is ordered.
|
* a self-signature.
|
||||||
* This is only found on a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8)
|
* See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8)
|
||||||
*/
|
*/
|
||||||
preferredHashAlgorithms(21),
|
preferredHashAlgorithms(21),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compression algorithm numbers that indicate which algorithms the
|
* Compression algorithm numbers that indicate which algorithms the keyholder prefers to use.
|
||||||
* keyholder prefers to use. Like the preferred symmetric algorithms, the
|
* Like the preferred symmetric algorithms, the list is ordered. If this subpacket is not
|
||||||
* list is ordered. If this subpacket is not included, ZIP is preferred.
|
* included, ZIP is preferred. A zero denotes that uncompressed data is preferred; the
|
||||||
* A zero denotes that uncompressed data is preferred; the keyholder's
|
* keyholder's software might have no compression software in that implementation. This is only
|
||||||
* software might have no compression software in that implementation.
|
* found on a self-signature.
|
||||||
* This is only found on a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9)
|
* See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9)
|
||||||
*/
|
*/
|
||||||
preferredCompressionAlgorithms(22),
|
preferredCompressionAlgorithms(22),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a list of one-bit flags that indicate preferences that the
|
* This is a list of one-bit flags that indicate preferences that the keyholder has about how
|
||||||
* keyholder has about how the key is handled on a key server. All
|
* the key is handled on a key server. All undefined flags MUST be zero. This is found only on a
|
||||||
* undefined flags MUST be zero.
|
* self-signature.
|
||||||
* This is found only on a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17)
|
* See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17)
|
||||||
*/
|
*/
|
||||||
keyServerPreferences(23),
|
keyServerPreferences(23),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a URI of a key server that the keyholder prefers be used for
|
* This is a URI of a key server that the keyholder prefers be used for updates. Note that keys
|
||||||
* updates. Note that keys with multiple User IDs can have a preferred
|
* with multiple User IDs can have a preferred key server for each User ID. Note also that since
|
||||||
* key server for each User ID. Note also that since this is a URI, the
|
* this is a URI, the key server can actually be a copy of the key retrieved by ftp, http,
|
||||||
* key server can actually be a copy of the key retrieved by ftp, http,
|
|
||||||
* finger, etc.
|
* finger, etc.
|
||||||
*
|
*
|
||||||
* See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18)
|
* See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18)
|
||||||
|
@ -189,63 +170,55 @@ enum class SignatureSubpacket(val code: Int) {
|
||||||
preferredKeyServers(24),
|
preferredKeyServers(24),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a flag in a User ID's self-signature that states whether this
|
* This is a flag in a User ID's self-signature that states whether this User ID is the main
|
||||||
* User ID is the main User ID for this key. It is reasonable for an
|
* User ID for this key. It is reasonable for an implementation to resolve ambiguities in
|
||||||
* implementation to resolve ambiguities in preferences, etc. by
|
* preferences, etc. by referring to the primary User ID. If this flag is absent, its value is
|
||||||
* referring to the primary User ID. If this flag is absent, its value
|
* zero. If more than one User ID in a key is marked as primary, the implementation may resolve
|
||||||
* is zero. If more than one User ID in a key is marked as primary, the
|
* the ambiguity in any way it sees fit, but it is RECOMMENDED that priority be given to the
|
||||||
* implementation may resolve the ambiguity in any way it sees fit, but
|
* User ID with the most recent self-signature.
|
||||||
* it is RECOMMENDED that priority be given to the User ID with the most
|
|
||||||
* recent self-signature.
|
|
||||||
*
|
*
|
||||||
* When appearing on a self-signature on a User ID packet, this
|
* When appearing on a self-signature on a User ID packet, this subpacket applies only to User
|
||||||
* subpacket applies only to User ID packets. When appearing on a
|
* ID packets. When appearing on a self-signature on a User Attribute packet, this subpacket
|
||||||
* self-signature on a User Attribute packet, this subpacket applies
|
* applies only to User Attribute packets. That is to say, there are two different and
|
||||||
* only to User Attribute packets. That is to say, there are two
|
* independent "primaries" -- one for User IDs, and one for User Attributes.
|
||||||
* different and independent "primaries" -- one for User IDs, and one
|
|
||||||
* for User Attributes.
|
|
||||||
*
|
*
|
||||||
* See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19)
|
* See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19)
|
||||||
*/
|
*/
|
||||||
primaryUserId(25),
|
primaryUserId(25),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket contains a URI of a document that describes the policy
|
* This subpacket contains a URI of a document that describes the policy under which the
|
||||||
* under which the signature was issued.
|
* signature was issued.
|
||||||
*
|
*
|
||||||
* See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20)
|
* See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20)
|
||||||
*/
|
*/
|
||||||
policyUrl(26),
|
policyUrl(26),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket contains a list of binary flags that hold information
|
* This subpacket contains a list of binary flags that hold information about a key. It is a
|
||||||
* about a key. It is a string of octets, and an implementation MUST
|
* string of octets, and an implementation MUST NOT assume a fixed size. This is so it can grow
|
||||||
* NOT assume a fixed size. This is so it can grow over time. If a
|
* over time. If a list is shorter than an implementation expects, the unstated flags are
|
||||||
* list is shorter than an implementation expects, the unstated flags
|
* considered to be zero.
|
||||||
* are considered to be zero.
|
|
||||||
*
|
*
|
||||||
* See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21)
|
* See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21)
|
||||||
*/
|
*/
|
||||||
keyFlags(27),
|
keyFlags(27),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket allows a keyholder to state which User ID is
|
* This subpacket allows a keyholder to state which User ID is responsible for the signing. Many
|
||||||
* responsible for the signing. Many keyholders use a single key for
|
* keyholders use a single key for different purposes, such as business communications as well
|
||||||
* different purposes, such as business communications as well as
|
* as personal communications. This subpacket allows such a keyholder to state which of their
|
||||||
* personal communications. This subpacket allows such a keyholder to
|
* roles is making a signature.
|
||||||
* state which of their roles is making a signature.
|
|
||||||
*
|
*
|
||||||
* See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22)
|
* See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22)
|
||||||
*/
|
*/
|
||||||
signerUserId(28),
|
signerUserId(28),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket is used only in key revocation and certification
|
* This subpacket is used only in key revocation and certification revocation signatures. It
|
||||||
* revocation signatures. It describes the reason why the key or
|
* describes the reason why the key or certificate was revoked.
|
||||||
* certificate was revoked.
|
|
||||||
*
|
*
|
||||||
* The first octet contains a machine-readable code that denotes the
|
* The first octet contains a machine-readable code that denotes the reason for the revocation:
|
||||||
* reason for the revocation:
|
|
||||||
*
|
*
|
||||||
* 0 - No reason specified (key revocations or cert revocations)
|
* 0 - No reason specified (key revocations or cert revocations)
|
||||||
* 1 - Key is superseded (key revocations)
|
* 1 - Key is superseded (key revocations)
|
||||||
|
@ -259,117 +232,105 @@ enum class SignatureSubpacket(val code: Int) {
|
||||||
revocationReason(29),
|
revocationReason(29),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Features subpacket denotes which advanced OpenPGP features a
|
* The Features subpacket denotes which advanced OpenPGP features a user's implementation
|
||||||
* user's implementation supports. This is so that as features are
|
* supports. This is so that as features are added to OpenPGP that cannot be
|
||||||
* added to OpenPGP that cannot be backwards-compatible, a user can
|
* backwards-compatible, a user can state that they can use that feature. The flags are single
|
||||||
* state that they can use that feature. The flags are single bits that
|
* bits that indicate that a given feature is supported.
|
||||||
* indicate that a given feature is supported.
|
|
||||||
*
|
*
|
||||||
* This subpacket is similar to a preferences subpacket, and only
|
* This subpacket is similar to a preferences subpacket, and only appears in a self-signature.
|
||||||
* appears in a self-signature.
|
|
||||||
*
|
*
|
||||||
* See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
|
* See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
|
||||||
*/
|
*/
|
||||||
features(30),
|
features(30),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket identifies a specific target signature to which a
|
* This subpacket identifies a specific target signature to which a signature refers. For
|
||||||
* signature refers. For revocation signatures, this subpacket
|
* revocation signatures, this subpacket provides explicit designation of which signature is
|
||||||
* provides explicit designation of which signature is being revoked.
|
* being revoked. For a third-party or timestamp signature, this designates what signature is
|
||||||
* For a third-party or timestamp signature, this designates what
|
* signed. All arguments are an identifier of that target signature.
|
||||||
* signature is signed. All arguments are an identifier of that target
|
|
||||||
* signature.
|
|
||||||
*
|
*
|
||||||
* The N octets of hash data MUST be the size of the hash of the
|
* The N octets of hash data MUST be the size of the hash of the signature. For example, a
|
||||||
* signature. For example, a target signature with a SHA-1 hash MUST
|
* target signature with a SHA-1 hash MUST have 20 octets of hash data.
|
||||||
* have 20 octets of hash data.
|
|
||||||
*
|
*
|
||||||
* See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25)
|
* See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25)
|
||||||
*/
|
*/
|
||||||
signatureTarget(31),
|
signatureTarget(31),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket contains a complete Signature packet body as
|
* This subpacket contains a complete Signature packet body as specified in Section 5.2 above.
|
||||||
* specified in Section 5.2 above. It is useful when one signature
|
* It is useful when one signature needs to refer to, or be incorporated in, another signature.
|
||||||
* needs to refer to, or be incorporated in, another signature.
|
|
||||||
*
|
*
|
||||||
* See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26)
|
* See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26)
|
||||||
*/
|
*/
|
||||||
embeddedSignature(32),
|
embeddedSignature(32),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The OpenPGP Key fingerprint of the key issuing the signature. This
|
* The OpenPGP Key fingerprint of the key issuing the signature. This subpacket SHOULD be
|
||||||
* subpacket SHOULD be included in all signatures. If the version of
|
* included in all signatures. If the version of the issuing key is 4 and an Issuer subpacket is
|
||||||
* the issuing key is 4 and an Issuer subpacket is also included in the
|
* also included in the signature, the key ID of the Issuer subpacket MUST match the low 64 bits
|
||||||
* signature, the key ID of the Issuer subpacket MUST match the low 64
|
* of the fingerprint.
|
||||||
* bits of the fingerprint.
|
|
||||||
*
|
*
|
||||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
* Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
|
||||||
* octets; for a version 5 key N is 32.
|
* key N is 32.
|
||||||
*
|
*
|
||||||
* See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
|
* See
|
||||||
|
* [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
|
||||||
*/
|
*/
|
||||||
issuerFingerprint(33),
|
issuerFingerprint(33),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AEAD algorithm numbers that indicate which AEAD algorithms the
|
* AEAD algorithm numbers that indicate which AEAD algorithms the keyholder prefers to use. The
|
||||||
* keyholder prefers to use. The subpackets body is an ordered list of
|
* subpackets body is an ordered list of octets with the most preferred listed first. It is
|
||||||
* octets with the most preferred listed first. It is assumed that only
|
* assumed that only algorithms listed are supported by the recipient's software. This is only
|
||||||
* algorithms listed are supported by the recipient's software.
|
* found on a self-signature. Note that support for the AEAD Encrypted Data packet in the
|
||||||
* This is only found on a self-signature.
|
* general is indicated by a Feature Flag.
|
||||||
* Note that support for the AEAD Encrypted Data packet in the general
|
|
||||||
* is indicated by a Feature Flag.
|
|
||||||
*
|
*
|
||||||
* See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
|
* See
|
||||||
|
* [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
|
||||||
*/
|
*/
|
||||||
preferredAEADAlgorithms(39),
|
preferredAEADAlgorithms(39),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The OpenPGP Key fingerprint of the intended recipient primary key.
|
* The OpenPGP Key fingerprint of the intended recipient primary key. If one or more subpackets
|
||||||
* If one or more subpackets of this type are included in a signature,
|
* of this type are included in a signature, it SHOULD be considered valid only in an encrypted
|
||||||
* it SHOULD be considered valid only in an encrypted context, where the
|
* context, where the key it was encrypted to is one of the indicated primary keys, or one of
|
||||||
* key it was encrypted to is one of the indicated primary keys, or one
|
* their subkeys. This can be used to prevent forwarding a signature outside its intended,
|
||||||
* of their subkeys. This can be used to prevent forwarding a signature
|
* encrypted context.
|
||||||
* outside its intended, encrypted context.
|
|
||||||
*
|
*
|
||||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
* Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
|
||||||
* octets; for a version 5 key N is 32.
|
* key N is 32.
|
||||||
*
|
*
|
||||||
* See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
|
* See
|
||||||
|
* [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
|
||||||
*/
|
*/
|
||||||
intendedRecipientFingerprint(35),
|
intendedRecipientFingerprint(35),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This subpacket MUST only appear as a hashed subpacket of an
|
* This subpacket MUST only appear as a hashed subpacket of an Attestation Key Signature. It has
|
||||||
* Attestation Key Signature. It has no meaning in any other signature
|
* no meaning in any other signature type. It is used by the primary key to attest to a set of
|
||||||
* type. It is used by the primary key to attest to a set of third-
|
* third- party certifications over the associated User ID or User Attribute. This enables the
|
||||||
* party certifications over the associated User ID or User Attribute.
|
* holder of an OpenPGP primary key to mark specific third-party certifications as
|
||||||
* This enables the holder of an OpenPGP primary key to mark specific
|
* re-distributable with the rest of the Transferable Public Key (see the "No-modify" flag in
|
||||||
* third-party certifications as re-distributable with the rest of the
|
* "Key Server Preferences", above). Implementations MUST include exactly one Attested
|
||||||
* Transferable Public Key (see the "No-modify" flag in "Key Server
|
* Certification subpacket in any generated Attestation Key Signature.
|
||||||
* Preferences", above). Implementations MUST include exactly one
|
|
||||||
* Attested Certification subpacket in any generated Attestation Key
|
|
||||||
* Signature.
|
|
||||||
*
|
*
|
||||||
* See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
|
* See
|
||||||
|
* [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
|
||||||
*/
|
*/
|
||||||
attestedCertification(37)
|
attestedCertification(37);
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [SignatureSubpacket] that corresponds to the provided id.
|
* Return the [SignatureSubpacket] that corresponds to the provided id. If an unmatched code
|
||||||
* If an unmatched code is presented, return null.
|
* is presented, return null.
|
||||||
*
|
*
|
||||||
* @param code id
|
* @param code id
|
||||||
* @return signature subpacket
|
* @return signature subpacket
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromCode(code: Int): SignatureSubpacket? {
|
fun fromCode(code: Int): SignatureSubpacket? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.code == code }
|
||||||
it.code == code
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -381,21 +342,20 @@ enum class SignatureSubpacket(val code: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromCode(code: Int): SignatureSubpacket {
|
fun requireFromCode(code: Int): SignatureSubpacket {
|
||||||
return fromCode(code) ?:
|
return fromCode(code)
|
||||||
throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
|
?: throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets].
|
* Convert an array of signature subpacket tags into a list of
|
||||||
|
* [SignatureSubpacket SignatureSubpackets].
|
||||||
*
|
*
|
||||||
* @param codes array of codes
|
* @param codes array of codes
|
||||||
* @return list of subpackets
|
* @return list of subpackets
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromCodes(vararg codes: Int): List<SignatureSubpacket> {
|
fun fromCodes(vararg codes: Int): List<SignatureSubpacket> {
|
||||||
return codes.toList().mapNotNull {
|
return codes.toList().mapNotNull { fromCode(it) }
|
||||||
fromCode(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,150 +7,120 @@ package org.pgpainless.algorithm
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
import org.bouncycastle.openpgp.PGPSignature
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1
|
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 See [PGPSignature] for
|
||||||
* See [PGPSignature] for comparison.
|
* comparison.
|
||||||
*
|
*
|
||||||
* See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11)
|
* See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11)
|
||||||
*/
|
*/
|
||||||
enum class SignatureType(val code: Int) {
|
enum class SignatureType(val code: Int) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature of a binary document.
|
* Signature of a binary document. This means the signer owns it, created it, or certifies that
|
||||||
* This means the signer owns it, created it, or certifies that it
|
* it has not been modified.
|
||||||
* has not been modified.
|
|
||||||
*/
|
*/
|
||||||
BINARY_DOCUMENT(0x00),
|
BINARY_DOCUMENT(0x00),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature of a canonical text document.
|
* Signature of a canonical text document. This means the signer owns it, created it, or
|
||||||
* This means the signer owns it, created it, or certifies that it
|
* certifies that it has not been modified. The signature is calculated over the text data with
|
||||||
* has not been modified. The signature is calculated over the text
|
* its line endings converted to {@code <CR><LF>}.
|
||||||
* data with its line endings converted to {@code <CR><LF>}.
|
|
||||||
*/
|
*/
|
||||||
CANONICAL_TEXT_DOCUMENT(0x01),
|
CANONICAL_TEXT_DOCUMENT(0x01),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standalone signature.
|
* Standalone signature. This signature is a signature of only its own subpacket contents. It is
|
||||||
* This signature is a signature of only its own subpacket contents.
|
* calculated identically to a signature over a zero-length binary document. Note that it
|
||||||
* It is calculated identically to a signature over a zero-length
|
* doesn't make sense to have a V3 standalone signature.
|
||||||
* binary document. Note that it doesn't make sense to have a V3
|
|
||||||
* standalone signature.
|
|
||||||
*/
|
*/
|
||||||
STANDALONE(0x02),
|
STANDALONE(0x02),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic certification of a User ID and Public-Key packet.
|
* Generic certification of a User ID and Public-Key packet. The issuer of this certification
|
||||||
* The issuer of this certification does not make any particular
|
* does not make any particular assertion as to how well the certifier has checked that the
|
||||||
* assertion as to how well the certifier has checked that the owner
|
* owner of the key is in fact the person described by the User ID.
|
||||||
* of the key is in fact the person described by the User ID.
|
|
||||||
*/
|
*/
|
||||||
GENERIC_CERTIFICATION(0x10),
|
GENERIC_CERTIFICATION(0x10),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persona certification of a User ID and Public-Key packet.
|
* Persona certification of a User ID and Public-Key packet. The issuer of this certification
|
||||||
* The issuer of this certification has not done any verification of
|
* has not done any verification of the claim that the owner of this key is the User ID
|
||||||
* the claim that the owner of this key is the User ID specified.
|
* specified.
|
||||||
*/
|
*/
|
||||||
NO_CERTIFICATION(0x11),
|
NO_CERTIFICATION(0x11),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Casual certification of a User ID and Public-Key packet.
|
* Casual certification of a User ID and Public-Key packet. The issuer of this certification has
|
||||||
* The issuer of this certification has done some casual
|
* done some casual verification of the claim of identity.
|
||||||
* verification of the claim of identity.
|
|
||||||
*/
|
*/
|
||||||
CASUAL_CERTIFICATION(0x12),
|
CASUAL_CERTIFICATION(0x12),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Positive certification of a User ID and Public-Key packet.
|
* Positive certification of a User ID and Public-Key packet. The issuer of this certification
|
||||||
* The issuer of this certification has done substantial
|
* has done substantial verification of the claim of identity.
|
||||||
* verification of the claim of identity.
|
|
||||||
*/
|
*/
|
||||||
POSITIVE_CERTIFICATION(0x13),
|
POSITIVE_CERTIFICATION(0x13),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subkey Binding Signature.
|
* Subkey Binding Signature. This signature is a statement by the top-level signing key that
|
||||||
* This signature is a statement by the top-level signing key that
|
* indicates that it owns the subkey. This signature is calculated directly on the primary key
|
||||||
* indicates that it owns the subkey. This signature is calculated
|
* and subkey, and not on any User ID or other packets. A signature that binds a signing subkey
|
||||||
* directly on the primary key and subkey, and not on any User ID or
|
* MUST have an Embedded Signature subpacket in this binding signature that contains a
|
||||||
* other packets. A signature that binds a signing subkey MUST have
|
* [#PRIMARYKEY_BINDING] signature made by the signing subkey on the primary key and subkey.
|
||||||
* an Embedded Signature subpacket in this binding signature that
|
|
||||||
* contains a [#PRIMARYKEY_BINDING] signature made by the
|
|
||||||
* signing subkey on the primary key and subkey.
|
|
||||||
*/
|
*/
|
||||||
SUBKEY_BINDING(0x18),
|
SUBKEY_BINDING(0x18),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary Key Binding Signature
|
* Primary Key Binding Signature This signature is a statement by a signing subkey, indicating
|
||||||
* This signature is a statement by a signing subkey, indicating
|
* that it is owned by the primary key and subkey. This signature is calculated the same way as
|
||||||
* that it is owned by the primary key and subkey. This signature
|
* a [#SUBKEY_BINDING] signature: directly on the primary key and subkey, and not on any User ID
|
||||||
* is calculated the same way as a [#SUBKEY_BINDING] signature:
|
* or other packets.
|
||||||
* directly on the primary key and subkey, and not on any User ID or
|
|
||||||
* other packets.
|
|
||||||
*/
|
*/
|
||||||
PRIMARYKEY_BINDING(0x19),
|
PRIMARYKEY_BINDING(0x19),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signature directly on a key
|
* Signature directly on a key This signature is calculated directly on a key. It binds the
|
||||||
* This signature is calculated directly on a key. It binds the
|
* information in the Signature subpackets to the key, and is appropriate to be used for
|
||||||
* information in the Signature subpackets to the key, and is
|
* subpackets that provide information about the key, such as the Revocation Key subpacket. It
|
||||||
* appropriate to be used for subpackets that provide information
|
* is also appropriate for statements that non-self certifiers want to make about the key
|
||||||
* about the key, such as the Revocation Key subpacket. It is also
|
* itself, rather than the binding between a key and a name.
|
||||||
* appropriate for statements that non-self certifiers want to make
|
|
||||||
* about the key itself, rather than the binding between a key and a
|
|
||||||
* name.
|
|
||||||
*/
|
*/
|
||||||
DIRECT_KEY(0x1f),
|
DIRECT_KEY(0x1f),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key revocation signature
|
* Key revocation signature The signature is calculated directly on the key being revoked. A
|
||||||
* The signature is calculated directly on the key being revoked. A
|
* revoked key is not to be used. Only revocation signatures by the key being revoked, or by an
|
||||||
* revoked key is not to be used. Only revocation signatures by the
|
* authorized revocation key, should be considered valid revocation signatures.
|
||||||
* key being revoked, or by an authorized revocation key, should be
|
|
||||||
* considered valid revocation signatures.
|
|
||||||
*/
|
*/
|
||||||
KEY_REVOCATION(0x20),
|
KEY_REVOCATION(0x20),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subkey revocation signature
|
* Subkey revocation signature The signature is calculated directly on the subkey being revoked.
|
||||||
* The signature is calculated directly on the subkey being revoked.
|
* A revoked subkey is not to be used. Only revocation signatures by the top-level signature key
|
||||||
* A revoked subkey is not to be used. Only revocation signatures
|
* that is bound to this subkey, or by an authorized revocation key, should be considered valid
|
||||||
* by the top-level signature key that is bound to this subkey, or
|
|
||||||
* by an authorized revocation key, should be considered valid
|
|
||||||
* revocation signatures.
|
* revocation signatures.
|
||||||
*/
|
*/
|
||||||
SUBKEY_REVOCATION(0x28),
|
SUBKEY_REVOCATION(0x28),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Certification revocation signature
|
* Certification revocation signature This signature revokes an earlier User ID certification
|
||||||
* This signature revokes an earlier User ID certification signature
|
* signature (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. It should be issued
|
||||||
* (signature class 0x10 through 0x13) or signature [#DIRECT_KEY].
|
* by the same key that issued the revoked signature or an authorized revocation key. The
|
||||||
* It should be issued by the same key that issued the
|
* signature is computed over the same data as the certificate that it revokes, and should have
|
||||||
* revoked signature or an authorized revocation key. The signature
|
* a later creation date than that certificate.
|
||||||
* is computed over the same data as the certificate that it
|
|
||||||
* revokes, and should have a later creation date than that
|
|
||||||
* certificate.
|
|
||||||
*/
|
*/
|
||||||
CERTIFICATION_REVOCATION(0x30),
|
CERTIFICATION_REVOCATION(0x30),
|
||||||
|
|
||||||
/**
|
/** Timestamp signature. This signature is only meaningful for the timestamp contained in it. */
|
||||||
* Timestamp signature.
|
|
||||||
* This signature is only meaningful for the timestamp contained in
|
|
||||||
* it.
|
|
||||||
*/
|
|
||||||
TIMESTAMP(0x40),
|
TIMESTAMP(0x40),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Third-Party Confirmation signature.
|
* Third-Party Confirmation signature. This signature is a signature over some other OpenPGP
|
||||||
* This signature is a signature over some other OpenPGP Signature
|
* Signature packet(s). It is analogous to a notary seal on the signed data. A third-party
|
||||||
* packet(s). It is analogous to a notary seal on the signed data.
|
* signature SHOULD include Signature Target subpacket(s) to give easy identification. Note that
|
||||||
* A third-party signature SHOULD include Signature Target
|
* we really do mean SHOULD. There are plausible uses for this (such as a blind party that only
|
||||||
* subpacket(s) to give easy identification. Note that we really do
|
* sees the signature, not the key or source document) that cannot include a target subpacket.
|
||||||
* mean SHOULD. There are plausible uses for this (such as a blind
|
|
||||||
* party that only sees the signature, not the key or source
|
|
||||||
* document) that cannot include a target subpacket.
|
|
||||||
*/
|
*/
|
||||||
THIRD_PARTY_CONFIRMATION(0x50)
|
THIRD_PARTY_CONFIRMATION(0x50);
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -162,9 +132,7 @@ enum class SignatureType(val code: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromCode(code: Int): SignatureType? {
|
fun fromCode(code: Int): SignatureType? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.code == code }
|
||||||
it.code == code
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -176,8 +144,9 @@ enum class SignatureType(val code: Int) {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromCode(code: Int): SignatureType {
|
fun requireFromCode(code: Int): SignatureType {
|
||||||
return fromCode(code) ?:
|
return fromCode(code)
|
||||||
throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
|
?: throw NoSuchElementException(
|
||||||
|
"Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -188,8 +157,7 @@ enum class SignatureType(val code: Int) {
|
||||||
* @throws IllegalArgumentException in case of an unmatched signature type code
|
* @throws IllegalArgumentException in case of an unmatched signature type code
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Deprecated in favor of requireFromCode",
|
@Deprecated("Deprecated in favor of requireFromCode", ReplaceWith("requireFromCode"))
|
||||||
ReplaceWith("requireFromCode"))
|
|
||||||
fun valueOf(code: Int): SignatureType {
|
fun valueOf(code: Int): SignatureType {
|
||||||
try {
|
try {
|
||||||
return requireFromCode(code)
|
return requireFromCode(code)
|
||||||
|
@ -197,12 +165,12 @@ enum class SignatureType(val code: Int) {
|
||||||
throw IllegalArgumentException(e.message)
|
throw IllegalArgumentException(e.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isRevocationSignature(signatureType: Int): Boolean {
|
fun isRevocationSignature(signatureType: Int): Boolean {
|
||||||
return isRevocationSignature(valueOf(signatureType))
|
return isRevocationSignature(valueOf(signatureType))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isRevocationSignature(signatureType: SignatureType): Boolean {
|
fun isRevocationSignature(signatureType: SignatureType): Boolean {
|
||||||
return when (signatureType) {
|
return when (signatureType) {
|
||||||
|
@ -218,11 +186,11 @@ enum class SignatureType(val code: Int) {
|
||||||
DIRECT_KEY,
|
DIRECT_KEY,
|
||||||
TIMESTAMP,
|
TIMESTAMP,
|
||||||
THIRD_PARTY_CONFIRMATION -> false
|
THIRD_PARTY_CONFIRMATION -> false
|
||||||
KEY_REVOCATION,
|
KEY_REVOCATION,
|
||||||
SUBKEY_REVOCATION,
|
SUBKEY_REVOCATION,
|
||||||
CERTIFICATION_REVOCATION -> true
|
CERTIFICATION_REVOCATION -> true
|
||||||
else -> throw IllegalArgumentException("Unknown signature type: $signatureType")
|
else -> throw IllegalArgumentException("Unknown signature type: $signatureType")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,61 +11,52 @@ package org.pgpainless.algorithm
|
||||||
*/
|
*/
|
||||||
enum class StreamEncoding(val code: Char) {
|
enum class StreamEncoding(val code: Char) {
|
||||||
|
|
||||||
/**
|
/** The Literal packet contains binary data. */
|
||||||
* The Literal packet contains binary data.
|
|
||||||
*/
|
|
||||||
BINARY('b'),
|
BINARY('b'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Literal packet contains text data, and thus may need line ends converted to local form, or other
|
* The Literal packet contains text data, and thus may need line ends converted to local form,
|
||||||
* text-mode changes.
|
* or other text-mode changes.
|
||||||
*/
|
*/
|
||||||
TEXT('t'),
|
TEXT('t'),
|
||||||
|
|
||||||
/**
|
/** Indication that the implementation believes that the literal data contains UTF-8 text. */
|
||||||
* Indication that the implementation believes that the literal data contains UTF-8 text.
|
|
||||||
*/
|
|
||||||
UTF8('u'),
|
UTF8('u'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions.
|
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local
|
||||||
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one).
|
* conversions. RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral
|
||||||
* Both of these local modes are deprecated.
|
* one). Both of these local modes are deprecated.
|
||||||
*/
|
*/
|
||||||
@Deprecated("LOCAL is deprecated.")
|
@Deprecated("LOCAL is deprecated.") LOCAL('l'),
|
||||||
LOCAL('l'),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
|
||||||
* If no matching encoding is found, return null.
|
* encoding is found, return null.
|
||||||
*
|
*
|
||||||
* @param code identifier
|
* @param code identifier
|
||||||
* @return encoding enum
|
* @return encoding enum
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromCode(code: Int): StreamEncoding? {
|
fun fromCode(code: Int): StreamEncoding? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.code == code.toChar() }
|
||||||
it.code == code.toChar()
|
?: if (code == 1) return LOCAL else null
|
||||||
} ?: if (code == 1) return LOCAL else null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
|
||||||
* If no matching encoding is found, throw a [NoSuchElementException].
|
* encoding is found, throw a [NoSuchElementException].
|
||||||
*
|
*
|
||||||
* @param code identifier
|
* @param code identifier
|
||||||
* @return encoding enum
|
* @return encoding enum
|
||||||
*
|
|
||||||
* @throws NoSuchElementException in case of an unmatched identifier
|
* @throws NoSuchElementException in case of an unmatched identifier
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromCode(code: Int): StreamEncoding {
|
fun requireFromCode(code: Int): StreamEncoding {
|
||||||
return fromCode(code) ?:
|
return fromCode(code)
|
||||||
throw NoSuchElementException("No StreamEncoding found for code $code")
|
?: throw NoSuchElementException("No StreamEncoding found for code $code")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -11,110 +11,79 @@ package org.pgpainless.algorithm
|
||||||
*/
|
*/
|
||||||
enum class SymmetricKeyAlgorithm(val algorithmId: Int) {
|
enum class SymmetricKeyAlgorithm(val algorithmId: Int) {
|
||||||
|
|
||||||
/**
|
/** Plaintext or unencrypted data. */
|
||||||
* Plaintext or unencrypted data.
|
NULL(0),
|
||||||
*/
|
|
||||||
NULL (0),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IDEA is deprecated.
|
* IDEA is deprecated.
|
||||||
|
*
|
||||||
* @deprecated use a different algorithm.
|
* @deprecated use a different algorithm.
|
||||||
*/
|
*/
|
||||||
@Deprecated("IDEA is deprecated.")
|
@Deprecated("IDEA is deprecated.") IDEA(1),
|
||||||
IDEA (1),
|
|
||||||
|
|
||||||
/**
|
/** TripleDES (DES-EDE - 168 bit key derived from 192). */
|
||||||
* TripleDES (DES-EDE - 168 bit key derived from 192).
|
TRIPLE_DES(2),
|
||||||
*/
|
|
||||||
TRIPLE_DES (2),
|
|
||||||
|
|
||||||
/**
|
/** CAST5 (128-bit key, as per RFC2144). */
|
||||||
* CAST5 (128-bit key, as per RFC2144).
|
CAST5(3),
|
||||||
*/
|
|
||||||
CAST5 (3),
|
|
||||||
|
|
||||||
/**
|
/** Blowfish (128-bit key, 16 rounds). */
|
||||||
* Blowfish (128-bit key, 16 rounds).
|
BLOWFISH(4),
|
||||||
*/
|
|
||||||
BLOWFISH (4),
|
|
||||||
|
|
||||||
/**
|
/** Reserved in RFC4880. SAFER-SK128 (13 rounds) */
|
||||||
* Reserved in RFC4880.
|
SAFER(5),
|
||||||
* SAFER-SK128 (13 rounds)
|
|
||||||
*/
|
|
||||||
SAFER (5),
|
|
||||||
|
|
||||||
/**
|
/** Reserved in RFC4880. Reserved for DES/SK */
|
||||||
* Reserved in RFC4880.
|
DES(6),
|
||||||
* Reserved for DES/SK
|
|
||||||
*/
|
|
||||||
DES (6),
|
|
||||||
|
|
||||||
/**
|
/** AES with 128-bit key. */
|
||||||
* AES with 128-bit key.
|
AES_128(7),
|
||||||
*/
|
|
||||||
AES_128 (7),
|
|
||||||
|
|
||||||
/**
|
/** AES with 192-bit key. */
|
||||||
* AES with 192-bit key.
|
AES_192(8),
|
||||||
*/
|
|
||||||
AES_192 (8),
|
|
||||||
|
|
||||||
/**
|
/** AES with 256-bit key. */
|
||||||
* AES with 256-bit key.
|
AES_256(9),
|
||||||
*/
|
|
||||||
AES_256 (9),
|
|
||||||
|
|
||||||
/**
|
/** Twofish with 256-bit key. */
|
||||||
* Twofish with 256-bit key.
|
TWOFISH(10),
|
||||||
*/
|
|
||||||
TWOFISH (10),
|
|
||||||
|
|
||||||
/**
|
/** Reserved for Camellia with 128-bit key. */
|
||||||
* Reserved for Camellia with 128-bit key.
|
CAMELLIA_128(11),
|
||||||
*/
|
|
||||||
CAMELLIA_128 (11),
|
|
||||||
|
|
||||||
/**
|
/** Reserved for Camellia with 192-bit key. */
|
||||||
* Reserved for Camellia with 192-bit key.
|
CAMELLIA_192(12),
|
||||||
*/
|
|
||||||
CAMELLIA_192 (12),
|
|
||||||
|
|
||||||
/**
|
/** Reserved for Camellia with 256-bit key. */
|
||||||
* Reserved for Camellia with 256-bit key.
|
CAMELLIA_256(13),
|
||||||
*/
|
|
||||||
CAMELLIA_256 (13),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
|
||||||
* If an invalid id is provided, null is returned.
|
* an invalid id is provided, null is returned.
|
||||||
*
|
*
|
||||||
* @param id numeric algorithm id
|
* @param id numeric algorithm id
|
||||||
* @return symmetric key algorithm enum
|
* @return symmetric key algorithm enum
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromId(id: Int): SymmetricKeyAlgorithm? {
|
fun fromId(id: Int): SymmetricKeyAlgorithm? {
|
||||||
return values().firstOrNull {
|
return values().firstOrNull { it.algorithmId == id }
|
||||||
it.algorithmId == id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
|
||||||
* If an invalid id is provided, throw a [NoSuchElementException].
|
* an invalid id is provided, throw a [NoSuchElementException].
|
||||||
*
|
*
|
||||||
* @param id numeric algorithm id
|
* @param id numeric algorithm id
|
||||||
* @return symmetric key algorithm enum
|
* @return symmetric key algorithm enum
|
||||||
*
|
|
||||||
* @throws NoSuchElementException if an unmatched id is provided
|
* @throws NoSuchElementException if an unmatched id is provided
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun requireFromId(id: Int): SymmetricKeyAlgorithm {
|
fun requireFromId(id: Int): SymmetricKeyAlgorithm {
|
||||||
return fromId(id) ?:
|
return fromId(id)
|
||||||
throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
|
?: throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,26 +5,25 @@
|
||||||
package org.pgpainless.algorithm
|
package org.pgpainless.algorithm
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facade class for [org.bouncycastle.bcpg.sig.TrustSignature].
|
* Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. A trust signature subpacket marks
|
||||||
* A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act
|
* the trustworthiness of a certificate and defines its capabilities to act as a trusted introducer.
|
||||||
* as a trusted introducer.
|
|
||||||
*/
|
*/
|
||||||
class Trustworthiness(amount: Int, depth: Int) {
|
class Trustworthiness(amount: Int, depth: Int) {
|
||||||
val depth = capDepth(depth)
|
val depth = capDepth(depth)
|
||||||
val amount = capAmount(amount)
|
val amount = capAmount(amount)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true, if the trust amount is equal to 0.
|
* Returns true, if the trust amount is equal to 0. This means the key is not trusted.
|
||||||
* This means the key is not trusted.
|
|
||||||
*
|
*
|
||||||
* Otherwise return false
|
* Otherwise return false
|
||||||
|
*
|
||||||
* @return true if untrusted
|
* @return true if untrusted
|
||||||
*/
|
*/
|
||||||
fun isNotTrusted() = amount == NOT_TRUSTED
|
fun isNotTrusted() = amount == NOT_TRUSTED
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the certificate is at least marginally trusted.
|
* Return true if the certificate is at least marginally trusted. That is the case, if the trust
|
||||||
* That is the case, if the trust amount is greater than 0.
|
* amount is greater than 0.
|
||||||
*
|
*
|
||||||
* @return true if the cert is at least marginally trusted
|
* @return true if the cert is at least marginally trusted
|
||||||
*/
|
*/
|
||||||
|
@ -46,7 +45,8 @@ class Trustworthiness(amount: Int, depth: Int) {
|
||||||
fun isIntroducer() = depth >= 1
|
fun isIntroducer() = depth >= 1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the certified cert can introduce certificates with trust depth of <pre>otherDepth</pre>.
|
* Return true, if the certified cert can introduce certificates with trust depth of
|
||||||
|
* <pre>otherDepth</pre>.
|
||||||
*
|
*
|
||||||
* @param otherDepth other certifications trust depth
|
* @param otherDepth other certifications trust depth
|
||||||
* @return true if the cert can introduce the other
|
* @return true if the cert can introduce the other
|
||||||
|
@ -54,7 +54,8 @@ class Trustworthiness(amount: Int, depth: Int) {
|
||||||
fun canIntroduce(otherDepth: Int) = depth > otherDepth
|
fun canIntroduce(otherDepth: Int) = depth > otherDepth
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre> trust depth.
|
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre>
|
||||||
|
* trust depth.
|
||||||
*
|
*
|
||||||
* @param other other certificates trust depth
|
* @param other other certificates trust depth
|
||||||
* @return true if the cert can introduce the other
|
* @return true if the cert can introduce the other
|
||||||
|
@ -66,33 +67,29 @@ class Trustworthiness(amount: Int, depth: Int) {
|
||||||
const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced
|
const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced
|
||||||
const val NOT_TRUSTED = 0 // 0 is not trusted
|
const val NOT_TRUSTED = 0 // 0 is not trusted
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic private val validRange = 0..255
|
||||||
private val validRange = 0..255
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This means that we are fully convinced of the trustworthiness of the key.
|
* This means that we are fully convinced of the trustworthiness of the key.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
|
||||||
fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This means that we are marginally (partially) convinced of the trustworthiness of the key.
|
* This means that we are marginally (partially) convinced of the trustworthiness of the
|
||||||
|
* key.
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
|
||||||
fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This means that we do not trust the key.
|
* This means that we do not trust the key. Can be used to overwrite previous trust.
|
||||||
* Can be used to overwrite previous trust.
|
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun untrusted() = Builder(NOT_TRUSTED)
|
||||||
fun untrusted() = Builder(NOT_TRUSTED)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun capAmount(amount: Int): Int {
|
private fun capAmount(amount: Int): Int {
|
||||||
|
@ -114,29 +111,28 @@ class Trustworthiness(amount: Int, depth: Int) {
|
||||||
class Builder(val amount: Int) {
|
class Builder(val amount: Int) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key is a trusted introducer (depth 1).
|
* The key is a trusted introducer (depth 1). Certifications made by this key are considered
|
||||||
* Certifications made by this key are considered trustworthy.
|
* trustworthy.
|
||||||
*
|
*
|
||||||
* @return trust
|
* @return trust
|
||||||
*/
|
*/
|
||||||
fun introducer() = Trustworthiness(amount, 1)
|
fun introducer() = Trustworthiness(amount, 1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key is a meta introducer (depth 2).
|
* The key is a meta introducer (depth 2). This key can introduce trusted introducers of
|
||||||
* This key can introduce trusted introducers of depth 1.
|
* depth 1.
|
||||||
*
|
*
|
||||||
* @return trust
|
* @return trust
|
||||||
*/
|
*/
|
||||||
fun metaIntroducer() = Trustworthiness(amount, 2)
|
fun metaIntroducer() = Trustworthiness(amount, 2)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key is a meta introducer of depth <pre>n</pre>.
|
* The key is a meta introducer of depth <pre>n</pre>. This key can introduce meta
|
||||||
* This key can introduce meta introducers of depth <pre>n - 1</pre>.
|
* introducers of depth <pre>n - 1</pre>.
|
||||||
*
|
*
|
||||||
* @param n depth
|
* @param n depth
|
||||||
* @return trust
|
* @return trust
|
||||||
*/
|
*/
|
||||||
fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d)
|
fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ interface HashAlgorithmNegotiator {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures
|
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for
|
||||||
* based on the given [Policy].
|
* non-revocation signatures based on the given [Policy].
|
||||||
*
|
*
|
||||||
* @param policy algorithm policy
|
* @param policy algorithm policy
|
||||||
* @return negotiator
|
* @return negotiator
|
||||||
|
@ -38,8 +38,8 @@ interface HashAlgorithmNegotiator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures
|
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation
|
||||||
* based on the given [Policy].
|
* signatures based on the given [Policy].
|
||||||
*
|
*
|
||||||
* @param policy algorithm policy
|
* @param policy algorithm policy
|
||||||
* @return negotiator
|
* @return negotiator
|
||||||
|
@ -57,15 +57,17 @@ interface HashAlgorithmNegotiator {
|
||||||
* @return negotiator
|
* @return negotiator
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator {
|
fun negotiateByPolicy(
|
||||||
return object: HashAlgorithmNegotiator {
|
hashAlgorithmPolicy: Policy.HashAlgorithmPolicy
|
||||||
override fun negotiateHashAlgorithm(orderedPrefs: Set<HashAlgorithm>): HashAlgorithm {
|
): HashAlgorithmNegotiator {
|
||||||
return orderedPrefs.firstOrNull {
|
return object : HashAlgorithmNegotiator {
|
||||||
hashAlgorithmPolicy.isAcceptable(it)
|
override fun negotiateHashAlgorithm(
|
||||||
} ?: hashAlgorithmPolicy.defaultHashAlgorithm()
|
orderedPrefs: Set<HashAlgorithm>
|
||||||
|
): HashAlgorithm {
|
||||||
|
return orderedPrefs.firstOrNull { hashAlgorithmPolicy.isAcceptable(it) }
|
||||||
|
?: hashAlgorithmPolicy.defaultHashAlgorithm()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,38 +4,41 @@
|
||||||
|
|
||||||
package org.pgpainless.algorithm.negotiation
|
package org.pgpainless.algorithm.negotiation
|
||||||
|
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.policy.Policy
|
import org.pgpainless.policy.Policy
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
|
|
||||||
interface SymmetricKeyAlgorithmNegotiator {
|
interface SymmetricKeyAlgorithmNegotiator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Negotiate a symmetric encryption algorithm.
|
* Negotiate a symmetric encryption algorithm. If the override is non-null, it will be returned
|
||||||
* If the override is non-null, it will be returned instead of performing an actual negotiation.
|
* instead of performing an actual negotiation. Otherwise, the list of ordered sets containing
|
||||||
* Otherwise, the list of ordered sets containing the preferences of different recipient keys will be
|
* the preferences of different recipient keys will be used to determine a suitable symmetric
|
||||||
* used to determine a suitable symmetric encryption algorithm.
|
* encryption algorithm.
|
||||||
*
|
*
|
||||||
* @param policy algorithm policy
|
* @param policy algorithm policy
|
||||||
* @param override algorithm override (if not null, return this)
|
* @param override algorithm override (if not null, return this)
|
||||||
* @param keyPreferences list of preferences per key
|
* @param keyPreferences list of preferences per key
|
||||||
* @return negotiated algorithm
|
* @return negotiated algorithm
|
||||||
*/
|
*/
|
||||||
fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy,
|
fun negotiate(
|
||||||
override: SymmetricKeyAlgorithm?,
|
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>): SymmetricKeyAlgorithm
|
override: SymmetricKeyAlgorithm?,
|
||||||
|
keyPreferences: List<Set<SymmetricKeyAlgorithm>>
|
||||||
|
): SymmetricKeyAlgorithm
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun byPopularity(): SymmetricKeyAlgorithmNegotiator {
|
fun byPopularity(): SymmetricKeyAlgorithmNegotiator {
|
||||||
return object: SymmetricKeyAlgorithmNegotiator {
|
return object : SymmetricKeyAlgorithmNegotiator {
|
||||||
override fun negotiate(
|
override fun negotiate(
|
||||||
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||||
override: SymmetricKeyAlgorithm?,
|
override: SymmetricKeyAlgorithm?,
|
||||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>):
|
keyPreferences: List<Set<SymmetricKeyAlgorithm>>
|
||||||
SymmetricKeyAlgorithm {
|
): SymmetricKeyAlgorithm {
|
||||||
if (override == SymmetricKeyAlgorithm.NULL) {
|
if (override == SymmetricKeyAlgorithm.NULL) {
|
||||||
throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).")
|
throw IllegalArgumentException(
|
||||||
|
"Algorithm override cannot be NULL (plaintext).")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (override != null) {
|
if (override != null) {
|
||||||
|
@ -53,7 +56,9 @@ interface SymmetricKeyAlgorithmNegotiator {
|
||||||
|
|
||||||
// Pivot map and sort by popularity ascending
|
// Pivot map and sort by popularity ascending
|
||||||
// score to list(algo)
|
// score to list(algo)
|
||||||
val byScore = supportWeight.toList()
|
val byScore =
|
||||||
|
supportWeight
|
||||||
|
.toList()
|
||||||
.map { e -> e.second to e.first }
|
.map { e -> e.second to e.first }
|
||||||
.groupBy { e -> e.first }
|
.groupBy { e -> e.first }
|
||||||
.map { e -> e.key to e.value.map { it.second }.toList() }
|
.map { e -> e.key to e.value.map { it.second }.toList() }
|
||||||
|
@ -70,8 +75,7 @@ interface SymmetricKeyAlgorithmNegotiator {
|
||||||
|
|
||||||
return policy.defaultSymmetricKeyAlgorithm
|
return policy.defaultSymmetricKeyAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,19 @@ package org.pgpainless.authentication
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
|
|
||||||
class CertificateAuthenticity(val userId: String,
|
class CertificateAuthenticity(
|
||||||
val certificate: PGPPublicKeyRing,
|
val userId: String,
|
||||||
val certificationChains: Map<CertificationChain, Int>,
|
val certificate: PGPPublicKeyRing,
|
||||||
val targetAmount: Int) {
|
val certificationChains: Map<CertificationChain, Int>,
|
||||||
|
val targetAmount: Int
|
||||||
|
) {
|
||||||
|
|
||||||
val totalTrustAmount: Int
|
val totalTrustAmount: Int
|
||||||
get() = certificationChains.values.sum()
|
get() = certificationChains.values.sum()
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the degree of authentication of the binding in percent.
|
* Return the degree of authentication of the binding in percent. 100% means full
|
||||||
* 100% means full authentication.
|
* authentication. Values smaller than 100% mean partial authentication.
|
||||||
* Values smaller than 100% mean partial authentication.
|
|
||||||
*
|
*
|
||||||
* @return authenticity in percent
|
* @return authenticity in percent
|
||||||
*/
|
*/
|
||||||
|
@ -42,16 +42,7 @@ class CertificateAuthenticity(val userId: String,
|
||||||
* @param trustAmount actual trust amount of the chain
|
* @param trustAmount actual trust amount of the chain
|
||||||
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
|
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
|
||||||
*/
|
*/
|
||||||
class CertificationChain(
|
class CertificationChain(val trustAmount: Int, val chainLinks: List<ChainLink>) {}
|
||||||
val trustAmount: Int,
|
|
||||||
val chainLinks: List<ChainLink>) {
|
|
||||||
|
|
||||||
}
|
/** A chain link contains a node in the trust chain. */
|
||||||
|
class ChainLink(val certificate: PGPPublicKeyRing) {}
|
||||||
/**
|
|
||||||
* A chain link contains a node in the trust chain.
|
|
||||||
*/
|
|
||||||
class ChainLink(
|
|
||||||
val certificate: PGPPublicKeyRing) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package org.pgpainless.authentication;
|
package org.pgpainless.authentication
|
||||||
|
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a CA that can authenticate trust-worthy certificates.
|
* Interface for a CA that can authenticate trust-worthy certificates. Such a CA might be a fixed
|
||||||
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
|
* list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
|
||||||
*
|
*
|
||||||
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
|
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
|
||||||
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
|
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
|
||||||
|
@ -17,52 +17,58 @@ import java.util.*
|
||||||
interface CertificateAuthority {
|
interface CertificateAuthority {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the authenticity of the binding between the given fingerprint and the userId.
|
* Determine the authenticity of the binding between the given fingerprint and the userId. In
|
||||||
* In other words, determine, how much evidence can be gathered, that the certificate with the given
|
* other words, determine, how much evidence can be gathered, that the certificate with the
|
||||||
* fingerprint really belongs to the user with the given userId.
|
* given fingerprint really belongs to the user with the given userId.
|
||||||
*
|
*
|
||||||
* @param fingerprint fingerprint of the certificate
|
* @param fingerprint fingerprint of the certificate
|
||||||
* @param userId userId
|
* @param userId userId
|
||||||
* @param email if true, the userId will be treated as an email address and all user-IDs containing
|
* @param email if true, the userId will be treated as an email address and all user-IDs
|
||||||
* the email address will be matched.
|
* containing the email address will be matched.
|
||||||
* @param referenceTime reference time at which the binding shall be evaluated
|
* @param referenceTime reference time at which the binding shall be evaluated
|
||||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||||
* 60 = partially authenticated...)
|
* authenticated, 60 = partially authenticated...)
|
||||||
* @return information about the authenticity of the binding
|
* @return information about the authenticity of the binding
|
||||||
*/
|
*/
|
||||||
fun authenticateBinding(fingerprint: OpenPgpFingerprint,
|
fun authenticateBinding(
|
||||||
userId: String,
|
fingerprint: OpenPgpFingerprint,
|
||||||
email: Boolean,
|
userId: String,
|
||||||
referenceTime: Date,
|
email: Boolean,
|
||||||
targetAmount: Int): CertificateAuthenticity;
|
referenceTime: Date,
|
||||||
|
targetAmount: Int
|
||||||
|
): CertificateAuthenticity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookup certificates, which carry a trustworthy binding to the given userId.
|
* Lookup certificates, which carry a trustworthy binding to the given userId.
|
||||||
*
|
*
|
||||||
* @param userId userId
|
* @param userId userId
|
||||||
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing
|
* @param email if true, the user-ID will be treated as an email address and all user-IDs
|
||||||
* the email address will be matched.
|
* containing the email address will be matched.
|
||||||
* @param referenceTime reference time at which the binding shall be evaluated
|
* @param referenceTime reference time at which the binding shall be evaluated
|
||||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||||
* 60 = partially authenticated...)
|
* authenticated, 60 = partially authenticated...)
|
||||||
* @return list of identified bindings
|
* @return list of identified bindings
|
||||||
*/
|
*/
|
||||||
fun lookupByUserId(userId: String,
|
fun lookupByUserId(
|
||||||
email: Boolean,
|
userId: String,
|
||||||
referenceTime: Date,
|
email: Boolean,
|
||||||
targetAmount: Int): List<CertificateAuthenticity>
|
referenceTime: Date,
|
||||||
|
targetAmount: Int
|
||||||
|
): List<CertificateAuthenticity>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identify trustworthy bindings for a certificate.
|
* Identify trustworthy bindings for a certificate. The result is a list of authenticatable
|
||||||
* The result is a list of authenticatable userIds on the certificate.
|
* userIds on the certificate.
|
||||||
*
|
*
|
||||||
* @param fingerprint fingerprint of the certificate
|
* @param fingerprint fingerprint of the certificate
|
||||||
* @param referenceTime reference time for trust calculations
|
* @param referenceTime reference time for trust calculations
|
||||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||||
* 60 = partially authenticated...)
|
* authenticated, 60 = partially authenticated...)
|
||||||
* @return list of identified bindings
|
* @return list of identified bindings
|
||||||
*/
|
*/
|
||||||
fun identifyByFingerprint(fingerprint: OpenPgpFingerprint,
|
fun identifyByFingerprint(
|
||||||
referenceTime: Date,
|
fingerprint: OpenPgpFingerprint,
|
||||||
targetAmount: Int): List<CertificateAuthenticity>
|
referenceTime: Date,
|
||||||
|
targetAmount: Int
|
||||||
|
): List<CertificateAuthenticity>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.extensions.getPublicKeyFor
|
import org.bouncycastle.extensions.getPublicKeyFor
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||||
|
@ -14,13 +17,8 @@ import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
import org.pgpainless.signature.SignatureUtils
|
import org.pgpainless.signature.SignatureUtils
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import org.pgpainless.util.SessionKey
|
import org.pgpainless.util.SessionKey
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/** Options for decryption and signature verification. */
|
||||||
* Options for decryption and signature verification.
|
|
||||||
*/
|
|
||||||
class ConsumerOptions {
|
class ConsumerOptions {
|
||||||
|
|
||||||
private var ignoreMDCErrors = false
|
private var ignoreMDCErrors = false
|
||||||
|
@ -34,15 +32,16 @@ class ConsumerOptions {
|
||||||
private var missingCertificateCallback: MissingPublicKeyCallback? = null
|
private var missingCertificateCallback: MissingPublicKeyCallback? = null
|
||||||
|
|
||||||
private var sessionKey: SessionKey? = null
|
private var sessionKey: SessionKey? = null
|
||||||
private val customDecryptorFactories = mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
|
private val customDecryptorFactories =
|
||||||
|
mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
|
||||||
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
|
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
|
||||||
private val decryptionPassphrases = mutableSetOf<Passphrase>()
|
private val decryptionPassphrases = mutableSetOf<Passphrase>()
|
||||||
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
|
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
|
||||||
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
|
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consider signatures on the message made before the given timestamp invalid.
|
* Consider signatures on the message made before the given timestamp invalid. Null means no
|
||||||
* Null means no limitation.
|
* limitation.
|
||||||
*
|
*
|
||||||
* @param timestamp timestamp
|
* @param timestamp timestamp
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -54,8 +53,8 @@ class ConsumerOptions {
|
||||||
fun getVerifyNotBefore() = verifyNotBefore
|
fun getVerifyNotBefore() = verifyNotBefore
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consider signatures on the message made after the given timestamp invalid.
|
* Consider signatures on the message made after the given timestamp invalid. Null means no
|
||||||
* Null means no limitation.
|
* limitation.
|
||||||
*
|
*
|
||||||
* @param timestamp timestamp
|
* @param timestamp timestamp
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -82,26 +81,27 @@ class ConsumerOptions {
|
||||||
* @param verificationCerts certificates for signature verification
|
* @param verificationCerts certificates for signature verification
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply {
|
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions =
|
||||||
for (cert in verificationCerts) {
|
apply {
|
||||||
addVerificationCert(cert)
|
for (cert in verificationCerts) {
|
||||||
|
addVerificationCert(cert)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some detached signatures from the given [InputStream] for verification.
|
* Add some detached signatures from the given [InputStream] for verification.
|
||||||
*
|
*
|
||||||
* @param signatureInputStream input stream of detached signatures
|
* @param signatureInputStream input stream of detached signatures
|
||||||
* @return options
|
* @return options
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
* @throws IOException in case of an IO error
|
||||||
* @throws PGPException in case of an OpenPGP error
|
* @throws PGPException in case of an OpenPGP error
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class, PGPException::class)
|
@Throws(IOException::class, PGPException::class)
|
||||||
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply {
|
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions =
|
||||||
val signatures = SignatureUtils.readSignatures(signatureInputStream)
|
apply {
|
||||||
addVerificationOfDetachedSignatures(signatures)
|
val signatures = SignatureUtils.readSignatures(signatureInputStream)
|
||||||
}
|
addVerificationOfDetachedSignatures(signatures)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add some detached signatures for verification.
|
* Add some detached signatures for verification.
|
||||||
|
@ -109,7 +109,9 @@ class ConsumerOptions {
|
||||||
* @param detachedSignatures detached signatures
|
* @param detachedSignatures detached signatures
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
fun addVerificationOfDetachedSignatures(detachedSignatures: List<PGPSignature>): ConsumerOptions = apply {
|
fun addVerificationOfDetachedSignatures(
|
||||||
|
detachedSignatures: List<PGPSignature>
|
||||||
|
): ConsumerOptions = apply {
|
||||||
for (signature in detachedSignatures) {
|
for (signature in detachedSignatures) {
|
||||||
addVerificationOfDetachedSignature(signature)
|
addVerificationOfDetachedSignature(signature)
|
||||||
}
|
}
|
||||||
|
@ -121,14 +123,16 @@ class ConsumerOptions {
|
||||||
* @param detachedSignature detached signature
|
* @param detachedSignature detached signature
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply {
|
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions =
|
||||||
detachedSignatures.add(detachedSignature)
|
apply {
|
||||||
}
|
detachedSignatures.add(detachedSignature)
|
||||||
|
}
|
||||||
|
|
||||||
fun getDetachedSignatures() = detachedSignatures.toList()
|
fun getDetachedSignatures() = detachedSignatures.toList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a callback that's used when a certificate (public key) is missing for signature verification.
|
* Set a callback that's used when a certificate (public key) is missing for signature
|
||||||
|
* verification.
|
||||||
*
|
*
|
||||||
* @param callback callback
|
* @param callback callback
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -152,18 +156,18 @@ class ConsumerOptions {
|
||||||
fun getSessionKey() = sessionKey
|
fun getSessionKey() = sessionKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector]
|
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is
|
||||||
* is used to decrypt it when needed.
|
* used to decrypt it when needed.
|
||||||
*
|
*
|
||||||
* @param key key
|
* @param key key
|
||||||
* @param keyRingProtector protector for the secret key
|
* @param keyRingProtector protector for the secret key
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addDecryptionKey(key: PGPSecretKeyRing,
|
fun addDecryptionKey(
|
||||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
key: PGPSecretKeyRing,
|
||||||
decryptionKeys[key] = protector
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||||
}
|
) = apply { decryptionKeys[key] = protector }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the keys in the provided key collection for message decryption.
|
* Add the keys in the provided key collection for message decryption.
|
||||||
|
@ -173,18 +177,21 @@ class ConsumerOptions {
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addDecryptionKeys(keys: PGPSecretKeyRingCollection,
|
fun addDecryptionKeys(
|
||||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
keys: PGPSecretKeyRingCollection,
|
||||||
|
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||||
|
) = apply {
|
||||||
for (key in keys) {
|
for (key in keys) {
|
||||||
addDecryptionKey(key, protector)
|
addDecryptionKey(key, protector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a passphrase for message decryption.
|
* Add a passphrase for message decryption. This passphrase will be used to try to decrypt
|
||||||
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase.
|
* messages which were symmetrically encrypted for a passphrase.
|
||||||
*
|
*
|
||||||
* See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
* See
|
||||||
|
* [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
||||||
*
|
*
|
||||||
* @param passphrase passphrase
|
* @param passphrase passphrase
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -195,8 +202,8 @@ class ConsumerOptions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
|
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
|
||||||
* hardware-backed secret keys.
|
* hardware-backed secret keys. (See e.g.
|
||||||
* (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
|
* [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
|
||||||
*
|
*
|
||||||
* @param factory decryptor factory
|
* @param factory decryptor factory
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -206,9 +213,8 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the custom [PublicKeyDataDecryptorFactory] that were
|
* Return the custom [PublicKeyDataDecryptorFactory] that were set by the user. These factories
|
||||||
* set by the user.
|
* can be used to decrypt session keys using a custom logic.
|
||||||
* These factories can be used to decrypt session keys using a custom logic.
|
|
||||||
*
|
*
|
||||||
* @return custom decryptor factories
|
* @return custom decryptor factories
|
||||||
*/
|
*/
|
||||||
|
@ -236,8 +242,8 @@ class ConsumerOptions {
|
||||||
fun getCertificateSource() = certificates
|
fun getCertificateSource() = certificates
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the callback that gets called when a certificate for signature verification is missing.
|
* Return the callback that gets called when a certificate for signature verification is
|
||||||
* This method might return `null` if the users hasn't set a callback.
|
* missing. This method might return `null` if the users hasn't set a callback.
|
||||||
*
|
*
|
||||||
* @return missing public key callback
|
* @return missing public key callback
|
||||||
*/
|
*/
|
||||||
|
@ -255,45 +261,46 @@ class ConsumerOptions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, PGPainless will require encrypted messages to make use of SEIP data packets.
|
* By default, PGPainless will require encrypted messages to make use of SEIP data packets.
|
||||||
* Those are Symmetrically Encrypted Integrity Protected Data packets.
|
* Those are Symmetrically Encrypted Integrity Protected Data packets. Symmetrically Encrypted
|
||||||
* Symmetrically Encrypted Data Packets without integrity protection are rejected by default.
|
* Data Packets without integrity protection are rejected by default. Furthermore, PGPainless
|
||||||
* Furthermore, PGPainless will throw an exception if verification of the MDC error detection
|
* will throw an exception if verification of the MDC error detection code of the SEIP packet
|
||||||
* code of the SEIP packet fails.
|
* fails.
|
||||||
*
|
*
|
||||||
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an
|
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an
|
||||||
* attack or data corruption.
|
* attack or data corruption.
|
||||||
*
|
*
|
||||||
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data
|
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data
|
||||||
* without integrity protection.
|
* without integrity protection. If the flag <pre>ignoreMDCErrors</pre> is set to true,
|
||||||
* If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will
|
* PGPainless will
|
||||||
|
* * not throw exceptions for SEIP packets with tampered ciphertext
|
||||||
|
* * not throw exceptions for SEIP packets with tampered 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
|
* It will however still throw an exception if it encounters a SEIP packet with missing or
|
||||||
* * not throw exceptions for SEIP packets with tampered MDC
|
* truncated MDC
|
||||||
* * not throw exceptions for MDCs with bad CTB
|
|
||||||
* * not throw exceptions for MDCs with bad length
|
|
||||||
*
|
*
|
||||||
*
|
* See
|
||||||
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
|
* [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
|
||||||
*
|
|
||||||
* See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
|
|
||||||
*
|
*
|
||||||
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
|
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
@Deprecated("Ignoring non-integrity-protected packets is discouraged.")
|
@Deprecated("Ignoring non-integrity-protected packets is discouraged.")
|
||||||
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors }
|
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply {
|
||||||
|
this.ignoreMDCErrors = ignoreMDCErrors
|
||||||
|
}
|
||||||
|
|
||||||
fun isIgnoreMDCErrors() = ignoreMDCErrors
|
fun isIgnoreMDCErrors() = ignoreMDCErrors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data.
|
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This
|
||||||
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data.
|
* workaround might come in handy if PGPainless accidentally mistakes the data for binary
|
||||||
|
* OpenPGP data.
|
||||||
*
|
*
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
fun forceNonOpenPgpData(): ConsumerOptions = apply {
|
fun forceNonOpenPgpData(): ConsumerOptions = apply { this.forceNonOpenPgpData = true }
|
||||||
this.forceNonOpenPgpData = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
|
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
|
||||||
|
@ -303,15 +310,15 @@ class ConsumerOptions {
|
||||||
fun isForceNonOpenPgpData() = forceNonOpenPgpData
|
fun isForceNonOpenPgpData() = forceNonOpenPgpData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify the [MissingKeyPassphraseStrategy].
|
* Specify the [MissingKeyPassphraseStrategy]. This strategy defines, how missing passphrases
|
||||||
* This strategy defines, how missing passphrases for unlocking secret keys are handled.
|
* for unlocking secret keys are handled. In interactive mode
|
||||||
* In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
|
* ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
|
||||||
* passphrases for secret keys via the [SecretKeyRingProtector]
|
* passphrases for secret keys via the [SecretKeyRingProtector]
|
||||||
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback.
|
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback.
|
||||||
*
|
*
|
||||||
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead
|
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will
|
||||||
* throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which
|
* instead throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of
|
||||||
* there are missing passphrases.
|
* all keys for which there are missing passphrases.
|
||||||
*
|
*
|
||||||
* @param strategy strategy
|
* @param strategy strategy
|
||||||
* @return options
|
* @return options
|
||||||
|
@ -331,8 +338,8 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a custom multi-pass strategy for processing cleartext-signed messages.
|
* Set a custom multi-pass strategy for processing cleartext-signed messages. Uses
|
||||||
* Uses [InMemoryMultiPassStrategy] by default.
|
* [InMemoryMultiPassStrategy] by default.
|
||||||
*
|
*
|
||||||
* @param multiPassStrategy multi-pass caching strategy
|
* @param multiPassStrategy multi-pass caching strategy
|
||||||
* @return builder
|
* @return builder
|
||||||
|
@ -343,8 +350,7 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the currently configured [MultiPassStrategy].
|
* Return the currently configured [MultiPassStrategy]. Defaults to [InMemoryMultiPassStrategy].
|
||||||
* Defaults to [InMemoryMultiPassStrategy].
|
|
||||||
*
|
*
|
||||||
* @return multi-pass strategy
|
* @return multi-pass strategy
|
||||||
*/
|
*/
|
||||||
|
@ -353,8 +359,8 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source for OpenPGP certificates.
|
* Source for OpenPGP certificates. When verifying signatures on a message, this object holds
|
||||||
* When verifying signatures on a message, this object holds available signer certificates.
|
* available signer certificates.
|
||||||
*/
|
*/
|
||||||
class CertificateSource {
|
class CertificateSource {
|
||||||
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
|
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
|
||||||
|
@ -370,6 +376,7 @@ class ConsumerOptions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the set of explicitly set verification certificates.
|
* Return the set of explicitly set verification certificates.
|
||||||
|
*
|
||||||
* @return explicitly set verification certs
|
* @return explicitly set verification certs
|
||||||
*/
|
*/
|
||||||
fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
|
fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
|
||||||
|
@ -377,9 +384,9 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a certificate which contains a subkey with the given keyId.
|
* Return a certificate which contains a subkey with the given keyId. This method first
|
||||||
* This method first checks all explicitly set verification certs and if no cert is found it consults
|
* checks all explicitly set verification certs and if no cert is found it consults the
|
||||||
* the certificate stores.
|
* certificate stores.
|
||||||
*
|
*
|
||||||
* @param keyId key id
|
* @param keyId key id
|
||||||
* @return certificate
|
* @return certificate
|
||||||
|
@ -389,13 +396,10 @@ class ConsumerOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? =
|
fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? =
|
||||||
explicitCertificates.firstOrNull {
|
explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null }
|
||||||
it.getPublicKeyFor(signature) != null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun get() = ConsumerOptions()
|
||||||
fun get() = ConsumerOptions()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message decryption
|
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message
|
||||||
* using public keys.
|
* decryption using public keys. This class can for example be used to implement message encryption
|
||||||
* This class can for example be used to implement message encryption using hardware tokens like smartcards or
|
* using hardware tokens like smartcards or TPMs.
|
||||||
* TPMs.
|
*
|
||||||
* @see [ConsumerOptions.addCustomDecryptorFactory]
|
* @see [ConsumerOptions.addCustomDecryptorFactory]
|
||||||
*/
|
*/
|
||||||
interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory {
|
interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory]
|
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is
|
||||||
* is intended.
|
* intended.
|
||||||
*
|
*
|
||||||
* @return subkey identifier
|
* @return subkey identifier
|
||||||
*/
|
*/
|
||||||
val subkeyIdentifier: SubkeyIdentifier
|
val subkeyIdentifier: SubkeyIdentifier
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,20 @@ package org.pgpainless.decryption_verification
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builder class that takes an [InputStream] of ciphertext (or plaintext signed data)
|
* Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) and combines
|
||||||
* and combines it with a configured [ConsumerOptions] object to form a [DecryptionStream] which
|
* it with a configured [ConsumerOptions] object to form a [DecryptionStream] which can be used to
|
||||||
* can be used to decrypt an OpenPGP message or verify signatures.
|
* decrypt an OpenPGP message or verify signatures.
|
||||||
*/
|
*/
|
||||||
class DecryptionBuilder: DecryptionBuilderInterface {
|
class DecryptionBuilder : DecryptionBuilderInterface {
|
||||||
|
|
||||||
override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
|
override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
|
||||||
return DecryptWithImpl(inputStream)
|
return DecryptWithImpl(inputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DecryptWithImpl(val inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
|
class DecryptWithImpl(val inputStream: InputStream) : DecryptionBuilderInterface.DecryptWith {
|
||||||
|
|
||||||
override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream {
|
override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream {
|
||||||
return OpenPgpMessageInputStream.create(inputStream, consumerOptions)
|
return OpenPgpMessageInputStream.create(inputStream, consumerOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import org.bouncycastle.openpgp.PGPException
|
||||||
|
|
||||||
interface DecryptionBuilderInterface {
|
interface DecryptionBuilderInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed data.
|
* Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed
|
||||||
|
* data.
|
||||||
*
|
*
|
||||||
* @param inputStream encrypted and/or signed data.
|
* @param inputStream encrypted and/or signed data.
|
||||||
* @return api handle
|
* @return api handle
|
||||||
|
@ -31,4 +32,4 @@ interface DecryptionBuilderInterface {
|
||||||
@Throws(PGPException::class, IOException::class)
|
@Throws(PGPException::class, IOException::class)
|
||||||
fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream
|
fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import java.io.InputStream
|
||||||
/**
|
/**
|
||||||
* Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages.
|
* Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages.
|
||||||
*/
|
*/
|
||||||
abstract class DecryptionStream: InputStream() {
|
abstract class DecryptionStream : InputStream() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return [MessageMetadata] about the decrypted / verified message.
|
* Return [MessageMetadata] about the decrypted / verified message. The [DecryptionStream] MUST
|
||||||
* The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed.
|
* be closed via [close] before the metadata object can be accessed.
|
||||||
*
|
*
|
||||||
* @return message metadata
|
* @return message metadata
|
||||||
*/
|
*/
|
||||||
abstract val metadata: MessageMetadata
|
abstract val metadata: MessageMetadata
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import kotlin.jvm.Throws
|
||||||
import org.bouncycastle.bcpg.AEADEncDataPacket
|
import org.bouncycastle.bcpg.AEADEncDataPacket
|
||||||
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
|
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
|
||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
|
@ -12,25 +13,22 @@ import org.bouncycastle.openpgp.operator.PGPDataDecryptor
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
|
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
import kotlin.jvm.Throws
|
|
||||||
|
|
||||||
/**
|
/** Enable integration of hardware-backed OpenPGP keys. */
|
||||||
* Enable integration of hardware-backed OpenPGP keys.
|
|
||||||
*/
|
|
||||||
class HardwareSecurity {
|
class HardwareSecurity {
|
||||||
|
|
||||||
interface DecryptionCallback {
|
interface DecryptionCallback {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with
|
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for
|
||||||
* hardware security modules such as smartcards or TPMs.
|
* dealing with hardware security modules such as smartcards or TPMs.
|
||||||
*
|
*
|
||||||
* If decryption fails for some reason, a subclass of the [HardwareSecurityException] is thrown.
|
* If decryption fails for some reason, a subclass of the [HardwareSecurityException] is
|
||||||
|
* thrown.
|
||||||
*
|
*
|
||||||
* @param keyId id of the key
|
* @param keyId id of the key
|
||||||
* @param keyAlgorithm algorithm
|
* @param keyAlgorithm algorithm
|
||||||
* @param sessionKeyData encrypted session key
|
* @param sessionKeyData encrypted session key
|
||||||
*
|
|
||||||
* @return decrypted session key
|
* @return decrypted session key
|
||||||
* @throws HardwareSecurityException exception
|
* @throws HardwareSecurityException exception
|
||||||
*/
|
*/
|
||||||
|
@ -39,38 +37,51 @@ class HardwareSecurity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys
|
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted
|
||||||
* to a [DecryptionCallback].
|
* session keys to a [DecryptionCallback]. Users can provide such a callback to delegate
|
||||||
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs.
|
* decryption of messages to hardware security SDKs.
|
||||||
*/
|
*/
|
||||||
class HardwareDataDecryptorFactory(
|
class HardwareDataDecryptorFactory(
|
||||||
override val subkeyIdentifier: SubkeyIdentifier,
|
override val subkeyIdentifier: SubkeyIdentifier,
|
||||||
private val callback: DecryptionCallback,
|
private val callback: DecryptionCallback,
|
||||||
) : CustomPublicKeyDataDecryptorFactory {
|
) : CustomPublicKeyDataDecryptorFactory {
|
||||||
|
|
||||||
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
|
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
|
||||||
private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null)
|
private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null)
|
||||||
|
|
||||||
override fun createDataDecryptor(withIntegrityPacket: Boolean, encAlgorithm: Int, key: ByteArray?): PGPDataDecryptor {
|
override fun createDataDecryptor(
|
||||||
|
withIntegrityPacket: Boolean,
|
||||||
|
encAlgorithm: Int,
|
||||||
|
key: ByteArray?
|
||||||
|
): PGPDataDecryptor {
|
||||||
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key)
|
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor {
|
override fun createDataDecryptor(
|
||||||
|
aeadEncDataPacket: AEADEncDataPacket?,
|
||||||
|
sessionKey: PGPSessionKey?
|
||||||
|
): PGPDataDecryptor {
|
||||||
return factory.createDataDecryptor(aeadEncDataPacket, sessionKey)
|
return factory.createDataDecryptor(aeadEncDataPacket, sessionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createDataDecryptor(seipd: SymmetricEncIntegrityPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor {
|
override fun createDataDecryptor(
|
||||||
|
seipd: SymmetricEncIntegrityPacket?,
|
||||||
|
sessionKey: PGPSessionKey?
|
||||||
|
): PGPDataDecryptor {
|
||||||
return factory.createDataDecryptor(seipd, sessionKey)
|
return factory.createDataDecryptor(seipd, sessionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray {
|
override fun recoverSessionData(
|
||||||
|
keyAlgorithm: Int,
|
||||||
|
secKeyData: Array<out ByteArray>
|
||||||
|
): ByteArray {
|
||||||
return try {
|
return try {
|
||||||
callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
|
callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
|
||||||
} catch (e : HardwareSecurityException) {
|
} catch (e: HardwareSecurityException) {
|
||||||
throw PGPException("Hardware-backed decryption failed.", e)
|
throw PGPException("Hardware-backed decryption failed.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HardwareSecurityException : Exception()
|
class HardwareSecurityException : Exception()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,23 +4,25 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedData
|
import org.bouncycastle.openpgp.PGPEncryptedData
|
||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
import org.pgpainless.exception.ModificationDetectionException
|
import org.pgpainless.exception.ModificationDetectionException
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class IntegrityProtectedInputStream(
|
class IntegrityProtectedInputStream(
|
||||||
private val inputStream: InputStream,
|
private val inputStream: InputStream,
|
||||||
private val encryptedData: PGPEncryptedData,
|
private val encryptedData: PGPEncryptedData,
|
||||||
private val options: ConsumerOptions
|
private val options: ConsumerOptions
|
||||||
) : InputStream() {
|
) : InputStream() {
|
||||||
private var closed: Boolean = false
|
private var closed: Boolean = false
|
||||||
|
|
||||||
override fun read() = inputStream.read()
|
override fun read() = inputStream.read()
|
||||||
|
|
||||||
override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len)
|
override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len)
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (closed) return
|
if (closed) return
|
||||||
|
|
||||||
|
@ -29,7 +31,7 @@ class IntegrityProtectedInputStream(
|
||||||
try {
|
try {
|
||||||
if (!encryptedData.verify()) throw ModificationDetectionException()
|
if (!encryptedData.verify()) throw ModificationDetectionException()
|
||||||
LOGGER.debug("Integrity Protection check passed.")
|
LOGGER.debug("Integrity Protection check passed.")
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
throw IOException("Data appears to not be integrity protected.", e)
|
throw IOException("Data appears to not be integrity protected.", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,4 +41,4 @@ class IntegrityProtectedInputStream(
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val LOGGER: Logger = LoggerFactory.getLogger(IntegrityProtectedInputStream::class.java)
|
val LOGGER: Logger = LoggerFactory.getLogger(IntegrityProtectedInputStream::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.pgpainless.implementation.ImplementationFactory
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
import org.pgpainless.util.ArmorUtils
|
import org.pgpainless.util.ArmorUtils
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected.
|
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase
|
||||||
|
* protected.
|
||||||
*/
|
*/
|
||||||
class MessageInspector {
|
class MessageInspector {
|
||||||
|
|
||||||
|
@ -23,9 +24,10 @@ class MessageInspector {
|
||||||
* @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures
|
* @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures
|
||||||
*/
|
*/
|
||||||
data class EncryptionInfo(
|
data class EncryptionInfo(
|
||||||
val keyIds: List<Long>,
|
val keyIds: List<Long>,
|
||||||
val isPassphraseEncrypted: Boolean,
|
val isPassphraseEncrypted: Boolean,
|
||||||
val isSignedOnly: Boolean) {
|
val isSignedOnly: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
val isEncrypted: Boolean
|
val isEncrypted: Boolean
|
||||||
get() = isPassphraseEncrypted || keyIds.isNotEmpty()
|
get() = isPassphraseEncrypted || keyIds.isNotEmpty()
|
||||||
|
@ -34,25 +36,26 @@ class MessageInspector {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
|
* Parses parts of the provided OpenPGP message in order to determine which keys were used
|
||||||
|
* to encrypt it.
|
||||||
*
|
*
|
||||||
* @param message OpenPGP message
|
* @param message OpenPGP message
|
||||||
* @return encryption info
|
* @return encryption info
|
||||||
*
|
|
||||||
* @throws PGPException in case the message is broken
|
* @throws PGPException in case the message is broken
|
||||||
* @throws IOException in case of an IO error
|
* @throws IOException in case of an IO error
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(PGPException::class, IOException::class)
|
@Throws(PGPException::class, IOException::class)
|
||||||
fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = determineEncryptionInfoForMessage(message.byteInputStream())
|
fun determineEncryptionInfoForMessage(message: String): EncryptionInfo =
|
||||||
|
determineEncryptionInfoForMessage(message.byteInputStream())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
|
* Parses parts of the provided OpenPGP message in order to determine which keys were used
|
||||||
* Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves.
|
* to encrypt it. Note: This method does not rewind the passed in Stream, so you might need
|
||||||
|
* to take care of that yourselves.
|
||||||
*
|
*
|
||||||
* @param inputStream openpgp message
|
* @param inputStream openpgp message
|
||||||
* @return encryption information
|
* @return encryption information
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
* @throws IOException in case of an IO error
|
||||||
* @throws PGPException if the message is broken
|
* @throws PGPException if the message is broken
|
||||||
*/
|
*/
|
||||||
|
@ -70,13 +73,12 @@ class MessageInspector {
|
||||||
var n: Any?
|
var n: Any?
|
||||||
while (objectFactory.nextObject().also { n = it } != null) {
|
while (objectFactory.nextObject().also { n = it } != null) {
|
||||||
when (val next = n!!) {
|
when (val next = n!!) {
|
||||||
|
|
||||||
is PGPOnePassSignatureList -> {
|
is PGPOnePassSignatureList -> {
|
||||||
if (!next.isEmpty) {
|
if (!next.isEmpty) {
|
||||||
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = true)
|
return EncryptionInfo(
|
||||||
|
listOf(), isPassphraseEncrypted = false, isSignedOnly = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is PGPEncryptedDataList -> {
|
is PGPEncryptedDataList -> {
|
||||||
var isPassphraseEncrypted = false
|
var isPassphraseEncrypted = false
|
||||||
val keyIds = mutableListOf<Long>()
|
val keyIds = mutableListOf<Long>()
|
||||||
|
@ -90,13 +92,12 @@ class MessageInspector {
|
||||||
// Data is encrypted, we cannot go deeper
|
// Data is encrypted, we cannot go deeper
|
||||||
return EncryptionInfo(keyIds, isPassphraseEncrypted, false)
|
return EncryptionInfo(keyIds, isPassphraseEncrypted, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
is PGPCompressedData -> {
|
is PGPCompressedData -> {
|
||||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
objectFactory =
|
||||||
PGPUtil.getDecoderStream(next.dataStream))
|
ImplementationFactory.getInstance()
|
||||||
|
.getPGPObjectFactory(PGPUtil.getDecoderStream(next.dataStream))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
is PGPLiteralData -> {
|
is PGPLiteralData -> {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -105,4 +106,4 @@ class MessageInspector {
|
||||||
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false)
|
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
import javax.annotation.Nonnull
|
||||||
import org.bouncycastle.extensions.matches
|
import org.bouncycastle.extensions.matches
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData
|
import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
|
@ -15,116 +17,118 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||||
import org.pgpainless.key.OpenPgpFingerprint
|
import org.pgpainless.key.OpenPgpFingerprint
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
import org.pgpainless.util.SessionKey
|
import org.pgpainless.util.SessionKey
|
||||||
import java.util.*
|
|
||||||
import javax.annotation.Nonnull
|
|
||||||
|
|
||||||
/**
|
/** View for extracting metadata about a [Message]. */
|
||||||
* View for extracting metadata about a [Message].
|
class MessageMetadata(val message: Message) {
|
||||||
*/
|
|
||||||
class MessageMetadata(
|
|
||||||
val message: Message
|
|
||||||
) {
|
|
||||||
|
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
// ### Encryption ###
|
// ### Encryption
|
||||||
|
// ###
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted.
|
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
|
||||||
|
* unencrypted.
|
||||||
*/
|
*/
|
||||||
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
||||||
get() = encryptionAlgorithms.let {
|
get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
|
||||||
if (it.hasNext()) it.next() else null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message.
|
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
|
||||||
* The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item
|
* returned by the iterator is the algorithm of the outermost encrypted data packet, the next
|
||||||
* that of the next nested encrypted data packet and so on.
|
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
||||||
* The iterator might also be empty, in case of an unencrypted message.
|
* empty, in case of an unencrypted message.
|
||||||
*/
|
*/
|
||||||
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
||||||
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
||||||
|
|
||||||
val isEncrypted: Boolean
|
val isEncrypted: Boolean
|
||||||
get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
get() =
|
||||||
|
if (encryptionAlgorithm == null) false
|
||||||
|
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
||||||
|
|
||||||
fun isEncryptedFor(keys: PGPKeyRing): Boolean {
|
fun isEncryptedFor(keys: PGPKeyRing): Boolean {
|
||||||
return encryptionLayers.asSequence().any {
|
return encryptionLayers.asSequence().any {
|
||||||
it.recipients.any { keyId ->
|
it.recipients.any { keyId -> keys.getPublicKey(keyId) != null }
|
||||||
keys.getPublicKey(keyId) != null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [SessionKey] of the outermost encrypted data packet.
|
* [SessionKey] of the outermost encrypted data packet. If the message was unencrypted, this
|
||||||
* If the message was unencrypted, this method returns `null`.
|
* method returns `null`.
|
||||||
*/
|
*/
|
||||||
val sessionKey: SessionKey?
|
val sessionKey: SessionKey?
|
||||||
get() = sessionKeys.asSequence().firstOrNull()
|
get() = sessionKeys.asSequence().firstOrNull()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Iterator] of each [SessionKey] for all encrypted data packets in the message.
|
* [Iterator] of each [SessionKey] for all encrypted data packets in the message. The first item
|
||||||
* The first item returned by the iterator is the session key of the outermost encrypted data packet,
|
* returned by the iterator is the session key of the outermost encrypted data packet, the next
|
||||||
* the next item that of the next nested encrypted data packet and so on.
|
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
||||||
* The iterator might also be empty, in case of an unencrypted message.
|
* empty, in case of an unencrypted message.
|
||||||
*/
|
*/
|
||||||
val sessionKeys: Iterator<SessionKey>
|
val sessionKeys: Iterator<SessionKey>
|
||||||
get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator()
|
get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption
|
* [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption
|
||||||
* layer.
|
* layer. If the message was unencrypted or was decrypted using a passphrase, this field might
|
||||||
* If the message was unencrypted or was decrypted using a passphrase, this field might be `null`.
|
* be `null`.
|
||||||
*/
|
*/
|
||||||
val decryptionKey: SubkeyIdentifier?
|
val decryptionKey: SubkeyIdentifier?
|
||||||
get() = encryptionLayers.asSequence()
|
get() = encryptionLayers.asSequence().mapNotNull { it.decryptionKey }.firstOrNull()
|
||||||
.mapNotNull { it.decryptionKey }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
/**
|
/** List containing all recipient keyIDs. */
|
||||||
* List containing all recipient keyIDs.
|
|
||||||
*/
|
|
||||||
val recipientKeyIds: List<Long>
|
val recipientKeyIds: List<Long>
|
||||||
get() = encryptionLayers.asSequence()
|
get() =
|
||||||
|
encryptionLayers
|
||||||
|
.asSequence()
|
||||||
.map { it.recipients.toMutableList() }
|
.map { it.recipients.toMutableList() }
|
||||||
.reduce { all, keyIds -> all.addAll(keyIds); all }
|
.reduce { all, keyIds ->
|
||||||
|
all.addAll(keyIds)
|
||||||
|
all
|
||||||
|
}
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
val encryptionLayers: Iterator<EncryptedData>
|
val encryptionLayers: Iterator<EncryptedData>
|
||||||
get() = object : LayerIterator<EncryptedData>(message) {
|
get() =
|
||||||
override fun matches(layer: Packet) = layer is EncryptedData
|
object : LayerIterator<EncryptedData>(message) {
|
||||||
override fun getProperty(last: Layer) = last as EncryptedData
|
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
|
* [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message does
|
||||||
* does not contain any compressed data packets.
|
* not contain any compressed data packets.
|
||||||
*/
|
*/
|
||||||
val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull()
|
val compressionAlgorithm: CompressionAlgorithm? =
|
||||||
|
compressionAlgorithms.asSequence().firstOrNull()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Iterator] of each [CompressionAlgorithm] encountered in the message.
|
* [Iterator] of each [CompressionAlgorithm] encountered in the message. The first item returned
|
||||||
* The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next
|
* by the iterator is the algorithm of the outermost compressed data packet, the next item that
|
||||||
* item that of the next nested compressed data packet and so on.
|
* of the next nested compressed data packet and so on. The iterator might also be empty, in
|
||||||
* The iterator might also be empty, in case of a message without any compressed data packets.
|
* case of a message without any compressed data packets.
|
||||||
*/
|
*/
|
||||||
val compressionAlgorithms: Iterator<CompressionAlgorithm>
|
val compressionAlgorithms: Iterator<CompressionAlgorithm>
|
||||||
get() = compressionLayers.asSequence().map { it.algorithm }.iterator()
|
get() = compressionLayers.asSequence().map { it.algorithm }.iterator()
|
||||||
|
|
||||||
val compressionLayers: Iterator<CompressedData>
|
val compressionLayers: Iterator<CompressedData>
|
||||||
get() = object : LayerIterator<CompressedData>(message) {
|
get() =
|
||||||
override fun matches(layer: Packet) = layer is CompressedData
|
object : LayerIterator<CompressedData>(message) {
|
||||||
override fun getProperty(last: Layer) = last as CompressedData
|
override fun matches(layer: Packet) = layer is CompressedData
|
||||||
}
|
|
||||||
|
override fun getProperty(last: Layer) = last as CompressedData
|
||||||
|
}
|
||||||
|
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
// ### Signatures ###
|
// ### Signatures
|
||||||
|
// ###
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
|
|
||||||
val isUsingCleartextSignatureFramework: Boolean
|
val isUsingCleartextSignatureFramework: Boolean
|
||||||
|
@ -133,81 +137,87 @@ class MessageMetadata(
|
||||||
val verifiedSignatures: List<SignatureVerification>
|
val verifiedSignatures: List<SignatureVerification>
|
||||||
get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures)
|
get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures)
|
||||||
|
|
||||||
/**
|
/** List of all rejected signatures. */
|
||||||
* List of all rejected signatures.
|
|
||||||
*/
|
|
||||||
val rejectedSignatures: List<SignatureVerification.Failure>
|
val rejectedSignatures: List<SignatureVerification.Failure>
|
||||||
get() = mutableListOf<SignatureVerification.Failure>()
|
get() =
|
||||||
|
mutableListOf<SignatureVerification.Failure>()
|
||||||
.plus(rejectedInlineSignatures)
|
.plus(rejectedInlineSignatures)
|
||||||
.plus(rejectedDetachedSignatures)
|
.plus(rejectedDetachedSignatures)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all verified inline-signatures.
|
* List of all verified inline-signatures. This list contains all acceptable, correct signatures
|
||||||
* This list contains all acceptable, correct signatures that were part of the message itself.
|
* that were part of the message itself.
|
||||||
*/
|
*/
|
||||||
val verifiedInlineSignatures: List<SignatureVerification> = verifiedInlineSignaturesByLayer
|
val verifiedInlineSignatures: List<SignatureVerification> =
|
||||||
|
verifiedInlineSignaturesByLayer
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map { it.toMutableList() }
|
.map { it.toMutableList() }
|
||||||
.reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc }
|
.reduce { acc, signatureVerifications ->
|
||||||
|
acc.addAll(signatureVerifications)
|
||||||
|
acc
|
||||||
|
}
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Iterator] of each [List] of verified inline-signatures of the message, separated by layer.
|
* [Iterator] of each [List] of verified inline-signatures of the message, separated by layer.
|
||||||
* Since signatures might occur in different layers within a message, this method can be used to gain more detailed
|
* Since signatures might occur in different layers within a message, this method can be used to
|
||||||
* insights into what signatures were encountered at what layers of the message structure.
|
* gain more detailed insights into what signatures were encountered at what layers of the
|
||||||
* Each item of the [Iterator] represents a layer of the message and contains only signatures from
|
* message structure. Each item of the [Iterator] represents a layer of the message and contains
|
||||||
* this layer.
|
* only signatures from this layer. An empty list means no (or no acceptable) signatures were
|
||||||
* An empty list means no (or no acceptable) signatures were encountered in that layer.
|
* encountered in that layer.
|
||||||
*/
|
*/
|
||||||
val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>>
|
val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>>
|
||||||
get() = object : LayerIterator<List<SignatureVerification>>(message) {
|
get() =
|
||||||
override fun matches(layer: Packet) = layer is Layer
|
object : LayerIterator<List<SignatureVerification>>(message) {
|
||||||
|
override fun matches(layer: Packet) = layer is Layer
|
||||||
|
|
||||||
override fun getProperty(last: Layer): List<SignatureVerification> {
|
override fun getProperty(last: Layer): List<SignatureVerification> {
|
||||||
return listOf<SignatureVerification>()
|
return listOf<SignatureVerification>()
|
||||||
.plus(last.verifiedOnePassSignatures)
|
.plus(last.verifiedOnePassSignatures)
|
||||||
.plus(last.verifiedPrependedSignatures)
|
.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()
|
.asSequence()
|
||||||
.map { it.toMutableList() }
|
.map { it.toMutableList() }
|
||||||
.reduce { acc, failures -> acc.addAll(failures); acc}
|
.reduce { acc, failures ->
|
||||||
|
acc.addAll(failures)
|
||||||
|
acc
|
||||||
|
}
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures
|
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected
|
||||||
* of the message, but organized by layer.
|
* inline-signatures of the message, but organized by layer.
|
||||||
*/
|
*/
|
||||||
val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>>
|
val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>>
|
||||||
get() = object : LayerIterator<List<SignatureVerification.Failure>>(message) {
|
get() =
|
||||||
override fun matches(layer: Packet) = layer is Layer
|
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>()
|
mutableListOf<SignatureVerification.Failure>()
|
||||||
.plus(last.rejectedOnePassSignatures)
|
.plus(last.rejectedOnePassSignatures)
|
||||||
.plus(last.rejectedPrependedSignatures)
|
.plus(last.rejectedPrependedSignatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all verified detached signatures.
|
* List of all verified detached signatures. This list contains all acceptable, correct detached
|
||||||
* This list contains all acceptable, correct detached signatures.
|
* signatures.
|
||||||
*/
|
*/
|
||||||
val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures
|
val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures
|
||||||
|
|
||||||
/**
|
/** List of all rejected detached signatures. */
|
||||||
* List of all rejected detached signatures.
|
val rejectedDetachedSignatures: List<SignatureVerification.Failure> =
|
||||||
*/
|
message.rejectedDetachedSignatures
|
||||||
val rejectedDetachedSignatures: List<SignatureVerification.Failure> = message.rejectedDetachedSignatures
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True, if the message contains any (verified or rejected) signature, false if no signatures are present.
|
* True, if the message contains any (verified or rejected) signature, false if no signatures
|
||||||
|
* are present.
|
||||||
*/
|
*/
|
||||||
val hasSignature: Boolean
|
val hasSignature: Boolean
|
||||||
get() = isVerifiedSigned() || hasRejectedSignatures()
|
get() = isVerifiedSigned() || hasRejectedSignatures()
|
||||||
|
@ -217,110 +227,131 @@ class MessageMetadata(
|
||||||
fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty()
|
fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId.
|
* Return true, if the message was signed by a certificate for which we can authenticate a
|
||||||
|
* binding to the given userId.
|
||||||
*
|
*
|
||||||
* @param userId userId
|
* @param userId userId
|
||||||
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
|
* @param email if true, treat the user-id as an email address and match all userIDs containing
|
||||||
|
* this address
|
||||||
* @param certificateAuthority certificate authority
|
* @param certificateAuthority certificate authority
|
||||||
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated.
|
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify
|
||||||
* defaults to 120.
|
* as authenticated. defaults to 120.
|
||||||
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean {
|
fun isAuthenticatablySignedBy(
|
||||||
return verifiedSignatures.any { certificateAuthority
|
userId: String,
|
||||||
.authenticateBinding(it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount)
|
email: Boolean,
|
||||||
|
certificateAuthority: CertificateAuthority,
|
||||||
|
targetAmount: Int = 120
|
||||||
|
): Boolean {
|
||||||
|
return verifiedSignatures.any {
|
||||||
|
certificateAuthority
|
||||||
|
.authenticateBinding(
|
||||||
|
it.signingKey.fingerprint,
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
it.signature.creationTime,
|
||||||
|
targetAmount)
|
||||||
.authenticated
|
.authenticated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint
|
* Return rue, if the message was verifiable signed by a certificate that either has the given
|
||||||
* as primary key, or as the signing subkey.
|
* fingerprint as primary key, or as the signing subkey.
|
||||||
*
|
*
|
||||||
* @param fingerprint fingerprint
|
* @param fingerprint fingerprint
|
||||||
* @return true if message was signed by a cert identified by the given fingerprint
|
* @return true if message was signed by a cert identified by the given fingerprint
|
||||||
*/
|
*/
|
||||||
fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) =
|
fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||||
verifiedSignatures.any { it.signingKey.matches(fingerprint) }
|
verifiedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||||
|
|
||||||
fun isVerifiedSignedBy(keys: PGPKeyRing) =
|
fun isVerifiedSignedBy(keys: PGPKeyRing) =
|
||||||
verifiedSignatures.any { keys.matches(it.signingKey) }
|
verifiedSignatures.any { keys.matches(it.signingKey) }
|
||||||
|
|
||||||
fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) =
|
fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||||
verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) }
|
verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||||
|
|
||||||
fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) =
|
fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) =
|
||||||
verifiedDetachedSignatures.any { keys.matches(it.signingKey) }
|
verifiedDetachedSignatures.any { keys.matches(it.signingKey) }
|
||||||
|
|
||||||
fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) =
|
fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||||
verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) }
|
verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) }
|
||||||
|
|
||||||
fun isVerifiedInlineSignedBy(keys: PGPKeyRing) =
|
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.
|
* Value of the literal data packet's filename field. This value can be used to store a
|
||||||
* This value can be used to store a decrypted file under its original filename,
|
* decrypted file under its original filename, but since this field is not necessarily part of
|
||||||
* but since this field is not necessarily part of the signed data of a message, usage of this field is
|
* the signed data of a message, usage of this field is discouraged.
|
||||||
* discouraged.
|
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||||
|
* Packet</a>
|
||||||
*/
|
*/
|
||||||
val filename: String? = findLiteralData()?.fileName
|
val filename: String? = findLiteralData()?.fileName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True, if the sender signals an increased degree of confidentiality by setting the filename of the literal
|
* True, if the sender signals an increased degree of confidentiality by setting the filename of
|
||||||
* data packet to a special value that indicates that the data is intended for your eyes only.
|
* the literal data packet to a special value that indicates that the data is intended for your
|
||||||
|
* eyes only.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Reliance on this signaling mechanism is discouraged.")
|
@Deprecated("Reliance on this signaling mechanism is discouraged.")
|
||||||
val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename
|
val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value of the literal data packets modification date field.
|
* Value of the literal data packets modification date field. This value can be used to restore
|
||||||
* This value can be used to restore the modification date of a decrypted file,
|
* the modification date of a decrypted file, but since this field is not necessarily part of
|
||||||
* but since this field is not necessarily part of the signed data, its use is discouraged.
|
* the signed data, its use is discouraged.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||||
|
* Packet</a>
|
||||||
*/
|
*/
|
||||||
val modificationDate: Date? = findLiteralData()?.modificationDate
|
val modificationDate: Date? = findLiteralData()?.modificationDate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value of the format field of the literal data packet.
|
* Value of the format field of the literal data packet. This value indicates what format (text,
|
||||||
* This value indicates what format (text, binary data, ...) the data has.
|
* binary data, ...) the data has. Since this field is not necessarily part of the signed data
|
||||||
* Since this field is not necessarily part of the signed data of a message, its usage is discouraged.
|
* of a message, its usage is discouraged.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||||
|
* Packet</a>
|
||||||
*/
|
*/
|
||||||
val literalDataEncoding: StreamEncoding? = findLiteralData()?.format
|
val literalDataEncoding: StreamEncoding? = findLiteralData()?.format
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the [LiteralData] layer of an OpenPGP message.
|
* Find the [LiteralData] layer of an OpenPGP message. This method might return null, for
|
||||||
* This method might return null, for example for a cleartext signed message without OpenPGP packets.
|
* example for a cleartext signed message without OpenPGP packets.
|
||||||
*
|
*
|
||||||
* @return literal data
|
* @return literal data
|
||||||
*/
|
*/
|
||||||
private fun findLiteralData(): LiteralData? {
|
private fun findLiteralData(): LiteralData? {
|
||||||
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message,
|
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed
|
||||||
|
// message,
|
||||||
// we might not have a Literal Data packet.
|
// we might not have a Literal Data packet.
|
||||||
var nested = message.child ?: return null
|
var nested = message.child ?: return null
|
||||||
|
|
||||||
while (nested.hasNestedChild()) {
|
while (nested.hasNestedChild()) {
|
||||||
val layer = nested as Layer
|
val layer = nested as Layer
|
||||||
nested = checkNotNull(layer.child) {
|
nested =
|
||||||
// Otherwise, we MUST find a Literal Data packet, or else the message is malformed
|
checkNotNull(layer.child) {
|
||||||
"Malformed OpenPGP message. Cannot find Literal Data Packet"
|
// 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
|
return nested as LiteralData
|
||||||
}
|
}
|
||||||
|
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
// ### Message Structure ###
|
// ### Message Structure
|
||||||
|
// ###
|
||||||
// ################################################################################################################
|
// ################################################################################################################
|
||||||
|
|
||||||
interface Packet
|
interface Packet
|
||||||
|
@ -329,13 +360,12 @@ class MessageMetadata(
|
||||||
fun hasNestedChild(): Boolean
|
fun hasNestedChild(): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Layer(
|
abstract class Layer(val depth: Int) : Packet {
|
||||||
val depth: Int
|
|
||||||
) : Packet {
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (depth > MAX_LAYER_DEPTH) {
|
if (depth > MAX_LAYER_DEPTH) {
|
||||||
throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
|
throw MalformedOpenPgpMessageException(
|
||||||
|
"Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,9 +377,8 @@ class MessageMetadata(
|
||||||
val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nested child element of this layer.
|
* Nested child element of this layer. Might be `null`, if this layer does not have a child
|
||||||
* Might be `null`, if this layer does not have a child element
|
* element (e.g. if this is a [LiteralData] packet).
|
||||||
* (e.g. if this is a [LiteralData] packet).
|
|
||||||
*/
|
*/
|
||||||
var child: Nested? = null
|
var child: Nested? = null
|
||||||
|
|
||||||
|
@ -386,8 +415,8 @@ class MessageMetadata(
|
||||||
* Outermost OpenPGP Message structure.
|
* Outermost OpenPGP Message structure.
|
||||||
*
|
*
|
||||||
* @param cleartextSigned whether the message is using the Cleartext Signature Framework
|
* @param cleartextSigned whether the message is using the Cleartext Signature Framework
|
||||||
*
|
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext
|
||||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a>
|
* Signature Framework</a>
|
||||||
*/
|
*/
|
||||||
class Message(var cleartextSigned: Boolean = false) : Layer(0) {
|
class Message(var cleartextSigned: Boolean = false) : Layer(0) {
|
||||||
fun setCleartextSigned() = apply { cleartextSigned = true }
|
fun setCleartextSigned() = apply { cleartextSigned = true }
|
||||||
|
@ -397,14 +426,14 @@ class MessageMetadata(
|
||||||
* Literal Data Packet.
|
* Literal Data Packet.
|
||||||
*
|
*
|
||||||
* @param fileName value of the filename field. An empty String represents no filename.
|
* @param fileName value of the filename field. An empty String represents no filename.
|
||||||
* @param modificationDate value of the modification date field. The special value `Date(0)` indicates no
|
* @param modificationDate value of the modification date field. The special value `Date(0)`
|
||||||
* modification date.
|
* indicates no modification date.
|
||||||
* @param format value of the format field.
|
* @param format value of the format field.
|
||||||
*/
|
*/
|
||||||
class LiteralData(
|
class LiteralData(
|
||||||
val fileName: String = "",
|
val fileName: String = "",
|
||||||
val modificationDate: Date = Date(0L),
|
val modificationDate: Date = Date(0L),
|
||||||
val format: StreamEncoding = StreamEncoding.BINARY
|
val format: StreamEncoding = StreamEncoding.BINARY
|
||||||
) : Nested {
|
) : Nested {
|
||||||
|
|
||||||
// A literal data packet MUST NOT have a child element, as its content is the plaintext
|
// 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 algorithm [CompressionAlgorithm] used to compress the packet.
|
||||||
* @param depth nesting depth at which this packet was encountered.
|
* @param depth nesting depth at which this packet was encountered.
|
||||||
*/
|
*/
|
||||||
class CompressedData(
|
class CompressedData(val algorithm: CompressionAlgorithm, depth: Int) : Layer(depth), Nested {
|
||||||
val algorithm: CompressionAlgorithm,
|
|
||||||
depth: Int) : Layer(depth), Nested {
|
|
||||||
|
|
||||||
// A compressed data packet MUST have a child element
|
// A compressed data packet MUST have a child element
|
||||||
override fun hasNestedChild() = true
|
override fun hasNestedChild() = true
|
||||||
|
@ -431,38 +458,30 @@ class MessageMetadata(
|
||||||
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
||||||
* @param depth nesting depth at which this packet was encountered.
|
* @param depth nesting depth at which this packet was encountered.
|
||||||
*/
|
*/
|
||||||
class EncryptedData(
|
class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested {
|
||||||
val algorithm: SymmetricKeyAlgorithm,
|
|
||||||
depth: Int
|
|
||||||
) : Layer(depth), Nested {
|
|
||||||
|
|
||||||
/**
|
/** [SessionKey] used to decrypt the packet. */
|
||||||
* [SessionKey] used to decrypt the packet.
|
|
||||||
*/
|
|
||||||
var sessionKey: SessionKey? = null
|
var sessionKey: SessionKey? = null
|
||||||
|
|
||||||
/**
|
/** List of all recipient key ids to which the packet was encrypted for. */
|
||||||
* List of all recipient key ids to which the packet was encrypted for.
|
|
||||||
*/
|
|
||||||
val recipients: List<Long> = mutableListOf()
|
val recipients: List<Long> = mutableListOf()
|
||||||
|
|
||||||
fun addRecipients(keyIds: List<Long>) = apply {
|
fun addRecipients(keyIds: List<Long>) = apply { (recipients as MutableList).addAll(keyIds) }
|
||||||
(recipients as MutableList).addAll(keyIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet).
|
* Identifier of the subkey that was used to decrypt the packet (in case of a public key
|
||||||
|
* encrypted packet).
|
||||||
*/
|
*/
|
||||||
var decryptionKey: SubkeyIdentifier? = null
|
var decryptionKey: SubkeyIdentifier? = null
|
||||||
|
|
||||||
// An encrypted data packet MUST have a child element
|
// An encrypted data packet MUST have a child element
|
||||||
override fun hasNestedChild() = true
|
override fun hasNestedChild() = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of
|
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the
|
||||||
* a transformation ([getProperty]) on those packets that match ([matches]) a given criterion.
|
* results of a transformation ([getProperty]) on those packets that match ([matches]) a given
|
||||||
|
* criterion.
|
||||||
*
|
*
|
||||||
* @param message outermost structure object
|
* @param message outermost structure object
|
||||||
*/
|
*/
|
||||||
|
@ -519,6 +538,7 @@ class MessageMetadata(
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun matches(layer: Packet): Boolean
|
abstract fun matches(layer: Packet): Boolean
|
||||||
|
|
||||||
abstract fun getProperty(last: Layer): O
|
abstract fun getProperty(last: Layer): O
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,18 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
/**
|
/** Strategy defining how missing secret key passphrases are handled. */
|
||||||
* Strategy defining how missing secret key passphrases are handled.
|
|
||||||
*/
|
|
||||||
enum class MissingKeyPassphraseStrategy {
|
enum class MissingKeyPassphraseStrategy {
|
||||||
/**
|
/**
|
||||||
* Try to interactively obtain key passphrases one-by-one via callbacks,
|
* Try to interactively obtain key passphrases one-by-one via callbacks, eg
|
||||||
* eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
|
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
|
||||||
*/
|
*/
|
||||||
INTERACTIVE,
|
INTERACTIVE,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not try to obtain passphrases interactively and instead throw a
|
* Do not try to obtain passphrases interactively and instead throw a
|
||||||
* [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing passphrases.
|
* [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing
|
||||||
|
* passphrases.
|
||||||
*/
|
*/
|
||||||
THROW_EXCEPTION
|
THROW_EXCEPTION
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,20 +9,19 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
fun interface MissingPublicKeyCallback {
|
fun interface MissingPublicKeyCallback {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method gets called if we encounter a signature made by a key which was not provided for signature verification.
|
* This method gets called if we encounter a signature made by a key which was not provided for
|
||||||
* If you cannot provide the requested key, it is safe to return null here.
|
* signature verification. If you cannot provide the requested key, it is safe to return null
|
||||||
* PGPainless will then continue verification with the next signature.
|
* here. PGPainless will then continue verification with the next signature.
|
||||||
*
|
*
|
||||||
* Note: The key-id might belong to a subkey, so be aware that when looking up the [PGPPublicKeyRing],
|
* Note: The key-id might belong to a subkey, so be aware that when looking up the
|
||||||
* you may not only search for the key-id on the key rings primary key!
|
* [PGPPublicKeyRing], you may not only search for the key-id on the key rings primary key!
|
||||||
*
|
*
|
||||||
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures
|
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately
|
||||||
* only contain the key id.
|
* one-pass-signatures only contain the key id.
|
||||||
*
|
*
|
||||||
* @param keyId ID of the missing signing (sub)key
|
* @param keyId ID of the missing signing (sub)key
|
||||||
* @return keyring containing the key or null
|
* @return keyring containing the key or null
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
|
||||||
*/
|
*/
|
||||||
fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing?
|
fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing?
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import openpgp.openPgpKeyId
|
import openpgp.openPgpKeyId
|
||||||
import org.bouncycastle.bcpg.BCPGInputStream
|
import org.bouncycastle.bcpg.BCPGInputStream
|
||||||
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
|
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
|
||||||
|
@ -34,16 +37,14 @@ import org.pgpainless.signature.consumer.SignatureValidator
|
||||||
import org.pgpainless.util.ArmoredInputStreamFactory
|
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||||
import org.pgpainless.util.SessionKey
|
import org.pgpainless.util.SessionKey
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
class OpenPgpMessageInputStream(
|
class OpenPgpMessageInputStream(
|
||||||
type: Type,
|
type: Type,
|
||||||
inputStream: InputStream,
|
inputStream: InputStream,
|
||||||
private val options: ConsumerOptions,
|
private val options: ConsumerOptions,
|
||||||
private val layerMetadata: Layer,
|
private val layerMetadata: Layer,
|
||||||
private val policy: Policy) : DecryptionStream() {
|
private val policy: Policy
|
||||||
|
) : DecryptionStream() {
|
||||||
|
|
||||||
private val signatures: Signatures = Signatures(options)
|
private val signatures: Signatures = Signatures(options)
|
||||||
private var packetInputStream: TeeBCPGInputStream? = null
|
private var packetInputStream: TeeBCPGInputStream? = null
|
||||||
|
@ -58,19 +59,20 @@ class OpenPgpMessageInputStream(
|
||||||
signatures.addDetachedSignatures(options.getDetachedSignatures())
|
signatures.addDetachedSignatures(options.getDetachedSignatures())
|
||||||
}
|
}
|
||||||
|
|
||||||
when(type) {
|
when (type) {
|
||||||
Type.standard -> {
|
Type.standard -> {
|
||||||
|
|
||||||
// tee out packet bytes for signature verification
|
// tee out packet bytes for signature verification
|
||||||
packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures)
|
packetInputStream =
|
||||||
|
TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures)
|
||||||
|
|
||||||
// *omnomnom*
|
// *omnomnom*
|
||||||
consumePackets()
|
consumePackets()
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.cleartext_signed -> {
|
Type.cleartext_signed -> {
|
||||||
val multiPassStrategy = options.getMultiPassStrategy()
|
val multiPassStrategy = options.getMultiPassStrategy()
|
||||||
val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
val detachedSignatures =
|
||||||
|
ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
||||||
inputStream, multiPassStrategy.messageOutputStream)
|
inputStream, multiPassStrategy.messageOutputStream)
|
||||||
|
|
||||||
for (signature in detachedSignatures) {
|
for (signature in detachedSignatures) {
|
||||||
|
@ -78,9 +80,9 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
options.isForceNonOpenPgpData()
|
options.isForceNonOpenPgpData()
|
||||||
nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
nestedInputStream =
|
||||||
|
TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.non_openpgp -> {
|
Type.non_openpgp -> {
|
||||||
packetInputStream = null
|
packetInputStream = null
|
||||||
nestedInputStream = TeeInputStream(inputStream, this.signatures)
|
nestedInputStream = TeeInputStream(inputStream, this.signatures)
|
||||||
|
@ -89,11 +91,17 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
standard, cleartext_signed, non_openpgp
|
standard,
|
||||||
|
cleartext_signed,
|
||||||
|
non_openpgp
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy):
|
constructor(
|
||||||
this(Type.standard, inputStream, options, metadata, policy)
|
inputStream: InputStream,
|
||||||
|
options: ConsumerOptions,
|
||||||
|
metadata: Layer,
|
||||||
|
policy: Policy
|
||||||
|
) : this(Type.standard, inputStream, options, metadata, policy)
|
||||||
|
|
||||||
private fun consumePackets() {
|
private fun consumePackets() {
|
||||||
val pIn = packetInputStream ?: return
|
val pIn = packetInputStream ?: return
|
||||||
|
@ -102,50 +110,53 @@ class OpenPgpMessageInputStream(
|
||||||
|
|
||||||
// Comsume packets, potentially stepping into nested layers
|
// Comsume packets, potentially stepping into nested layers
|
||||||
layer@ while (run {
|
layer@ while (run {
|
||||||
packet = pIn.nextPacketTag()
|
packet = pIn.nextPacketTag()
|
||||||
packet
|
packet
|
||||||
} != null) {
|
} != null) {
|
||||||
|
|
||||||
signatures.nextPacket(packet!!)
|
signatures.nextPacket(packet!!)
|
||||||
// Consume packets in a layer
|
// Consume packets in a layer
|
||||||
when(packet) {
|
when (packet) {
|
||||||
|
|
||||||
OpenPgpPacket.LIT -> {
|
OpenPgpPacket.LIT -> {
|
||||||
processLiteralData()
|
processLiteralData()
|
||||||
break@layer // nest down
|
break@layer // nest down
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenPgpPacket.COMP -> {
|
OpenPgpPacket.COMP -> {
|
||||||
processCompressedData()
|
processCompressedData()
|
||||||
break@layer // nest down
|
break@layer // nest down
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenPgpPacket.OPS -> {
|
OpenPgpPacket.OPS -> {
|
||||||
processOnePassSignature() // OPS is on the same layer, no nest down
|
processOnePassSignature() // OPS is on the same layer, no nest down
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenPgpPacket.SIG -> {
|
OpenPgpPacket.SIG -> {
|
||||||
processSignature() // SIG is on the same layer, no nest down
|
processSignature() // SIG is on the same layer, no nest down
|
||||||
}
|
}
|
||||||
|
OpenPgpPacket.PKESK,
|
||||||
OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> {
|
OpenPgpPacket.SKESK,
|
||||||
|
OpenPgpPacket.SED,
|
||||||
|
OpenPgpPacket.SEIPD -> {
|
||||||
if (processEncryptedData()) {
|
if (processEncryptedData()) {
|
||||||
break@layer
|
break@layer
|
||||||
}
|
}
|
||||||
throw MissingDecryptionMethodException("No working decryption method found.")
|
throw MissingDecryptionMethodException("No working decryption method found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenPgpPacket.MARKER -> {
|
OpenPgpPacket.MARKER -> {
|
||||||
LOGGER.debug("Skipping Marker Packet")
|
LOGGER.debug("Skipping Marker Packet")
|
||||||
pIn.readMarker()
|
pIn.readMarker()
|
||||||
}
|
}
|
||||||
|
OpenPgpPacket.SK,
|
||||||
OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR ->
|
OpenPgpPacket.PK,
|
||||||
|
OpenPgpPacket.SSK,
|
||||||
|
OpenPgpPacket.PSK,
|
||||||
|
OpenPgpPacket.TRUST,
|
||||||
|
OpenPgpPacket.UID,
|
||||||
|
OpenPgpPacket.UATTR ->
|
||||||
throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet")
|
throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet")
|
||||||
|
OpenPgpPacket.EXP_1,
|
||||||
OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 ->
|
OpenPgpPacket.EXP_2,
|
||||||
|
OpenPgpPacket.EXP_3,
|
||||||
|
OpenPgpPacket.EXP_4 ->
|
||||||
throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet")
|
throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet")
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet")
|
throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet")
|
||||||
}
|
}
|
||||||
|
@ -158,8 +169,10 @@ class OpenPgpMessageInputStream(
|
||||||
val literalData = packetInputStream!!.readLiteralData()
|
val literalData = packetInputStream!!.readLiteralData()
|
||||||
|
|
||||||
// Extract Metadata
|
// Extract Metadata
|
||||||
layerMetadata.child = LiteralData(
|
layerMetadata.child =
|
||||||
literalData.fileName, literalData.modificationTime,
|
LiteralData(
|
||||||
|
literalData.fileName,
|
||||||
|
literalData.modificationTime,
|
||||||
StreamEncoding.requireFromCode(literalData.format))
|
StreamEncoding.requireFromCode(literalData.format))
|
||||||
|
|
||||||
nestedInputStream = literalData.inputStream
|
nestedInputStream = literalData.inputStream
|
||||||
|
@ -171,18 +184,22 @@ class OpenPgpMessageInputStream(
|
||||||
val compressedData = packetInputStream!!.readCompressedData()
|
val compressedData = packetInputStream!!.readCompressedData()
|
||||||
|
|
||||||
// Extract Metadata
|
// Extract Metadata
|
||||||
val compressionLayer = CompressedData(
|
val compressionLayer =
|
||||||
|
CompressedData(
|
||||||
CompressionAlgorithm.requireFromId(compressedData.algorithm),
|
CompressionAlgorithm.requireFromId(compressedData.algorithm),
|
||||||
layerMetadata.depth + 1)
|
layerMetadata.depth + 1)
|
||||||
|
|
||||||
LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.")
|
LOGGER.debug(
|
||||||
nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy)
|
"Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.")
|
||||||
|
nestedInputStream =
|
||||||
|
OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processOnePassSignature() {
|
private fun processOnePassSignature() {
|
||||||
syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE)
|
syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE)
|
||||||
val ops = packetInputStream!!.readOnePassSignature()
|
val ops = packetInputStream!!.readOnePassSignature()
|
||||||
LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
LOGGER.debug(
|
||||||
|
"One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||||
signatures.addOnePassSignature(ops)
|
signatures.addOnePassSignature(ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,26 +207,33 @@ class OpenPgpMessageInputStream(
|
||||||
// true if signature corresponds to OPS
|
// true if signature corresponds to OPS
|
||||||
val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS
|
val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS
|
||||||
syntaxVerifier.next(InputSymbol.SIGNATURE)
|
syntaxVerifier.next(InputSymbol.SIGNATURE)
|
||||||
val signature = try {
|
val signature =
|
||||||
packetInputStream!!.readSignature()
|
try {
|
||||||
} catch (e : UnsupportedPacketVersionException) {
|
packetInputStream!!.readSignature()
|
||||||
LOGGER.debug("Unsupported Signature at depth ${layerMetadata.depth} encountered.", e)
|
} catch (e: UnsupportedPacketVersionException) {
|
||||||
return
|
LOGGER.debug(
|
||||||
}
|
"Unsupported Signature at depth ${layerMetadata.depth} encountered.", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val keyId = signature.issuerKeyId
|
val keyId = signature.issuerKeyId
|
||||||
if (isSigForOps) {
|
if (isSigForOps) {
|
||||||
LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
LOGGER.debug(
|
||||||
signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with
|
"Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||||
|
signatures
|
||||||
|
.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are
|
||||||
|
// dealt with
|
||||||
signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy)
|
signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy)
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
LOGGER.debug(
|
||||||
|
"Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||||
signatures.addPrependedSignature(signature)
|
signatures.addPrependedSignature(signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processEncryptedData(): Boolean {
|
private fun processEncryptedData(): Boolean {
|
||||||
LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
|
LOGGER.debug(
|
||||||
|
"Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
|
||||||
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
|
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
|
||||||
val encDataList = packetInputStream!!.readEncryptedDataList()
|
val encDataList = packetInputStream!!.readEncryptedDataList()
|
||||||
if (!encDataList.isIntegrityProtected) {
|
if (!encDataList.isIntegrityProtected) {
|
||||||
|
@ -220,22 +244,25 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
val esks = SortedESKs(encDataList)
|
val esks = SortedESKs(encDataList)
|
||||||
LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
|
LOGGER.debug(
|
||||||
|
"Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
|
||||||
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
|
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
|
||||||
" have an anonymous recipient.")
|
" have an anonymous recipient.")
|
||||||
|
|
||||||
// try custom decryptor factories
|
// try custom decryptor factories
|
||||||
for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) {
|
for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) {
|
||||||
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
|
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
|
||||||
esks.pkesks.filter {
|
esks.pkesks
|
||||||
// find matching PKESK
|
.filter {
|
||||||
it.keyID == key.subkeyId
|
// find matching PKESK
|
||||||
}.forEach {
|
it.keyID == key.subkeyId
|
||||||
// attempt decryption
|
}
|
||||||
if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) {
|
.forEach {
|
||||||
return true
|
// attempt decryption
|
||||||
|
if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// try provided session key
|
// try provided session key
|
||||||
|
@ -244,20 +271,24 @@ class OpenPgpMessageInputStream(
|
||||||
LOGGER.debug("Attempt decryption with provided session key.")
|
LOGGER.debug("Attempt decryption with provided session key.")
|
||||||
throwIfUnacceptable(sk.algorithm)
|
throwIfUnacceptable(sk.algorithm)
|
||||||
|
|
||||||
val decryptorFactory = ImplementationFactory.getInstance()
|
val decryptorFactory =
|
||||||
.getSessionKeyDataDecryptorFactory(sk)
|
ImplementationFactory.getInstance().getSessionKeyDataDecryptorFactory(sk)
|
||||||
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1)
|
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1)
|
||||||
val skEncData = encDataList.extractSessionKeyEncryptedData()
|
val skEncData = encDataList.extractSessionKeyEncryptedData()
|
||||||
try {
|
try {
|
||||||
val decrypted = skEncData.getDataStream(decryptorFactory)
|
val decrypted = skEncData.getDataStream(decryptorFactory)
|
||||||
layer.sessionKey = sk
|
layer.sessionKey = sk
|
||||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options)
|
val integrityProtected =
|
||||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy)
|
IntegrityProtectedInputStream(decrypted, skEncData, options)
|
||||||
|
nestedInputStream =
|
||||||
|
OpenPgpMessageInputStream(integrityProtected, options, layer, policy)
|
||||||
LOGGER.debug("Successfully decrypted data using provided session key")
|
LOGGER.debug("Successfully decrypted data using provided session key")
|
||||||
return true
|
return true
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
// Session key mismatch?
|
// Session key mismatch?
|
||||||
LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e)
|
LOGGER.debug(
|
||||||
|
"Decryption using provided session key failed. Mismatched session key and message?",
|
||||||
|
e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,19 +298,21 @@ class OpenPgpMessageInputStream(
|
||||||
LOGGER.debug("Attempt decryption with provided passphrase")
|
LOGGER.debug("Attempt decryption with provided passphrase")
|
||||||
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
|
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
|
||||||
if (!isAcceptable(algorithm)) {
|
if (!isAcceptable(algorithm)) {
|
||||||
LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm")
|
LOGGER.debug(
|
||||||
|
"Skipping SKESK with unacceptable encapsulation algorithm $algorithm")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val decryptorFactory = ImplementationFactory.getInstance()
|
val decryptorFactory =
|
||||||
.getPBEDataDecryptorFactory(passphrase)
|
ImplementationFactory.getInstance().getPBEDataDecryptorFactory(passphrase)
|
||||||
if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) {
|
if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val postponedDueToMissingPassphrase = mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
|
val postponedDueToMissingPassphrase =
|
||||||
|
mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
|
||||||
|
|
||||||
// try (known) secret keys
|
// try (known) secret keys
|
||||||
esks.pkesks.forEach { pkesk ->
|
esks.pkesks.forEach { pkesk ->
|
||||||
|
@ -295,7 +328,8 @@ class OpenPgpMessageInputStream(
|
||||||
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
|
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
|
||||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||||
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
||||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
LOGGER.debug(
|
||||||
|
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||||
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -319,7 +353,8 @@ class OpenPgpMessageInputStream(
|
||||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||||
|
|
||||||
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
||||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
LOGGER.debug(
|
||||||
|
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||||
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -331,15 +366,14 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
if (options.getMissingKeyPassphraseStrategy() ==
|
||||||
|
MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||||
// Non-interactive mode: Throw an exception with all locked decryption keys
|
// Non-interactive mode: Throw an exception with all locked decryption keys
|
||||||
postponedDueToMissingPassphrase.map {
|
postponedDueToMissingPassphrase
|
||||||
SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID)
|
.map { SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) }
|
||||||
}.also {
|
.also { if (it.isNotEmpty()) throw MissingPassphraseException(it.toSet()) }
|
||||||
if (it.isNotEmpty())
|
} else if (options.getMissingKeyPassphraseStrategy() ==
|
||||||
throw MissingPassphraseException(it.toSet())
|
MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||||
}
|
|
||||||
} else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
|
||||||
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
|
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
|
||||||
val keyId = secretKey.keyID
|
val keyId = secretKey.keyID
|
||||||
val decryptionKeys = getDecryptionKey(pkesk)!!
|
val decryptionKeys = getDecryptionKey(pkesk)!!
|
||||||
|
@ -348,7 +382,8 @@ class OpenPgpMessageInputStream(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.")
|
LOGGER.debug(
|
||||||
|
"Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.")
|
||||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||||
val privateKey = secretKey.unlock(protector)
|
val privateKey = secretKey.unlock(protector)
|
||||||
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
|
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
|
||||||
|
@ -364,29 +399,37 @@ class OpenPgpMessageInputStream(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptWithPrivateKey(esks: SortedESKs,
|
private fun decryptWithPrivateKey(
|
||||||
privateKey: PGPPrivateKey,
|
esks: SortedESKs,
|
||||||
decryptionKeyId: SubkeyIdentifier,
|
privateKey: PGPPrivateKey,
|
||||||
pkesk: PGPPublicKeyEncryptedData): Boolean {
|
decryptionKeyId: SubkeyIdentifier,
|
||||||
val decryptorFactory = ImplementationFactory.getInstance()
|
pkesk: PGPPublicKeyEncryptedData
|
||||||
.getPublicKeyDataDecryptorFactory(privateKey)
|
): Boolean {
|
||||||
|
val decryptorFactory =
|
||||||
|
ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey)
|
||||||
return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk)
|
return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean {
|
private fun hasUnsupportedS2KSpecifier(
|
||||||
|
secretKey: PGPSecretKey,
|
||||||
|
decryptionKeyId: SubkeyIdentifier
|
||||||
|
): Boolean {
|
||||||
val s2k = secretKey.s2K
|
val s2k = secretKey.s2K
|
||||||
if (s2k != null) {
|
if (s2k != null) {
|
||||||
if (s2k.type in 100..110) {
|
if (s2k.type in 100..110) {
|
||||||
LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
|
LOGGER.debug(
|
||||||
|
"Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptSKESKAndStream(esks: SortedESKs,
|
private fun decryptSKESKAndStream(
|
||||||
skesk: PGPPBEEncryptedData,
|
esks: SortedESKs,
|
||||||
decryptorFactory: PBEDataDecryptorFactory): Boolean {
|
skesk: PGPPBEEncryptedData,
|
||||||
|
decryptorFactory: PBEDataDecryptorFactory
|
||||||
|
): Boolean {
|
||||||
try {
|
try {
|
||||||
val decrypted = skesk.getDataStream(decryptorFactory)
|
val decrypted = skesk.getDataStream(decryptorFactory)
|
||||||
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
|
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
|
||||||
|
@ -396,38 +439,45 @@ class OpenPgpMessageInputStream(
|
||||||
encryptedData.addRecipients(esks.pkesks.map { it.keyID })
|
encryptedData.addRecipients(esks.pkesks.map { it.keyID })
|
||||||
LOGGER.debug("Successfully decrypted data with passphrase")
|
LOGGER.debug("Successfully decrypted data with passphrase")
|
||||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
|
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
|
||||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
nestedInputStream =
|
||||||
|
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||||
return true
|
return true
|
||||||
} catch (e : UnacceptableAlgorithmException) {
|
} catch (e: UnacceptableAlgorithmException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e)
|
LOGGER.debug(
|
||||||
|
"Decryption of encrypted data packet using password failed. Password mismatch?", e)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptPKESKAndStream(esks: SortedESKs,
|
private fun decryptPKESKAndStream(
|
||||||
decryptionKeyId: SubkeyIdentifier,
|
esks: SortedESKs,
|
||||||
decryptorFactory: PublicKeyDataDecryptorFactory,
|
decryptionKeyId: SubkeyIdentifier,
|
||||||
pkesk: PGPPublicKeyEncryptedData): Boolean {
|
decryptorFactory: PublicKeyDataDecryptorFactory,
|
||||||
|
pkesk: PGPPublicKeyEncryptedData
|
||||||
|
): Boolean {
|
||||||
try {
|
try {
|
||||||
val decrypted = pkesk.getDataStream(decryptorFactory)
|
val decrypted = pkesk.getDataStream(decryptorFactory)
|
||||||
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
|
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
|
||||||
throwIfUnacceptable(sessionKey.algorithm)
|
throwIfUnacceptable(sessionKey.algorithm)
|
||||||
|
|
||||||
val encryptedData = EncryptedData(
|
val encryptedData =
|
||||||
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
EncryptedData(
|
||||||
|
SymmetricKeyAlgorithm.requireFromId(
|
||||||
|
pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
||||||
layerMetadata.depth + 1)
|
layerMetadata.depth + 1)
|
||||||
encryptedData.decryptionKey = decryptionKeyId
|
encryptedData.decryptionKey = decryptionKeyId
|
||||||
encryptedData.sessionKey = sessionKey
|
encryptedData.sessionKey = sessionKey
|
||||||
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID })
|
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID })
|
||||||
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
|
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
|
||||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
|
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
|
||||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
nestedInputStream =
|
||||||
|
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||||
return true
|
return true
|
||||||
} catch (e : UnacceptableAlgorithmException) {
|
} catch (e: UnacceptableAlgorithmException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e)
|
LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -441,11 +491,12 @@ class OpenPgpMessageInputStream(
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
val r: Int = try {
|
val r: Int =
|
||||||
nestedInputStream!!.read()
|
try {
|
||||||
} catch (e: IOException) {
|
nestedInputStream!!.read()
|
||||||
-1
|
} catch (e: IOException) {
|
||||||
}
|
-1
|
||||||
|
}
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
signatures.updateLiteral(r.toByte())
|
signatures.updateLiteral(r.toByte())
|
||||||
} else {
|
} else {
|
||||||
|
@ -532,34 +583,37 @@ class OpenPgpMessageInputStream(
|
||||||
return MessageMetadata((layerMetadata as Message))
|
return MessageMetadata((layerMetadata as Message))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull {
|
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? =
|
||||||
it.any {
|
options.getDecryptionKeys().firstOrNull {
|
||||||
k -> k.keyID == keyId
|
it.any { k -> k.keyID == keyId }
|
||||||
}.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any {
|
.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k -> k.keyID == keyId })
|
||||||
k -> k.keyID == keyId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull {
|
|
||||||
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 getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> =
|
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? =
|
||||||
options.getDecryptionKeys().filter {
|
options.getDecryptionKeys().firstOrNull {
|
||||||
it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
it.getSecretKeyFor(pkesk) != null &&
|
||||||
|
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
||||||
when (pkesk.version) {
|
when (pkesk.version) {
|
||||||
3 -> pkesk.keyID == subkey.keyID
|
3 -> pkesk.keyID == subkey.keyID
|
||||||
else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
|
else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 algorithm = pkesk.algorithm
|
||||||
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
||||||
options.getDecryptionKeys().forEach {
|
options.getDecryptionKeys().forEach {
|
||||||
|
@ -574,11 +628,12 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean =
|
private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean =
|
||||||
policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm)
|
policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm)
|
||||||
|
|
||||||
private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) {
|
private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) {
|
||||||
if (!isAcceptable(algorithm)) {
|
if (!isAcceptable(algorithm)) {
|
||||||
throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.")
|
throw UnacceptableAlgorithmException(
|
||||||
|
"Symmetric-Key algorithm $algorithm is not acceptable for message decryption.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,9 +665,7 @@ class OpenPgpMessageInputStream(
|
||||||
get() = skesks.plus(pkesks).plus(anonPkesks)
|
get() = skesks.plus(pkesks).plus(anonPkesks)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Signatures(
|
private class Signatures(val options: ConsumerOptions) : OutputStream() {
|
||||||
val options: ConsumerOptions
|
|
||||||
) : OutputStream() {
|
|
||||||
val detachedSignatures = mutableListOf<SignatureCheck>()
|
val detachedSignatures = mutableListOf<SignatureCheck>()
|
||||||
val prependedSignatures = mutableListOf<SignatureCheck>()
|
val prependedSignatures = mutableListOf<SignatureCheck>()
|
||||||
val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
|
val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
|
||||||
|
@ -636,8 +689,10 @@ class OpenPgpMessageInputStream(
|
||||||
if (check != null) {
|
if (check != null) {
|
||||||
detachedSignatures.add(check)
|
detachedSignatures.add(check)
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug(
|
||||||
detachedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
|
detachedSignaturesWithMissingCert.add(
|
||||||
|
SignatureVerification.Failure(
|
||||||
signature, null, SignatureValidationException("Missing verification key.")))
|
signature, null, SignatureValidationException("Missing verification key.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -648,10 +703,11 @@ class OpenPgpMessageInputStream(
|
||||||
if (check != null) {
|
if (check != null) {
|
||||||
prependedSignatures.add(check)
|
prependedSignatures.add(check)
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug(
|
||||||
prependedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
signature, null, SignatureValidationException("Missing verification key")
|
prependedSignaturesWithMissingCert.add(
|
||||||
))
|
SignatureVerification.Failure(
|
||||||
|
signature, null, SignatureValidationException("Missing verification key")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,7 +736,11 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) {
|
fun addCorrespondingOnePassSignature(
|
||||||
|
signature: PGPSignature,
|
||||||
|
layer: Layer,
|
||||||
|
policy: Policy
|
||||||
|
) {
|
||||||
var found = false
|
var found = false
|
||||||
val keyId = signature.issuerKeyId
|
val keyId = signature.issuerKeyId
|
||||||
for ((i, check) in onePassSignatures.withIndex().reversed()) {
|
for ((i, check) in onePassSignatures.withIndex().reversed()) {
|
||||||
|
@ -694,27 +754,32 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
check.signature = signature
|
check.signature = signature
|
||||||
val verification = SignatureVerification(signature,
|
val verification =
|
||||||
|
SignatureVerification(
|
||||||
|
signature,
|
||||||
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
|
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
SignatureValidator.signatureWasCreatedInBounds(
|
||||||
.verify(signature)
|
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
|
.verify(signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
|
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
|
||||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||||
layer.addVerifiedOnePassSignature(verification)
|
layer.addVerifiedOnePassSignature(verification)
|
||||||
} catch (e: SignatureValidationException) {
|
} catch (e: SignatureValidationException) {
|
||||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||||
layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e))
|
layer.addRejectedOnePassSignature(
|
||||||
|
SignatureVerification.Failure(verification, e))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
LOGGER.debug(
|
||||||
inbandSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||||
signature, null, SignatureValidationException("Missing verification key.")
|
inbandSignaturesWithMissingCert.add(
|
||||||
))
|
SignatureVerification.Failure(
|
||||||
|
signature, null, SignatureValidationException("Missing verification key.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,7 +802,9 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.getMissingCertificateCallback() != null) {
|
if (options.getMissingCertificateCallback() != null) {
|
||||||
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID)
|
return options
|
||||||
|
.getMissingCertificateCallback()!!
|
||||||
|
.onMissingPublicKeyEncountered(signature.keyID)
|
||||||
}
|
}
|
||||||
return null // TODO: Missing cert for sig
|
return null // TODO: Missing cert for sig
|
||||||
}
|
}
|
||||||
|
@ -749,7 +816,9 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.getMissingCertificateCallback() != null) {
|
if (options.getMissingCertificateCallback() != null) {
|
||||||
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID)
|
return options
|
||||||
|
.getMissingCertificateCallback()!!
|
||||||
|
.onMissingPublicKeyEncountered(signature.keyID)
|
||||||
}
|
}
|
||||||
return null // TODO: Missing cert for sig
|
return null // TODO: Missing cert for sig
|
||||||
}
|
}
|
||||||
|
@ -800,32 +869,42 @@ class OpenPgpMessageInputStream(
|
||||||
|
|
||||||
fun finish(layer: Layer, policy: Policy) {
|
fun finish(layer: Layer, policy: Policy) {
|
||||||
for (detached in detachedSignatures) {
|
for (detached in detachedSignatures) {
|
||||||
val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
val verification =
|
||||||
|
SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
SignatureValidator.signatureWasCreatedInBounds(
|
||||||
.verify(detached.signature)
|
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
|
.verify(detached.signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||||
detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy)
|
detached.signature,
|
||||||
|
KeyRingUtils.publicKeys(detached.signingKeyRing),
|
||||||
|
policy)
|
||||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||||
layer.addVerifiedDetachedSignature(verification)
|
layer.addVerifiedDetachedSignature(verification)
|
||||||
} catch (e : SignatureValidationException) {
|
} catch (e: SignatureValidationException) {
|
||||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||||
layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e))
|
layer.addRejectedDetachedSignature(
|
||||||
|
SignatureVerification.Failure(verification, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (prepended in prependedSignatures) {
|
for (prepended in prependedSignatures) {
|
||||||
val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
val verification =
|
||||||
|
SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
||||||
try {
|
try {
|
||||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
SignatureValidator.signatureWasCreatedInBounds(
|
||||||
.verify(prepended.signature)
|
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||||
|
.verify(prepended.signature)
|
||||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||||
prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy)
|
prepended.signature,
|
||||||
|
KeyRingUtils.publicKeys(prepended.signingKeyRing),
|
||||||
|
policy)
|
||||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||||
layer.addVerifiedPrependedSignature(verification)
|
layer.addVerifiedPrependedSignature(verification)
|
||||||
} catch (e : SignatureValidationException) {
|
} catch (e: SignatureValidationException) {
|
||||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||||
layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e))
|
layer.addRejectedPrependedSignature(
|
||||||
|
SignatureVerification.Failure(verification, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,22 +943,22 @@ class OpenPgpMessageInputStream(
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) {
|
private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) {
|
||||||
val verifierProvider = ImplementationFactory.getInstance()
|
val verifierProvider =
|
||||||
.pgpContentVerifierBuilderProvider
|
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
|
||||||
try {
|
try {
|
||||||
signature.init(verifierProvider, publicKey)
|
signature.init(verifierProvider, publicKey)
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) {
|
private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) {
|
||||||
val verifierProvider = ImplementationFactory.getInstance()
|
val verifierProvider =
|
||||||
.pgpContentVerifierBuilderProvider
|
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
|
||||||
try {
|
try {
|
||||||
ops.init(verifierProvider, publicKey)
|
ops.init(verifierProvider, publicKey)
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -891,36 +970,40 @@ class OpenPgpMessageInputStream(
|
||||||
private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java)
|
private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun create(inputStream: InputStream,
|
fun create(inputStream: InputStream, options: ConsumerOptions) =
|
||||||
options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy())
|
create(inputStream, options, PGPainless.getPolicy())
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun create(inputStream: InputStream,
|
fun create(inputStream: InputStream, options: ConsumerOptions, policy: Policy) =
|
||||||
options: ConsumerOptions,
|
create(inputStream, options, Message(), policy)
|
||||||
policy: Policy) = create(inputStream, options, Message(), policy)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
internal fun create(inputStream: InputStream,
|
internal fun create(
|
||||||
options: ConsumerOptions,
|
inputStream: InputStream,
|
||||||
metadata: Layer,
|
options: ConsumerOptions,
|
||||||
policy: Policy): OpenPgpMessageInputStream {
|
metadata: Layer,
|
||||||
|
policy: Policy
|
||||||
|
): OpenPgpMessageInputStream {
|
||||||
val openPgpIn = OpenPgpInputStream(inputStream)
|
val openPgpIn = OpenPgpInputStream(inputStream)
|
||||||
openPgpIn.reset()
|
openPgpIn.reset()
|
||||||
|
|
||||||
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
|
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
|
||||||
return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy)
|
return OpenPgpMessageInputStream(
|
||||||
|
Type.non_openpgp, openPgpIn, options, metadata, policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openPgpIn.isBinaryOpenPgp) {
|
if (openPgpIn.isBinaryOpenPgp) {
|
||||||
// Simply consume OpenPGP message
|
// Simply consume OpenPGP message
|
||||||
return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy)
|
return OpenPgpMessageInputStream(
|
||||||
|
Type.standard, openPgpIn, options, metadata, policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (openPgpIn.isAsciiArmored) {
|
return if (openPgpIn.isAsciiArmored) {
|
||||||
val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
|
val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
|
||||||
if (armorIn.isClearText) {
|
if (armorIn.isClearText) {
|
||||||
(metadata as Message).setCleartextSigned()
|
(metadata as Message).setCleartextSigned()
|
||||||
OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy)
|
OpenPgpMessageInputStream(
|
||||||
|
Type.cleartext_signed, armorIn, options, metadata, policy)
|
||||||
} else {
|
} else {
|
||||||
// Simply consume dearmored OpenPGP message
|
// Simply consume dearmored OpenPGP message
|
||||||
OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy)
|
OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy)
|
||||||
|
@ -930,4 +1013,4 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,43 +11,43 @@ import org.pgpainless.key.SubkeyIdentifier
|
||||||
import org.pgpainless.signature.SignatureUtils
|
import org.pgpainless.signature.SignatureUtils
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuple of a signature and an identifier of its corresponding verification key.
|
* Tuple of a signature and an identifier of its corresponding verification key. Semantic meaning of
|
||||||
* Semantic meaning of the signature verification (success, failure) is merely given by context.
|
* the signature verification (success, failure) is merely given by context. E.g.
|
||||||
* E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications,
|
* [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, while the class
|
||||||
* while the class [Failure] contains failed verifications.
|
* [Failure] contains failed verifications.
|
||||||
*
|
*
|
||||||
* @param signature PGPSignature object
|
* @param signature PGPSignature object
|
||||||
* @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification.
|
* @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification.
|
||||||
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
|
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
|
||||||
*/
|
*/
|
||||||
data class SignatureVerification(
|
data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) {
|
||||||
val signature: PGPSignature,
|
|
||||||
val signingKey: SubkeyIdentifier
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
||||||
" Key: $signingKey;"
|
" Key: $signingKey;"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException]
|
* Tuple object of a [SignatureVerification] and the corresponding
|
||||||
* that caused the verification to fail.
|
* [SignatureValidationException] that caused the verification to fail.
|
||||||
*
|
*
|
||||||
* @param signatureVerification verification (tuple of [PGPSignature] and corresponding [SubkeyIdentifier])
|
* @param signatureVerification verification (tuple of [PGPSignature] and corresponding
|
||||||
|
* [SubkeyIdentifier])
|
||||||
* @param validationException exception that caused the verification to fail
|
* @param validationException exception that caused the verification to fail
|
||||||
*/
|
*/
|
||||||
data class Failure(
|
data class Failure(
|
||||||
val signature: PGPSignature,
|
val signature: PGPSignature,
|
||||||
val signingKey: SubkeyIdentifier?,
|
val signingKey: SubkeyIdentifier?,
|
||||||
val validationException: SignatureValidationException
|
val validationException: SignatureValidationException
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(verification: SignatureVerification, validationException: SignatureValidationException):
|
constructor(
|
||||||
this(verification.signature, verification.signingKey, validationException)
|
verification: SignatureVerification,
|
||||||
|
validationException: SignatureValidationException
|
||||||
|
) : this(verification.signature, verification.signingKey, validationException)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
|
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification
|
package org.pgpainless.decryption_verification
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import org.bouncycastle.bcpg.BCPGInputStream
|
import org.bouncycastle.bcpg.BCPGInputStream
|
||||||
import org.bouncycastle.bcpg.MarkerPacket
|
import org.bouncycastle.bcpg.MarkerPacket
|
||||||
import org.bouncycastle.bcpg.Packet
|
import org.bouncycastle.bcpg.Packet
|
||||||
|
@ -13,25 +16,21 @@ import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignature
|
import org.bouncycastle.openpgp.PGPOnePassSignature
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
import org.bouncycastle.openpgp.PGPSignature
|
||||||
import org.pgpainless.algorithm.OpenPgpPacket
|
import org.pgpainless.algorithm.OpenPgpPacket
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data.
|
* Since we need to update signatures with data from the underlying stream, this class is used to
|
||||||
* Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data out though, since
|
* tee out the data. Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data
|
||||||
* [BCPGInputStream.readPacket] inconsistently calls a mix of [BCPGInputStream.read] and
|
* out though, since [BCPGInputStream.readPacket] inconsistently calls a mix of
|
||||||
* [InputStream.read] of the underlying stream. This would cause the second length byte to get swallowed up.
|
* [BCPGInputStream.read] and [InputStream.read] of the underlying stream. This would cause the
|
||||||
|
* second length byte to get swallowed up.
|
||||||
*
|
*
|
||||||
* Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the underlying
|
* Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the
|
||||||
* stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the next packets tag,
|
* underlying stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the
|
||||||
* we need to delay teeing out that byte to signature verifiers.
|
* next packets tag, we need to delay teeing out that byte to signature verifiers. Hence, the
|
||||||
* Hence, the reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using
|
* reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using
|
||||||
* [DelayedTeeInputStream.squeeze].
|
* [DelayedTeeInputStream.squeeze].
|
||||||
*/
|
*/
|
||||||
class TeeBCPGInputStream(
|
class TeeBCPGInputStream(inputStream: BCPGInputStream, outputStream: OutputStream) {
|
||||||
inputStream: BCPGInputStream,
|
|
||||||
outputStream: OutputStream) {
|
|
||||||
|
|
||||||
private val delayedTee: DelayedTeeInputStream
|
private val delayedTee: DelayedTeeInputStream
|
||||||
private val packetInputStream: BCPGInputStream
|
private val packetInputStream: BCPGInputStream
|
||||||
|
@ -43,8 +42,7 @@ class TeeBCPGInputStream(
|
||||||
|
|
||||||
fun nextPacketTag(): OpenPgpPacket? {
|
fun nextPacketTag(): OpenPgpPacket? {
|
||||||
return packetInputStream.nextPacketTag().let {
|
return packetInputStream.nextPacketTag().let {
|
||||||
if (it == -1) null
|
if (it == -1) null else OpenPgpPacket.requireFromTag(it)
|
||||||
else OpenPgpPacket.requireFromTag(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +80,8 @@ class TeeBCPGInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelayedTeeInputStream(
|
class DelayedTeeInputStream(
|
||||||
private val inputStream: InputStream,
|
private val inputStream: InputStream,
|
||||||
private val outputStream: OutputStream
|
private val outputStream: OutputStream
|
||||||
) : InputStream() {
|
) : InputStream() {
|
||||||
private var last: Int = -1
|
private var last: Int = -1
|
||||||
|
|
||||||
|
@ -94,7 +92,7 @@ class TeeBCPGInputStream(
|
||||||
return try {
|
return try {
|
||||||
last = inputStream.read()
|
last = inputStream.read()
|
||||||
last
|
last
|
||||||
} catch (e : IOException) {
|
} catch (e: IOException) {
|
||||||
if (e.message?.contains("crc check failed in armored message") == true) {
|
if (e.message?.contains("crc check failed in armored message") == true) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
@ -108,19 +106,18 @@ class TeeBCPGInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
inputStream.read(b, off, len).let { r ->
|
inputStream.read(b, off, len).let { r ->
|
||||||
last = if (r > 0) {
|
last =
|
||||||
outputStream.write(b, off, r - 1)
|
if (r > 0) {
|
||||||
b[off + r - 1].toInt()
|
outputStream.write(b, off, r - 1)
|
||||||
} else {
|
b[off + r - 1].toInt()
|
||||||
-1
|
} else {
|
||||||
}
|
-1
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Squeeze the last byte out and update the output stream. */
|
||||||
* Squeeze the last byte out and update the output stream.
|
|
||||||
*/
|
|
||||||
fun squeeze() {
|
fun squeeze() {
|
||||||
if (last != -1) {
|
if (last != -1) {
|
||||||
outputStream.write(last)
|
outputStream.write(last)
|
||||||
|
@ -133,4 +130,4 @@ class TeeBCPGInputStream(
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,47 +4,49 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.cleartext_signatures
|
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
import kotlin.jvm.Throws
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList
|
import org.bouncycastle.openpgp.PGPSignatureList
|
||||||
import org.bouncycastle.util.Strings
|
import org.bouncycastle.util.Strings
|
||||||
import org.pgpainless.exception.WrongConsumingMethodException
|
import org.pgpainless.exception.WrongConsumingMethodException
|
||||||
import org.pgpainless.implementation.ImplementationFactory
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
import org.pgpainless.util.ArmoredInputStreamFactory
|
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||||
import java.io.*
|
|
||||||
import kotlin.jvm.Throws
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to deal with cleartext-signed messages.
|
* Utility class to deal with cleartext-signed messages. Based on Bouncycastle's
|
||||||
* Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
|
* [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
|
||||||
*/
|
*/
|
||||||
class ClearsignedMessageUtil {
|
class ClearsignedMessageUtil {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
|
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext
|
||||||
* messageOutputStream.
|
* message to the provided messageOutputStream.
|
||||||
*
|
*
|
||||||
* @param clearsignedInputStream input stream containing a clearsigned message
|
* @param clearsignedInputStream input stream containing a clearsigned message
|
||||||
* @param messageOutputStream output stream to which the dearmored message shall be written
|
* @param messageOutputStream output stream to which the dearmored message shall be written
|
||||||
* @return signatures
|
* @return signatures
|
||||||
*
|
|
||||||
* @throws IOException if the message is not clearsigned or some other IO error happens
|
* @throws IOException if the message is not clearsigned or some other IO error happens
|
||||||
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(WrongConsumingMethodException::class, IOException::class)
|
@Throws(WrongConsumingMethodException::class, IOException::class)
|
||||||
fun detachSignaturesFromInbandClearsignedMessage(
|
fun detachSignaturesFromInbandClearsignedMessage(
|
||||||
clearsignedInputStream: InputStream,
|
clearsignedInputStream: InputStream,
|
||||||
messageOutputStream: OutputStream): PGPSignatureList {
|
messageOutputStream: OutputStream
|
||||||
val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) {
|
): PGPSignatureList {
|
||||||
clearsignedInputStream
|
val input: ArmoredInputStream =
|
||||||
} else {
|
if (clearsignedInputStream is ArmoredInputStream) {
|
||||||
ArmoredInputStreamFactory.get(clearsignedInputStream)
|
clearsignedInputStream
|
||||||
}
|
} else {
|
||||||
|
ArmoredInputStreamFactory.get(clearsignedInputStream)
|
||||||
|
}
|
||||||
|
|
||||||
if (!input.isClearText) {
|
if (!input.isClearText) {
|
||||||
throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.")
|
throw WrongConsumingMethodException(
|
||||||
|
"Message isn't using the Cleartext Signature Framework.")
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedOutputStream(messageOutputStream).use { output ->
|
BufferedOutputStream(messageOutputStream).use { output ->
|
||||||
|
@ -94,7 +96,11 @@ class ClearsignedMessageUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int {
|
private fun readInputLine(
|
||||||
|
bOut: ByteArrayOutputStream,
|
||||||
|
lookAhead: Int,
|
||||||
|
fIn: InputStream
|
||||||
|
): Int {
|
||||||
var mLookAhead = lookAhead
|
var mLookAhead = lookAhead
|
||||||
bOut.reset()
|
bOut.reset()
|
||||||
var ch = mLookAhead
|
var ch = mLookAhead
|
||||||
|
@ -150,4 +156,4 @@ class ClearsignedMessageUtil {
|
||||||
return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte()
|
return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the [MultiPassStrategy].
|
* Implementation of the [MultiPassStrategy]. This class keeps the read data in memory by caching
|
||||||
* This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream].
|
* the data inside a [ByteArrayOutputStream].
|
||||||
*
|
*
|
||||||
* Note, that this class is suitable and efficient for processing small amounts of data.
|
* Note, that this class is suitable and efficient for processing small amounts of data. For larger
|
||||||
* For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to
|
* data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to prevent
|
||||||
* prevent [OutOfMemoryError] and other issues.
|
* [OutOfMemoryError] and other issues.
|
||||||
*/
|
*/
|
||||||
class InMemoryMultiPassStrategy : MultiPassStrategy {
|
class InMemoryMultiPassStrategy : MultiPassStrategy {
|
||||||
|
|
||||||
|
@ -26,4 +26,4 @@ class InMemoryMultiPassStrategy : MultiPassStrategy {
|
||||||
get() = ByteArrayInputStream(getBytes())
|
get() = ByteArrayInputStream(getBytes())
|
||||||
|
|
||||||
fun getBytes(): ByteArray = messageOutputStream.toByteArray()
|
fun getBytes(): ByteArray = messageOutputStream.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,16 @@ package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures,
|
* Since for verification of cleartext signed messages, we need to read the whole data twice in
|
||||||
* a strategy for how to cache the read data is required.
|
* order to verify signatures, a strategy for how to cache the read data is required. Otherwise,
|
||||||
* Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues.
|
* large data kept in memory could cause an [OutOfMemoryError] or other issues.
|
||||||
*
|
*
|
||||||
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes
|
* This is an Interface that describes a strategy to deal with the fact that detached signatures
|
||||||
* to do verification.
|
* require multiple passes to do verification.
|
||||||
*
|
*
|
||||||
* This interface can be used to write the signed data stream out via [messageOutputStream] and later
|
* This interface can be used to write the signed data stream out via [messageOutputStream] and
|
||||||
* get access to the data again via [messageInputStream].
|
* later get access to the data again via [messageInputStream]. Thereby the detail where the data is
|
||||||
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away.
|
* being stored (memory, file, etc.) can be abstracted away.
|
||||||
*/
|
*/
|
||||||
interface MultiPassStrategy {
|
interface MultiPassStrategy {
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ interface MultiPassStrategy {
|
||||||
* Provide an [InputStream] which contains the data that was previously written away in
|
* Provide an [InputStream] which contains the data that was previously written away in
|
||||||
* [messageOutputStream].
|
* [messageOutputStream].
|
||||||
*
|
*
|
||||||
* As there may be multiple signatures that need to be processed, each call of this method MUST return
|
* As there may be multiple signatures that need to be processed, each call of this method MUST
|
||||||
* a new [InputStream].
|
* return a new [InputStream].
|
||||||
*
|
*
|
||||||
* @return input stream
|
* @return input stream
|
||||||
* @throws IOException io error
|
* @throws IOException io error
|
||||||
|
@ -43,9 +43,10 @@ interface MultiPassStrategy {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the message content out to a file and re-read it to verify signatures.
|
* Write the message content out to a file and re-read it to verify signatures. This
|
||||||
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory.
|
* strategy is best suited for larger messages (e.g. plaintext signed files) which might not
|
||||||
* After the message has been processed completely, the messages content are available at the provided file.
|
* fit into memory. After the message has been processed completely, the messages content
|
||||||
|
* are available at the provided file.
|
||||||
*
|
*
|
||||||
* @param file target file
|
* @param file target file
|
||||||
* @return strategy
|
* @return strategy
|
||||||
|
@ -56,10 +57,10 @@ interface MultiPassStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the message content into memory.
|
* Read the message content into memory. This strategy is best suited for small messages
|
||||||
* This strategy is best suited for small messages which fit into memory.
|
* which fit into memory. After the message has been processed completely, the message
|
||||||
* After the message has been processed completely, the message content can be accessed by calling
|
* content can be accessed by calling [ByteArrayOutputStream.toByteArray] on
|
||||||
* [ByteArrayOutputStream.toByteArray] on [messageOutputStream].
|
* [messageOutputStream].
|
||||||
*
|
*
|
||||||
* @return strategy
|
* @return strategy
|
||||||
*/
|
*/
|
||||||
|
@ -68,4 +69,4 @@ interface MultiPassStrategy {
|
||||||
return InMemoryMultiPassStrategy()
|
return InMemoryMultiPassStrategy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,15 @@ package org.pgpainless.decryption_verification.cleartext_signatures
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the [MultiPassStrategy].
|
* Implementation of the [MultiPassStrategy]. When processing signed data the first time, the data
|
||||||
* When processing signed data the first time, the data is being written out into a file.
|
* is being written out into a file. For the second pass, that file is being read again.
|
||||||
* For the second pass, that file is being read again.
|
|
||||||
*
|
*
|
||||||
* This strategy is recommended when larger amounts of data need to be processed.
|
* This strategy is recommended when larger amounts of data need to be processed. For smaller files,
|
||||||
* For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency.
|
* [InMemoryMultiPassStrategy] yields higher efficiency.
|
||||||
*
|
*
|
||||||
* @param file file to write the data to and read from
|
* @param file file to write the data to and read from
|
||||||
*/
|
*/
|
||||||
class WriteToFileMultiPassStrategy(
|
class WriteToFileMultiPassStrategy(private val file: File) : MultiPassStrategy {
|
||||||
private val file: File
|
|
||||||
) : MultiPassStrategy {
|
|
||||||
|
|
||||||
override val messageOutputStream: OutputStream
|
override val messageOutputStream: OutputStream
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
@ -39,5 +36,4 @@ class WriteToFileMultiPassStrategy(
|
||||||
}
|
}
|
||||||
return FileInputStream(file)
|
return FileInputStream(file)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -5,35 +5,26 @@
|
||||||
package org.pgpainless.decryption_verification.syntax_check
|
package org.pgpainless.decryption_verification.syntax_check
|
||||||
|
|
||||||
enum class InputSymbol {
|
enum class InputSymbol {
|
||||||
/**
|
/** A [PGPLiteralData] packet. */
|
||||||
* A [PGPLiteralData] packet.
|
|
||||||
*/
|
|
||||||
LITERAL_DATA,
|
LITERAL_DATA,
|
||||||
/**
|
/** A [PGPSignatureList] object. */
|
||||||
* A [PGPSignatureList] object.
|
|
||||||
*/
|
|
||||||
SIGNATURE,
|
SIGNATURE,
|
||||||
/**
|
/** A [PGPOnePassSignatureList] object. */
|
||||||
* A [PGPOnePassSignatureList] object.
|
|
||||||
*/
|
|
||||||
ONE_PASS_SIGNATURE,
|
ONE_PASS_SIGNATURE,
|
||||||
/**
|
/**
|
||||||
* A [PGPCompressedData] packet.
|
* A [PGPCompressedData] packet. The contents of this packet MUST form a valid OpenPGP message,
|
||||||
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
|
* so a nested PDA is opened to verify its nested packet sequence.
|
||||||
* its nested packet sequence.
|
|
||||||
*/
|
*/
|
||||||
COMPRESSED_DATA,
|
COMPRESSED_DATA,
|
||||||
/**
|
/**
|
||||||
* A [PGPEncryptedDataList] object.
|
* A [PGPEncryptedDataList] object. This object combines multiple ESKs and the corresponding
|
||||||
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
|
* Symmetrically Encrypted (possibly Integrity Protected) Data packet.
|
||||||
* (possibly Integrity Protected) Data packet.
|
|
||||||
*/
|
*/
|
||||||
ENCRYPTED_DATA,
|
ENCRYPTED_DATA,
|
||||||
/**
|
/**
|
||||||
* Marks the end of a (sub-) sequence.
|
* Marks the end of a (sub-) sequence. This input is given if the end of an OpenPGP message is
|
||||||
* This input is given if the end of an OpenPGP message is reached.
|
* reached. This might be the case for the end of the whole ciphertext, or the end of a packet
|
||||||
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
|
* with nested contents (e.g. the end of a Compressed Data packet).
|
||||||
* (e.g. the end of a Compressed Data packet).
|
|
||||||
*/
|
*/
|
||||||
END_OF_SEQUENCE
|
END_OF_SEQUENCE
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||||
/**
|
/**
|
||||||
* This class describes the syntax for OpenPGP messages as specified by rfc4880.
|
* This class describes the syntax for OpenPGP messages as specified by rfc4880.
|
||||||
*
|
*
|
||||||
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
|
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) See
|
||||||
* See [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/)
|
* [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/)
|
||||||
* See [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/)
|
* See
|
||||||
|
* [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/)
|
||||||
*/
|
*/
|
||||||
class OpenPgpMessageSyntax : Syntax {
|
class OpenPgpMessageSyntax : Syntax {
|
||||||
|
|
||||||
|
@ -33,10 +34,12 @@ class OpenPgpMessageSyntax : Syntax {
|
||||||
return when (input) {
|
return when (input) {
|
||||||
InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE)
|
InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE)
|
||||||
InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG)
|
InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG)
|
||||||
InputSymbol.ONE_PASS_SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG)
|
InputSymbol.ONE_PASS_SIGNATURE ->
|
||||||
|
Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG)
|
||||||
InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE)
|
InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE)
|
||||||
InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE)
|
InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE)
|
||||||
InputSymbol.END_OF_SEQUENCE -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
InputSymbol.END_OF_SEQUENCE ->
|
||||||
|
throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
||||||
else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,4 +88,4 @@ class OpenPgpMessageSyntax : Syntax {
|
||||||
}
|
}
|
||||||
throw MalformedOpenPgpMessageException(State.VALID, input, stackItem)
|
throw MalformedOpenPgpMessageException(State.VALID, input, stackItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,37 +8,41 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pushdown Automaton for validating context-free languages.
|
* Pushdown Automaton for validating context-free languages. In PGPainless, this class is used to
|
||||||
* In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax.
|
* validate OpenPGP message packet sequences against the allowed syntax.
|
||||||
*
|
*
|
||||||
* See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
|
* See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
|
||||||
*/
|
*/
|
||||||
class PDA constructor(
|
class PDA
|
||||||
private val syntax: Syntax,
|
constructor(
|
||||||
private val stack: ArrayDeque<StackSymbol>,
|
private val syntax: Syntax,
|
||||||
private val inputs: MutableList<InputSymbol>,
|
private val stack: ArrayDeque<StackSymbol>,
|
||||||
private var state: State
|
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 syntax syntax
|
||||||
* @param initialState initial state
|
* @param initialState initial state
|
||||||
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance)
|
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of
|
||||||
|
* appearance)
|
||||||
*/
|
*/
|
||||||
constructor(syntax: Syntax, initialState: State, vararg initialStack: StackSymbol): this (
|
constructor(
|
||||||
syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
|
syntax: Syntax,
|
||||||
|
initialState: State,
|
||||||
|
vararg initialStack: StackSymbol
|
||||||
|
) : this(syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
|
||||||
|
|
||||||
|
/** Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. */
|
||||||
|
constructor() :
|
||||||
|
this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax].
|
* Process the next [InputSymbol]. This will either leave the PDA in the next state, or throw a
|
||||||
*/
|
* [MalformedOpenPgpMessageException] if the input symbol is rejected.
|
||||||
constructor(): this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the next [InputSymbol].
|
|
||||||
* This will either leave the PDA in the next state, or throw a [MalformedOpenPgpMessageException] if the
|
|
||||||
* input symbol is rejected.
|
|
||||||
*
|
*
|
||||||
* @param input input symbol
|
* @param input input symbol
|
||||||
* @throws MalformedOpenPgpMessageException if the input symbol is rejected
|
* @throws MalformedOpenPgpMessageException if the input symbol is rejected
|
||||||
|
@ -53,14 +57,17 @@ class PDA constructor(
|
||||||
}
|
}
|
||||||
inputs.add(input)
|
inputs.add(input)
|
||||||
} catch (e: MalformedOpenPgpMessageException) {
|
} catch (e: MalformedOpenPgpMessageException) {
|
||||||
val stackFormat = if (stackSymbol != null) {
|
val stackFormat =
|
||||||
"${stack.joinToString()}||$stackSymbol"
|
if (stackSymbol != null) {
|
||||||
} else {
|
"${stack.joinToString()}||$stackSymbol"
|
||||||
stack.joinToString()
|
} else {
|
||||||
}
|
stack.joinToString()
|
||||||
val wrapped = MalformedOpenPgpMessageException(
|
}
|
||||||
|
val wrapped =
|
||||||
|
MalformedOpenPgpMessageException(
|
||||||
"Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" +
|
"Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" +
|
||||||
"No transition from state '$state' with stack $stackFormat", e)
|
"No transition from state '$state' with stack $stackFormat",
|
||||||
|
e)
|
||||||
LOGGER.debug("Invalid input '$input'", wrapped)
|
LOGGER.debug("Invalid input '$input'", wrapped)
|
||||||
throw wrapped
|
throw wrapped
|
||||||
}
|
}
|
||||||
|
@ -87,7 +94,8 @@ class PDA constructor(
|
||||||
*/
|
*/
|
||||||
fun assertValid() {
|
fun assertValid() {
|
||||||
if (!isValid()) {
|
if (!isValid()) {
|
||||||
throw MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: ${toString()}")
|
throw MalformedOpenPgpMessageException(
|
||||||
|
"Pushdown Automaton is not in an acceptable state: ${toString()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +122,6 @@ class PDA constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
|
||||||
private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,10 @@
|
||||||
package org.pgpainless.decryption_verification.syntax_check
|
package org.pgpainless.decryption_verification.syntax_check
|
||||||
|
|
||||||
enum class StackSymbol {
|
enum class StackSymbol {
|
||||||
/**
|
/** OpenPGP Message. */
|
||||||
* OpenPGP Message.
|
|
||||||
*/
|
|
||||||
MSG,
|
MSG,
|
||||||
/**
|
/** OnePassSignature (in case of BC this represents a OnePassSignatureList). */
|
||||||
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
|
|
||||||
*/
|
|
||||||
OPS,
|
OPS,
|
||||||
/**
|
/** Special symbol representing the end of the message. */
|
||||||
* Special symbol representing the end of the message.
|
|
||||||
*/
|
|
||||||
TERMINUS
|
TERMINUS
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,11 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification.syntax_check
|
package org.pgpainless.decryption_verification.syntax_check
|
||||||
|
|
||||||
/**
|
/** Set of states of the automaton. */
|
||||||
* Set of states of the automaton.
|
|
||||||
*/
|
|
||||||
enum class State {
|
enum class State {
|
||||||
OPENPGP_MESSAGE,
|
OPENPGP_MESSAGE,
|
||||||
LITERAL_MESSAGE,
|
LITERAL_MESSAGE,
|
||||||
COMPRESSED_MESSAGE,
|
COMPRESSED_MESSAGE,
|
||||||
ENCRYPTED_MESSAGE,
|
ENCRYPTED_MESSAGE,
|
||||||
VALID
|
VALID
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,25 +6,24 @@ package org.pgpainless.decryption_verification.syntax_check
|
||||||
|
|
||||||
import org.pgpainless.exception.MalformedOpenPgpMessageException
|
import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||||
|
|
||||||
/**
|
/** This interface can be used to define a custom syntax for the [PDA]. */
|
||||||
* This interface can be used to define a custom syntax for the [PDA].
|
|
||||||
*/
|
|
||||||
interface Syntax {
|
interface Syntax {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describe a transition rule from [State] <pre>from</pre> for [InputSymbol] <pre>input</pre>
|
* Describe a transition rule from [State] <pre>from</pre> for [InputSymbol] <pre>input</pre>
|
||||||
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack.
|
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack. The resulting
|
||||||
* The resulting [Transition] contains the new [State], as well as a list of
|
* [Transition] contains the new [State], as well as a list of [StackSymbols][StackSymbol] that
|
||||||
* [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule.
|
* get pushed onto the stack by the transition rule. If there is no applicable rule, a
|
||||||
* If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case
|
* [MalformedOpenPgpMessageException] is thrown, since in this case the [InputSymbol] must be
|
||||||
* the [InputSymbol] must be considered illegal.
|
* considered illegal.
|
||||||
*
|
*
|
||||||
* @param from current state of the PDA
|
* @param from current state of the PDA
|
||||||
* @param input input symbol
|
* @param input input symbol
|
||||||
* @param stackItem item that got popped from the top of the stack
|
* @param stackItem item that got popped from the top of the stack
|
||||||
* @return applicable transition rule containing the new state and pushed stack symbols
|
* @return applicable transition rule containing the new state and pushed stack symbols
|
||||||
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal)
|
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input
|
||||||
|
* symbol is illegal)
|
||||||
*/
|
*/
|
||||||
@Throws(MalformedOpenPgpMessageException::class)
|
@Throws(MalformedOpenPgpMessageException::class)
|
||||||
fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition
|
fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
package org.pgpainless.decryption_verification.syntax_check
|
package org.pgpainless.decryption_verification.syntax_check
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result of applying a transition rule.
|
* Result of applying a transition rule. Transition rules can be described by implementing the
|
||||||
* Transition rules can be described by implementing the [Syntax] interface.
|
* [Syntax] interface.
|
||||||
*
|
*
|
||||||
* @param newState new [State] that is reached by applying the transition.
|
* @param newState new [State] that is reached by applying the transition.
|
||||||
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the transition.
|
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the
|
||||||
* The list contains items in the order in which they are pushed onto the stack.
|
* transition. The list contains items in the order in which they are pushed onto the stack. The
|
||||||
* The list may be empty.
|
* list may be empty.
|
||||||
*/
|
*/
|
||||||
class Transition private constructor(
|
class Transition private constructor(val pushedItems: List<StackSymbol>, val newState: State) {
|
||||||
val pushedItems: List<StackSymbol>,
|
|
||||||
val newState: State
|
|
||||||
) {
|
|
||||||
|
|
||||||
constructor(newState: State, vararg pushedItems: StackSymbol): this(pushedItems.toList(), newState)
|
constructor(
|
||||||
}
|
newState: State,
|
||||||
|
vararg pushedItems: StackSymbol
|
||||||
|
) : this(pushedItems.toList(), newState)
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.security.MessageDigest
|
||||||
import org.bouncycastle.extensions.unlock
|
import org.bouncycastle.extensions.unlock
|
||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey
|
import org.bouncycastle.openpgp.PGPPrivateKey
|
||||||
|
@ -13,20 +14,23 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator
|
||||||
import org.pgpainless.PGPainless
|
import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.algorithm.SignatureType
|
import org.pgpainless.algorithm.SignatureType
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
class BcHashContextSigner {
|
class BcHashContextSigner {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun signHashContext(hashContext: MessageDigest,
|
fun signHashContext(
|
||||||
signatureType: SignatureType,
|
hashContext: MessageDigest,
|
||||||
secretKey: PGPSecretKeyRing,
|
signatureType: SignatureType,
|
||||||
protector: SecretKeyRingProtector): PGPSignature {
|
secretKey: PGPSecretKeyRing,
|
||||||
|
protector: SecretKeyRingProtector
|
||||||
|
): PGPSignature {
|
||||||
val info = PGPainless.inspectKeyRing(secretKey)
|
val info = PGPainless.inspectKeyRing(secretKey)
|
||||||
return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull()
|
return info.signingSubkeys
|
||||||
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
|
.mapNotNull { info.getSecretKey(it.keyID) }
|
||||||
?: throw PGPException("Key does not contain suitable signing subkey.")
|
.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
|
* @throws PGPException in case of an OpenPGP error
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
internal fun signHashContext(hashContext: MessageDigest,
|
internal fun signHashContext(
|
||||||
signatureType: SignatureType,
|
hashContext: MessageDigest,
|
||||||
privateKey: PGPPrivateKey): PGPSignature {
|
signatureType: SignatureType,
|
||||||
|
privateKey: PGPPrivateKey
|
||||||
|
): PGPSignature {
|
||||||
return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext))
|
return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext))
|
||||||
.apply { init(signatureType.code, privateKey) }
|
.apply { init(signatureType.code, privateKey) }
|
||||||
.generate()
|
.generate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
|
||||||
import org.bouncycastle.crypto.CipherParameters
|
import org.bouncycastle.crypto.CipherParameters
|
||||||
import org.bouncycastle.crypto.CryptoException
|
import org.bouncycastle.crypto.CryptoException
|
||||||
|
@ -23,17 +25,14 @@ import org.bouncycastle.openpgp.operator.PGPContentSigner
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter
|
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter
|
||||||
import org.pgpainless.algorithm.HashAlgorithm
|
import org.pgpainless.algorithm.HashAlgorithm
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import java.io.OutputStream
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash contexts.
|
* Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash
|
||||||
* This can come in handy to sign data, which was already processed to calculate the hash context, without the
|
* contexts. This can come in handy to sign data, which was already processed to calculate the hash
|
||||||
* need to process it again to calculate the OpenPGP signature.
|
* context, without the need to process it again to calculate the OpenPGP signature.
|
||||||
*/
|
*/
|
||||||
class BcPGPHashContextContentSignerBuilder(
|
class BcPGPHashContextContentSignerBuilder(private val messageDigest: MessageDigest) :
|
||||||
private val messageDigest: MessageDigest
|
PGPHashContextContentSignerBuilder() {
|
||||||
) : PGPHashContextContentSignerBuilder() {
|
|
||||||
|
|
||||||
private val keyConverter = BcPGPKeyConverter()
|
private val keyConverter = BcPGPKeyConverter()
|
||||||
private val _hashAlgorithm: HashAlgorithm
|
private val _hashAlgorithm: HashAlgorithm
|
||||||
|
@ -50,15 +49,22 @@ class BcPGPHashContextContentSignerBuilder(
|
||||||
|
|
||||||
return object : PGPContentSigner {
|
return object : PGPContentSigner {
|
||||||
override fun getOutputStream(): OutputStream = SignerOutputStream(signer)
|
override fun getOutputStream(): OutputStream = SignerOutputStream(signer)
|
||||||
override fun getSignature(): ByteArray = try {
|
|
||||||
signer.generateSignature()
|
override fun getSignature(): ByteArray =
|
||||||
} catch (e : CryptoException) {
|
try {
|
||||||
throw IllegalStateException("unable to create signature.", e)
|
signer.generateSignature()
|
||||||
}
|
} catch (e: CryptoException) {
|
||||||
|
throw IllegalStateException("unable to create signature.", e)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getDigest(): ByteArray = messageDigest.digest()
|
override fun getDigest(): ByteArray = messageDigest.digest()
|
||||||
|
|
||||||
override fun getType(): Int = signatureType
|
override fun getType(): Int = signatureType
|
||||||
|
|
||||||
override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId
|
override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId
|
||||||
|
|
||||||
override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId
|
override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId
|
||||||
|
|
||||||
override fun getKeyID(): Long = privateKey.keyID
|
override fun getKeyID(): Long = privateKey.keyID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,21 +73,25 @@ class BcPGPHashContextContentSignerBuilder(
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun requireFromName(digestName: String): HashAlgorithm {
|
private fun requireFromName(digestName: String): HashAlgorithm {
|
||||||
val algorithm = HashAlgorithm.fromName(digestName)
|
val algorithm = HashAlgorithm.fromName(digestName)
|
||||||
require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName"}
|
require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName" }
|
||||||
return algorithm
|
return algorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun createSigner(keyAlgorithm: PublicKeyAlgorithm,
|
private fun createSigner(
|
||||||
messageDigest: MessageDigest,
|
keyAlgorithm: PublicKeyAlgorithm,
|
||||||
keyParam: CipherParameters): Signer {
|
messageDigest: MessageDigest,
|
||||||
|
keyParam: CipherParameters
|
||||||
|
): Signer {
|
||||||
val staticDigest = ExistingMessageDigest(messageDigest)
|
val staticDigest = ExistingMessageDigest(messageDigest)
|
||||||
return when (keyAlgorithm.algorithmId) {
|
return when (keyAlgorithm.algorithmId) {
|
||||||
PublicKeyAlgorithmTags.RSA_GENERAL, PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest)
|
PublicKeyAlgorithmTags.RSA_GENERAL,
|
||||||
|
PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest)
|
||||||
PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest)
|
PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest)
|
||||||
PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest)
|
PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest)
|
||||||
PublicKeyAlgorithmTags.EDDSA_LEGACY -> {
|
PublicKeyAlgorithmTags.EDDSA_LEGACY -> {
|
||||||
if (keyParam is Ed25519PrivateKeyParameters || keyParam is Ed25519PublicKeyParameters)
|
if (keyParam is Ed25519PrivateKeyParameters ||
|
||||||
|
keyParam is Ed25519PublicKeyParameters)
|
||||||
EdDsaSigner(Ed25519Signer(), staticDigest)
|
EdDsaSigner(Ed25519Signer(), staticDigest)
|
||||||
else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest)
|
else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest)
|
||||||
}
|
}
|
||||||
|
@ -91,10 +101,7 @@ class BcPGPHashContextContentSignerBuilder(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from BCs BcImplProvider - required since BCs class is package visible only :/
|
// Copied from BCs BcImplProvider - required since BCs class is package visible only :/
|
||||||
internal class EdDsaSigner(
|
internal class EdDsaSigner(private val signer: Signer, private val digest: Digest) : Signer {
|
||||||
private val signer: Signer,
|
|
||||||
private val digest: Digest
|
|
||||||
) : Signer {
|
|
||||||
private val digBuf: ByteArray = ByteArray(digest.digestSize)
|
private val digBuf: ByteArray = ByteArray(digest.digestSize)
|
||||||
|
|
||||||
override fun init(forSigning: Boolean, param: CipherParameters) {
|
override fun init(forSigning: Boolean, param: CipherParameters) {
|
||||||
|
@ -128,4 +135,4 @@ class BcPGPHashContextContentSignerBuilder(
|
||||||
digest.reset()
|
digest.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,15 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
import org.pgpainless.algorithm.StreamEncoding
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import org.pgpainless.algorithm.StreamEncoding
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding].
|
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired
|
||||||
* This implementation originates from the Bouncy Castle library.
|
* [StreamEncoding]. This implementation originates from the Bouncy Castle library.
|
||||||
*/
|
*/
|
||||||
class CRLFGeneratorStream(
|
class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEncoding) :
|
||||||
private val crlfOut: OutputStream,
|
OutputStream() {
|
||||||
encoding: StreamEncoding
|
|
||||||
) : OutputStream() {
|
|
||||||
|
|
||||||
private val isBinary: Boolean
|
private val isBinary: Boolean
|
||||||
private var lastB = 0
|
private var lastB = 0
|
||||||
|
@ -26,9 +24,9 @@ class CRLFGeneratorStream(
|
||||||
|
|
||||||
override fun write(b: Int) {
|
override fun write(b: Int) {
|
||||||
if (!isBinary) {
|
if (!isBinary) {
|
||||||
if (b == '\n'.code && lastB != '\r'.code) { // Unix
|
if (b == '\n'.code && lastB != '\r'.code) { // Unix
|
||||||
crlfOut.write('\r'.code)
|
crlfOut.write('\r'.code)
|
||||||
} else if (lastB == '\r'.code) { // MAC
|
} else if (lastB == '\r'.code) { // MAC
|
||||||
if (b != '\n'.code) {
|
if (b != '\n'.code) {
|
||||||
crlfOut.write('\n'.code)
|
crlfOut.write('\n'.code)
|
||||||
}
|
}
|
||||||
|
@ -49,4 +47,4 @@ class CRLFGeneratorStream(
|
||||||
super.flush()
|
super.flush()
|
||||||
crlfOut.flush()
|
crlfOut.flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,18 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
import org.pgpainless.PGPainless.Companion.getPolicy
|
import org.pgpainless.PGPainless.Companion.getPolicy
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
|
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
class EncryptionBuilder : EncryptionBuilderInterface {
|
class EncryptionBuilder : EncryptionBuilderInterface {
|
||||||
override fun onOutputStream(outputStream: OutputStream): EncryptionBuilderInterface.WithOptions {
|
override fun onOutputStream(
|
||||||
|
outputStream: OutputStream
|
||||||
|
): EncryptionBuilderInterface.WithOptions {
|
||||||
return WithOptionsImpl(outputStream)
|
return WithOptionsImpl(outputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +28,7 @@ class EncryptionBuilder : EncryptionBuilderInterface {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
|
||||||
val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Negotiate the [SymmetricKeyAlgorithm] used for message encryption.
|
* Negotiate the [SymmetricKeyAlgorithm] used for message encryption.
|
||||||
|
@ -36,24 +37,32 @@ class EncryptionBuilder : EncryptionBuilderInterface {
|
||||||
* @return negotiated symmetric key algorithm
|
* @return negotiated symmetric key algorithm
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun negotiateSymmetricEncryptionAlgorithm(encryptionOptions: EncryptionOptions): SymmetricKeyAlgorithm {
|
fun negotiateSymmetricEncryptionAlgorithm(
|
||||||
val preferences = encryptionOptions.keyViews.values
|
encryptionOptions: EncryptionOptions
|
||||||
|
): SymmetricKeyAlgorithm {
|
||||||
|
val preferences =
|
||||||
|
encryptionOptions.keyViews.values
|
||||||
.map { it.preferredSymmetricKeyAlgorithms }
|
.map { it.preferredSymmetricKeyAlgorithms }
|
||||||
.toList()
|
.toList()
|
||||||
val algorithm = byPopularity().negotiate(
|
val algorithm =
|
||||||
getPolicy().symmetricKeyEncryptionAlgorithmPolicy,
|
byPopularity()
|
||||||
encryptionOptions.encryptionAlgorithmOverride,
|
.negotiate(
|
||||||
preferences)
|
getPolicy().symmetricKeyEncryptionAlgorithmPolicy,
|
||||||
LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm)
|
encryptionOptions.encryptionAlgorithmOverride,
|
||||||
|
preferences)
|
||||||
|
LOGGER.debug(
|
||||||
|
"Negotiation resulted in {} being the symmetric encryption algorithm of choice.",
|
||||||
|
algorithm)
|
||||||
return algorithm
|
return algorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm {
|
fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm {
|
||||||
val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride
|
val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride
|
||||||
return compressionAlgorithmOverride ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm()
|
return compressionAlgorithmOverride
|
||||||
|
?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm()
|
||||||
|
|
||||||
// TODO: Negotiation
|
// TODO: Negotiation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import org.bouncycastle.openpgp.PGPException
|
||||||
|
|
||||||
fun interface EncryptionBuilderInterface {
|
fun interface EncryptionBuilderInterface {
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ fun interface EncryptionBuilderInterface {
|
||||||
*
|
*
|
||||||
* @param options options
|
* @param options options
|
||||||
* @return encryption stream
|
* @return encryption stream
|
||||||
*
|
|
||||||
* @throws PGPException if something goes wrong during encryption stream preparation
|
* @throws PGPException if something goes wrong during encryption stream preparation
|
||||||
* @throws IOException if something goes wrong during encryption stream preparation (writing headers)
|
* @throws IOException if something goes wrong during encryption stream preparation (writing
|
||||||
|
* headers)
|
||||||
*/
|
*/
|
||||||
@Throws(PGPException::class, IOException::class)
|
@Throws(PGPException::class, IOException::class)
|
||||||
fun withOptions(options: ProducerOptions): EncryptionStream
|
fun withOptions(options: ProducerOptions): EncryptionStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
|
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
|
||||||
|
@ -19,12 +20,8 @@ import org.pgpainless.key.SubkeyIdentifier
|
||||||
import org.pgpainless.key.info.KeyAccessor
|
import org.pgpainless.key.info.KeyAccessor
|
||||||
import org.pgpainless.key.info.KeyRingInfo
|
import org.pgpainless.key.info.KeyRingInfo
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
class EncryptionOptions(private val purpose: EncryptionPurpose) {
|
||||||
class EncryptionOptions(
|
|
||||||
private val purpose: EncryptionPurpose
|
|
||||||
) {
|
|
||||||
private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf()
|
private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf()
|
||||||
private val _encryptionKeyIdentifiers: MutableSet<SubkeyIdentifier> = mutableSetOf()
|
private val _encryptionKeyIdentifiers: MutableSet<SubkeyIdentifier> = mutableSetOf()
|
||||||
private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf()
|
private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf()
|
||||||
|
@ -37,16 +34,20 @@ class EncryptionOptions(
|
||||||
|
|
||||||
val encryptionMethods
|
val encryptionMethods
|
||||||
get() = _encryptionMethods.toSet()
|
get() = _encryptionMethods.toSet()
|
||||||
|
|
||||||
val encryptionKeyIdentifiers
|
val encryptionKeyIdentifiers
|
||||||
get() = _encryptionKeyIdentifiers.toSet()
|
get() = _encryptionKeyIdentifiers.toSet()
|
||||||
|
|
||||||
val keyRingInfo
|
val keyRingInfo
|
||||||
get() = _keyRingInfo.toMap()
|
get() = _keyRingInfo.toMap()
|
||||||
|
|
||||||
val keyViews
|
val keyViews
|
||||||
get() = _keyViews.toMap()
|
get() = _keyViews.toMap()
|
||||||
|
|
||||||
val encryptionAlgorithmOverride
|
val encryptionAlgorithmOverride
|
||||||
get() = _encryptionAlgorithmOverride
|
get() = _encryptionAlgorithmOverride
|
||||||
|
|
||||||
constructor(): this(EncryptionPurpose.ANY)
|
constructor() : this(EncryptionPurpose.ANY)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
|
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
|
||||||
|
@ -54,54 +55,56 @@ class EncryptionOptions(
|
||||||
*
|
*
|
||||||
* @return encryption options
|
* @return encryption options
|
||||||
*/
|
*/
|
||||||
fun setEvaluationDate(evaluationDate: Date) = apply {
|
fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate }
|
||||||
this.evaluationDate = evaluationDate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for
|
* Identify authenticatable certificates for the given user-ID by querying the {@link
|
||||||
* identifiable bindings.
|
* CertificateAuthority} for identifiable bindings. Add all acceptable bindings, whose trust
|
||||||
* Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients.
|
* amount is larger or equal to the target amount to the list of recipients.
|
||||||
|
*
|
||||||
* @param userId userId
|
* @param userId userId
|
||||||
* @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address
|
* @param email if true, treat the user-ID as an email address and match all user-IDs containing
|
||||||
|
* the mail address
|
||||||
* @param authority certificate authority
|
* @param authority certificate authority
|
||||||
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated,
|
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, 60
|
||||||
* 60 = partially authenticated...)
|
* = partially authenticated...)
|
||||||
* @return encryption options
|
* @return encryption options
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addAuthenticatableRecipients(userId: String, email: Boolean, authority: CertificateAuthority, targetAmount: Int = 120) = apply {
|
fun addAuthenticatableRecipients(
|
||||||
|
userId: String,
|
||||||
|
email: Boolean,
|
||||||
|
authority: CertificateAuthority,
|
||||||
|
targetAmount: Int = 120
|
||||||
|
) = apply {
|
||||||
var foundAcceptable = false
|
var foundAcceptable = false
|
||||||
authority.lookupByUserId(userId, email, evaluationDate, targetAmount)
|
authority
|
||||||
.filter { it.isAuthenticated() }
|
.lookupByUserId(userId, email, evaluationDate, targetAmount)
|
||||||
.forEach { addRecipient(it.certificate)
|
.filter { it.isAuthenticated() }
|
||||||
.also {
|
.forEach { addRecipient(it.certificate).also { foundAcceptable = true } }
|
||||||
foundAcceptable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require(foundAcceptable) {
|
require(foundAcceptable) {
|
||||||
"Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount."
|
"Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
|
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
|
||||||
|
* as recipients.
|
||||||
*
|
*
|
||||||
* @param keys keys
|
* @param keys keys
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
|
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
|
||||||
keys.toList().let {
|
keys.toList().let {
|
||||||
require(it.isNotEmpty()) {
|
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
|
||||||
"Set of recipient keys cannot be empty."
|
|
||||||
}
|
|
||||||
it.forEach { key -> addRecipient(key) }
|
it.forEach { key -> addRecipient(key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
|
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
|
||||||
* Per key ring, the selector is applied to select one or more encryption subkeys.
|
* as recipients. Per key ring, the selector is applied to select one or more encryption
|
||||||
|
* subkeys.
|
||||||
*
|
*
|
||||||
* @param keys keys
|
* @param keys keys
|
||||||
* @param selector encryption key selector
|
* @param selector encryption key selector
|
||||||
|
@ -109,9 +112,7 @@ class EncryptionOptions(
|
||||||
*/
|
*/
|
||||||
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
|
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
|
||||||
keys.toList().let {
|
keys.toList().let {
|
||||||
require(it.isNotEmpty()) {
|
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
|
||||||
"Set of recipient keys cannot be empty."
|
|
||||||
}
|
|
||||||
it.forEach { key -> addRecipient(key, selector) }
|
it.forEach { key -> addRecipient(key, selector) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,19 +126,25 @@ class EncryptionOptions(
|
||||||
fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector)
|
fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a recipient by providing a key and recipient user-id.
|
* Add a recipient by providing a key and recipient user-id. The user-id is used to determine
|
||||||
* The user-id is used to determine the recipients preferences (algorithms etc.).
|
* the recipients preferences (algorithms etc.).
|
||||||
*
|
*
|
||||||
* @param key key ring
|
* @param key key ring
|
||||||
* @param userId user id
|
* @param userId user id
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
|
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
|
||||||
addRecipient(key, userId, encryptionKeySelector)
|
addRecipient(key, userId, encryptionKeySelector)
|
||||||
|
|
||||||
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector) = apply {
|
fun addRecipient(
|
||||||
|
key: PGPPublicKeyRing,
|
||||||
|
userId: CharSequence,
|
||||||
|
encryptionKeySelector: EncryptionKeySelector
|
||||||
|
) = apply {
|
||||||
val info = KeyRingInfo(key, evaluationDate)
|
val info = KeyRingInfo(key, evaluationDate)
|
||||||
val subkeys = encryptionKeySelector.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose))
|
val subkeys =
|
||||||
|
encryptionKeySelector.selectEncryptionSubkeys(
|
||||||
|
info.getEncryptionSubkeys(userId, purpose))
|
||||||
if (subkeys.isEmpty()) {
|
if (subkeys.isEmpty()) {
|
||||||
throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
|
throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
|
||||||
}
|
}
|
||||||
|
@ -155,17 +162,23 @@ class EncryptionOptions(
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addHiddenRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector) = apply {
|
fun addHiddenRecipient(
|
||||||
addAsRecipient(key, selector, true)
|
key: PGPPublicKeyRing,
|
||||||
}
|
selector: EncryptionKeySelector = encryptionKeySelector
|
||||||
|
) = apply { addAsRecipient(key, selector, true) }
|
||||||
|
|
||||||
private fun addAsRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector, wildcardKeyId: Boolean) = apply {
|
private fun addAsRecipient(
|
||||||
|
key: PGPPublicKeyRing,
|
||||||
|
selector: EncryptionKeySelector,
|
||||||
|
wildcardKeyId: Boolean
|
||||||
|
) = apply {
|
||||||
val info = KeyRingInfo(key, evaluationDate)
|
val info = KeyRingInfo(key, evaluationDate)
|
||||||
val primaryKeyExpiration = try {
|
val primaryKeyExpiration =
|
||||||
info.primaryKeyExpirationDate
|
try {
|
||||||
} catch (e: NoSuchElementException) {
|
info.primaryKeyExpirationDate
|
||||||
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
|
} catch (e: NoSuchElementException) {
|
||||||
}
|
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
|
||||||
|
}
|
||||||
|
|
||||||
if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) {
|
if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) {
|
||||||
throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration)
|
throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration)
|
||||||
|
@ -174,10 +187,12 @@ class EncryptionOptions(
|
||||||
var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose))
|
var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose))
|
||||||
|
|
||||||
// There are some legacy keys around without key flags.
|
// There are some legacy keys around without key flags.
|
||||||
// If we allow encryption for those keys, we add valid keys without any key flags, if they are
|
// If we allow encryption for those keys, we add valid keys without any key flags, if they
|
||||||
|
// are
|
||||||
// capable of encryption by means of their algorithm
|
// capable of encryption by means of their algorithm
|
||||||
if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) {
|
if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) {
|
||||||
encryptionSubkeys = info.validSubkeys
|
encryptionSubkeys =
|
||||||
|
info.validSubkeys
|
||||||
.filter { it.isEncryptionKey }
|
.filter { it.isEncryptionKey }
|
||||||
.filter { info.getKeyFlagsOf(it.keyID).isEmpty() }
|
.filter { info.getKeyFlagsOf(it.keyID).isEmpty() }
|
||||||
}
|
}
|
||||||
|
@ -194,13 +209,16 @@ class EncryptionOptions(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addRecipientKey(certificate: PGPPublicKeyRing,
|
private fun addRecipientKey(
|
||||||
key: PGPPublicKey,
|
certificate: PGPPublicKeyRing,
|
||||||
wildcardKeyId: Boolean) {
|
key: PGPPublicKey,
|
||||||
|
wildcardKeyId: Boolean
|
||||||
|
) {
|
||||||
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
|
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
|
||||||
addEncryptionMethod(ImplementationFactory.getInstance()
|
addEncryptionMethod(
|
||||||
.getPublicKeyKeyEncryptionMethodGenerator(key)
|
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also {
|
||||||
.also { it.setUseWildcardKeyID(wildcardKeyId) })
|
it.setUseWildcardKeyID(wildcardKeyId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,19 +228,19 @@ class EncryptionOptions(
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun addPassphrase(passphrase: Passphrase) = apply {
|
fun addPassphrase(passphrase: Passphrase) = apply {
|
||||||
require(!passphrase.isEmpty) {
|
require(!passphrase.isEmpty) { "Passphrase MUST NOT be empty." }
|
||||||
"Passphrase MUST NOT be empty."
|
addEncryptionMethod(
|
||||||
}
|
ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
|
||||||
addEncryptionMethod(ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
|
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
|
||||||
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase)
|
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) or {@link
|
||||||
* or {@link PGPKeyEncryptionMethodGenerator} (public key).
|
* PGPKeyEncryptionMethodGenerator} (public key).
|
||||||
*
|
*
|
||||||
* This method is intended for advanced users to allow encryption for specific subkeys.
|
* This method is intended for advanced users to allow encryption for specific subkeys. This can
|
||||||
* This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless.
|
* come in handy for example if data needs to be encrypted to a subkey that's ignored by
|
||||||
|
* PGPainless.
|
||||||
*
|
*
|
||||||
* @param encryptionMethod encryption method
|
* @param encryptionMethod encryption method
|
||||||
* @return this
|
* @return this
|
||||||
|
@ -232,10 +250,9 @@ class EncryptionOptions(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the used symmetric encryption algorithm.
|
* Override the used symmetric encryption algorithm. The symmetric encryption algorithm is used
|
||||||
* The symmetric encryption algorithm is used to encrypt the message itself,
|
* to encrypt the message itself, while the used symmetric key will be encrypted to all
|
||||||
* while the used symmetric key will be encrypted to all recipients using public key
|
* recipients using public key cryptography.
|
||||||
* cryptography.
|
|
||||||
*
|
*
|
||||||
* If the algorithm is not overridden, a suitable algorithm will be negotiated.
|
* If the algorithm is not overridden, a suitable algorithm will be negotiated.
|
||||||
*
|
*
|
||||||
|
@ -250,10 +267,10 @@ class EncryptionOptions(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption
|
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will
|
||||||
* for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket.
|
* allow encryption for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag}
|
||||||
* This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm
|
* subpacket. This is a workaround for dealing with legacy keys that have no key flags subpacket
|
||||||
* type to convey the subkeys use.
|
* but rely on the key algorithm type to convey the subkeys use.
|
||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
|
@ -263,20 +280,16 @@ class EncryptionOptions(
|
||||||
|
|
||||||
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
|
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
|
||||||
|
|
||||||
|
|
||||||
fun interface EncryptionKeySelector {
|
fun interface EncryptionKeySelector {
|
||||||
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey>
|
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey>
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun get() = EncryptionOptions()
|
||||||
fun get() = EncryptionOptions()
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
|
||||||
fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
|
||||||
fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only encrypt to the first valid encryption capable subkey we stumble upon.
|
* Only encrypt to the first valid encryption capable subkey we stumble upon.
|
||||||
|
@ -285,7 +298,8 @@ class EncryptionOptions(
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys ->
|
fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys ->
|
||||||
encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() }
|
encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt to any valid, encryption capable subkey on the key ring.
|
* Encrypt to any valid, encryption capable subkey on the key ring.
|
||||||
|
@ -293,6 +307,8 @@ class EncryptionOptions(
|
||||||
* @return encryption key selector
|
* @return encryption key selector
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> encryptionCapableKeys }
|
fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys ->
|
||||||
|
encryptionCapableKeys
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.extensions.matches
|
import org.bouncycastle.extensions.matches
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData
|
import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
|
@ -13,21 +14,20 @@ import org.pgpainless.algorithm.StreamEncoding
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
import org.pgpainless.util.MultiMap
|
import org.pgpainless.util.MultiMap
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class EncryptionResult(
|
data class EncryptionResult(
|
||||||
val encryptionAlgorithm: SymmetricKeyAlgorithm,
|
val encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||||
val compressionAlgorithm: CompressionAlgorithm,
|
val compressionAlgorithm: CompressionAlgorithm,
|
||||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>,
|
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>,
|
||||||
val recipients: Set<SubkeyIdentifier>,
|
val recipients: Set<SubkeyIdentifier>,
|
||||||
val fileName: String,
|
val fileName: String,
|
||||||
val modificationDate: Date,
|
val modificationDate: Date,
|
||||||
val fileEncoding: StreamEncoding
|
val fileEncoding: StreamEncoding
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the message is marked as for-your-eyes-only.
|
* Return true, if the message is marked as for-your-eyes-only. This is typically done by
|
||||||
* This is typically done by setting the filename "_CONSOLE".
|
* setting the filename "_CONSOLE".
|
||||||
*
|
*
|
||||||
* @return is message for your eyes only?
|
* @return is message for your eyes only?
|
||||||
*/
|
*/
|
||||||
|
@ -48,8 +48,7 @@ data class EncryptionResult(
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun builder() = Builder()
|
||||||
fun builder() = Builder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
@ -70,32 +69,35 @@ data class EncryptionResult(
|
||||||
_compressionAlgorithm = compressionAlgorithm
|
_compressionAlgorithm = compressionAlgorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFileName(fileName: String) = apply {
|
fun setFileName(fileName: String) = apply { _fileName = fileName }
|
||||||
_fileName = fileName
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setModificationDate(modificationDate: Date) = apply {
|
fun setModificationDate(modificationDate: Date) = apply {
|
||||||
_modificationDate = modificationDate
|
_modificationDate = modificationDate
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFileEncoding(encoding: StreamEncoding) = apply {
|
fun setFileEncoding(encoding: StreamEncoding) = apply { _encoding = encoding }
|
||||||
_encoding = encoding
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addRecipient(recipient: SubkeyIdentifier) = apply {
|
fun addRecipient(recipient: SubkeyIdentifier) = apply {
|
||||||
(recipients as MutableSet).add(recipient)
|
(recipients as MutableSet).add(recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addDetachedSignature(signingSubkeyIdentifier: SubkeyIdentifier, detachedSignature: PGPSignature) = apply {
|
fun addDetachedSignature(
|
||||||
detachedSignatures.put(signingSubkeyIdentifier, detachedSignature)
|
signingSubkeyIdentifier: SubkeyIdentifier,
|
||||||
}
|
detachedSignature: PGPSignature
|
||||||
|
) = apply { detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) }
|
||||||
|
|
||||||
fun build(): EncryptionResult {
|
fun build(): EncryptionResult {
|
||||||
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
|
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
|
||||||
checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." }
|
checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." }
|
||||||
|
|
||||||
return EncryptionResult(_encryptionAlgorithm!!, _compressionAlgorithm!!, detachedSignatures, recipients,
|
return EncryptionResult(
|
||||||
_fileName, _modificationDate, _encoding)
|
_encryptionAlgorithm!!,
|
||||||
|
_compressionAlgorithm!!,
|
||||||
|
detachedSignatures,
|
||||||
|
recipients,
|
||||||
|
_fileName,
|
||||||
|
_modificationDate,
|
||||||
|
_encoding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
import org.bouncycastle.bcpg.ArmoredOutputStream
|
import org.bouncycastle.bcpg.ArmoredOutputStream
|
||||||
import org.bouncycastle.bcpg.BCPGOutputStream
|
import org.bouncycastle.bcpg.BCPGOutputStream
|
||||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator
|
import org.bouncycastle.openpgp.PGPCompressedDataGenerator
|
||||||
|
@ -16,9 +19,6 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.implementation.ImplementationFactory
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory
|
import org.pgpainless.util.ArmoredOutputStreamFactory
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
// 1 << 8 causes wrong partial body length encoding
|
// 1 << 8 causes wrong partial body length encoding
|
||||||
// 1 << 9 fixes this.
|
// 1 << 9 fixes this.
|
||||||
|
@ -30,11 +30,13 @@ const val BUFFER_SIZE = 1 shl 9
|
||||||
* depending on its configuration.
|
* depending on its configuration.
|
||||||
*
|
*
|
||||||
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
||||||
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
|
*
|
||||||
|
* @see <a
|
||||||
|
* href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
|
||||||
*/
|
*/
|
||||||
class EncryptionStream(
|
class EncryptionStream(
|
||||||
private var outermostStream: OutputStream,
|
private var outermostStream: OutputStream,
|
||||||
private val options: ProducerOptions,
|
private val options: ProducerOptions,
|
||||||
) : OutputStream() {
|
) : OutputStream() {
|
||||||
|
|
||||||
private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder()
|
private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder()
|
||||||
|
@ -66,8 +68,8 @@ class EncryptionStream(
|
||||||
|
|
||||||
outermostStream = BufferedOutputStream(outermostStream)
|
outermostStream = BufferedOutputStream(outermostStream)
|
||||||
LOGGER.debug("Wrap encryption output in ASCII armor.")
|
LOGGER.debug("Wrap encryption output in ASCII armor.")
|
||||||
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options)
|
armorOutputStream =
|
||||||
.also { outermostStream = it }
|
ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, PGPException::class)
|
@Throws(IOException::class, PGPException::class)
|
||||||
|
@ -84,9 +86,11 @@ class EncryptionStream(
|
||||||
EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let {
|
EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let {
|
||||||
resultBuilder.setEncryptionAlgorithm(it)
|
resultBuilder.setEncryptionAlgorithm(it)
|
||||||
LOGGER.debug("Encrypt message using symmetric algorithm $it.")
|
LOGGER.debug("Encrypt message using symmetric algorithm $it.")
|
||||||
val encryptedDataGenerator = PGPEncryptedDataGenerator(
|
val encryptedDataGenerator =
|
||||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it)
|
PGPEncryptedDataGenerator(
|
||||||
.apply { setWithIntegrityPacket(true) })
|
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it).apply {
|
||||||
|
setWithIntegrityPacket(true)
|
||||||
|
})
|
||||||
options.encryptionOptions.encryptionMethods.forEach { m ->
|
options.encryptionOptions.encryptionMethods.forEach { m ->
|
||||||
encryptedDataGenerator.addMethod(m)
|
encryptedDataGenerator.addMethod(m)
|
||||||
}
|
}
|
||||||
|
@ -94,8 +98,11 @@ class EncryptionStream(
|
||||||
resultBuilder.addRecipient(r)
|
resultBuilder.addRecipient(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE))
|
publicKeyEncryptedStream =
|
||||||
.also { stream -> outermostStream = stream }
|
encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream
|
||||||
|
->
|
||||||
|
outermostStream = stream
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +114,10 @@ class EncryptionStream(
|
||||||
if (it == CompressionAlgorithm.UNCOMPRESSED) return
|
if (it == CompressionAlgorithm.UNCOMPRESSED) return
|
||||||
|
|
||||||
LOGGER.debug("Compress using $it.")
|
LOGGER.debug("Compress using $it.")
|
||||||
basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream))
|
basicCompressionStream =
|
||||||
.also { stream -> outermostStream = stream }
|
BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream ->
|
||||||
|
outermostStream = stream
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,12 +147,17 @@ class EncryptionStream(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
literalDataGenerator = PGPLiteralDataGenerator().also { gen ->
|
literalDataGenerator =
|
||||||
literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName,
|
PGPLiteralDataGenerator().also { gen ->
|
||||||
options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream ->
|
literalDataStream =
|
||||||
outermostStream = stream
|
gen.open(
|
||||||
|
outermostStream,
|
||||||
|
options.encoding.code,
|
||||||
|
options.fileName,
|
||||||
|
options.modificationDate,
|
||||||
|
ByteArray(BUFFER_SIZE))
|
||||||
|
.also { stream -> outermostStream = stream }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
resultBuilder.apply {
|
resultBuilder.apply {
|
||||||
setFileName(options.fileName)
|
setFileName(options.fileName)
|
||||||
setModificationDate(options.modificationDate)
|
setModificationDate(options.modificationDate)
|
||||||
|
@ -156,39 +170,47 @@ class EncryptionStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareInputEncoding() {
|
private fun prepareInputEncoding() {
|
||||||
outermostStream = CRLFGeneratorStream(
|
outermostStream =
|
||||||
|
CRLFGeneratorStream(
|
||||||
// By buffering here, we drastically improve performance
|
// By buffering here, we drastically improve performance
|
||||||
// Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to
|
// Reason is that CRLFGeneratorStream only implements write(int), so we need
|
||||||
|
// BufferedOutputStream to
|
||||||
// "convert" to write(buf) calls again
|
// "convert" to write(buf) calls again
|
||||||
BufferedOutputStream(outermostStream),
|
BufferedOutputStream(outermostStream),
|
||||||
if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY)
|
if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> {
|
private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> {
|
||||||
return options.signingOptions?.signingMethods?.values
|
return options.signingOptions
|
||||||
?.map { it.hashAlgorithm }?.toSet()
|
?.signingMethods
|
||||||
?.map { it.algorithmId }?.toTypedArray()
|
?.values
|
||||||
?: arrayOf()
|
?.map { it.hashAlgorithm }
|
||||||
|
?.toSet()
|
||||||
|
?.map { it.algorithmId }
|
||||||
|
?.toTypedArray()
|
||||||
|
?: arrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class) override fun write(data: Int) = outermostStream.write(data)
|
||||||
override fun write(data: Int) = outermostStream.write(data)
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size)
|
override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size)
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun write(buffer: ByteArray, off: Int, len: Int) = outermostStream.write(buffer, off, len)
|
override fun write(buffer: ByteArray, off: Int, len: Int) =
|
||||||
|
outermostStream.write(buffer, off, len)
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class) override fun flush() = outermostStream.flush()
|
||||||
override fun flush() = outermostStream.flush()
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (closed) return
|
if (closed) return
|
||||||
|
|
||||||
outermostStream.close()
|
outermostStream.close()
|
||||||
literalDataStream?.apply { flush(); close() }
|
literalDataStream?.apply {
|
||||||
|
flush()
|
||||||
|
close()
|
||||||
|
}
|
||||||
literalDataGenerator?.close()
|
literalDataGenerator?.close()
|
||||||
|
|
||||||
if (options.isCleartextSigned) {
|
if (options.isCleartextSigned) {
|
||||||
|
@ -201,7 +223,7 @@ class EncryptionStream(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writeSignatures()
|
writeSignatures()
|
||||||
} catch (e : PGPException) {
|
} catch (e: PGPException) {
|
||||||
throw IOException("Exception while writing signatures.", e)
|
throw IOException("Exception while writing signatures.", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,14 +260,14 @@ class EncryptionStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
val result: EncryptionResult
|
val result: EncryptionResult
|
||||||
get() = check(closed) { "EncryptionStream must be closed before accessing the result." }
|
get() =
|
||||||
|
check(closed) { "EncryptionStream must be closed before accessing the result." }
|
||||||
.let { resultBuilder.build() }
|
.let { resultBuilder.build() }
|
||||||
|
|
||||||
val isClosed
|
val isClosed
|
||||||
get() = closed
|
get() = closed
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
|
||||||
private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,39 +4,43 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
import org.bouncycastle.crypto.Digest
|
import org.bouncycastle.crypto.Digest
|
||||||
import org.bouncycastle.crypto.Signer
|
import org.bouncycastle.crypto.Signer
|
||||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
|
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
|
||||||
import java.io.OutputStream
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder {
|
abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder {
|
||||||
|
|
||||||
// Copied from BC, required since BCs class is package visible only
|
// Copied from BC, required since BCs class is package visible only
|
||||||
internal class SignerOutputStream(
|
internal class SignerOutputStream(private val signer: Signer) : OutputStream() {
|
||||||
private val signer: Signer
|
|
||||||
) : OutputStream() {
|
|
||||||
override fun write(p0: Int) = signer.update(p0.toByte())
|
override fun write(p0: Int) = signer.update(p0.toByte())
|
||||||
|
|
||||||
override fun write(b: ByteArray) = signer.update(b, 0, b.size)
|
override fun write(b: ByteArray) = signer.update(b, 0, b.size)
|
||||||
|
|
||||||
override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len)
|
override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ExistingMessageDigest(
|
internal class ExistingMessageDigest(private val digest: MessageDigest) : Digest {
|
||||||
private val digest: MessageDigest
|
|
||||||
) : Digest {
|
|
||||||
|
|
||||||
override fun getAlgorithmName(): String = digest.algorithm
|
override fun getAlgorithmName(): String = digest.algorithm
|
||||||
|
|
||||||
override fun getDigestSize(): Int = digest.digestLength
|
override fun getDigestSize(): Int = digest.digestLength
|
||||||
|
|
||||||
override fun update(b: Byte) = digest.update(b)
|
override fun update(b: Byte) = digest.update(b)
|
||||||
|
|
||||||
override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf)
|
override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf)
|
||||||
|
|
||||||
override fun doFinal(out: ByteArray, outOff: Int): Int {
|
override fun doFinal(out: ByteArray, outOff: Int): Int {
|
||||||
digest.digest().copyInto(out, outOff)
|
digest.digest().copyInto(out, outOff)
|
||||||
return digestSize
|
return digestSize
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
// Nope!
|
// Nope!
|
||||||
// We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset
|
// We cannot reset, since BCs signer classes are resetting in their init() methods,
|
||||||
|
// which would also reset
|
||||||
// the messageDigest, losing its state. This would shatter our intention.
|
// the messageDigest, losing its state. This would shatter our intention.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,17 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData
|
import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
import org.pgpainless.PGPainless
|
import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||||
import org.pgpainless.algorithm.StreamEncoding
|
import org.pgpainless.algorithm.StreamEncoding
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class ProducerOptions private constructor(
|
class ProducerOptions
|
||||||
val encryptionOptions: EncryptionOptions?,
|
private constructor(
|
||||||
val signingOptions: SigningOptions?) {
|
val encryptionOptions: EncryptionOptions?,
|
||||||
|
val signingOptions: SigningOptions?
|
||||||
|
) {
|
||||||
|
|
||||||
private var _fileName: String = ""
|
private var _fileName: String = ""
|
||||||
private var _modificationDate: Date = PGPLiteralData.NOW
|
private var _modificationDate: Date = PGPLiteralData.NOW
|
||||||
|
@ -22,14 +24,15 @@ class ProducerOptions private constructor(
|
||||||
private var _hideArmorHeaders = false
|
private var _hideArmorHeaders = false
|
||||||
var isDisableAsciiArmorCRC = false
|
var isDisableAsciiArmorCRC = false
|
||||||
|
|
||||||
private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm
|
private var _compressionAlgorithmOverride: CompressionAlgorithm =
|
||||||
|
PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm
|
||||||
private var asciiArmor = true
|
private var asciiArmor = true
|
||||||
private var _comment: String? = null
|
private var _comment: String? = null
|
||||||
private var _version: String? = null
|
private var _version: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify, whether the result of the encryption/signing operation shall be ascii armored.
|
* Specify, whether the result of the encryption/signing operation shall be ascii armored. The
|
||||||
* The default value is true.
|
* default value is true.
|
||||||
*
|
*
|
||||||
* @param asciiArmor ascii armor
|
* @param asciiArmor ascii armor
|
||||||
* @return builder
|
* @return builder
|
||||||
|
@ -50,19 +53,15 @@ class ProducerOptions private constructor(
|
||||||
get() = asciiArmor
|
get() = asciiArmor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the comment header in ASCII armored output.
|
* Set the comment header in ASCII armored output. The default value is null, which means no
|
||||||
* The default value is null, which means no comment header is added.
|
* comment header is added. Multiline comments are possible using '\\n'. <br> Note: If a default
|
||||||
* Multiline comments are possible using '\\n'.
|
* header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], then
|
||||||
* <br>
|
* both comments will be written to the produced ASCII armor.
|
||||||
* Note: If a default header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment],
|
|
||||||
* then both comments will be written to the produced ASCII armor.
|
|
||||||
*
|
*
|
||||||
* @param comment comment header text
|
* @param comment comment header text
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
fun setComment(comment: String?) = apply {
|
fun setComment(comment: String?) = apply { _comment = comment }
|
||||||
_comment = comment
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return comment set for header in ascii armored output.
|
* Return comment set for header in ascii armored output.
|
||||||
|
@ -80,18 +79,15 @@ class ProducerOptions private constructor(
|
||||||
fun hasComment() = _comment != null
|
fun hasComment() = _comment != null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the version header in ASCII armored output.
|
* Set the version header in ASCII armored output. The default value is null, which means no
|
||||||
* The default value is null, which means no version header is added.
|
* version header is added. <br> Note: If the value is non-null, then this method overrides the
|
||||||
* <br>
|
* default version header set using
|
||||||
* Note: If the value is non-null, then this method overrides the default version header set using
|
|
||||||
* [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo].
|
* [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo].
|
||||||
*
|
*
|
||||||
* @param version version header, or null for no version info.
|
* @param version version header, or null for no version info.
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
fun setVersion(version: String?) = apply {
|
fun setVersion(version: String?) = apply { _version = version }
|
||||||
_version = version
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the version info header in ascii armored output.
|
* Return the version info header in ascii armored output.
|
||||||
|
@ -128,15 +124,13 @@ class ProducerOptions private constructor(
|
||||||
get() = cleartextSigned
|
get() = cleartextSigned
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the name of the encrypted file.
|
* Set the name of the encrypted file. Note: This option cannot be used simultaneously with
|
||||||
* Note: This option cannot be used simultaneously with [setForYourEyesOnly].
|
* [setForYourEyesOnly].
|
||||||
*
|
*
|
||||||
* @param fileName name of the encrypted file
|
* @param fileName name of the encrypted file
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun setFileName(fileName: String) = apply {
|
fun setFileName(fileName: String) = apply { _fileName = fileName }
|
||||||
_fileName = fileName
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the encrypted files name.
|
* Return the encrypted files name.
|
||||||
|
@ -147,17 +141,15 @@ class ProducerOptions private constructor(
|
||||||
get() = _fileName
|
get() = _fileName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the encrypted message as for-your-eyes-only by setting a special file name.
|
* Mark the encrypted message as for-your-eyes-only by setting a special file name. Note:
|
||||||
* Note: Therefore this method cannot be used simultaneously with [setFileName].
|
* Therefore this method cannot be used simultaneously with [setFileName].
|
||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in
|
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this
|
||||||
* newly generated literal data packets
|
* special filename in newly generated literal data packets
|
||||||
*/
|
*/
|
||||||
@Deprecated("Signaling using special file name is discouraged.")
|
@Deprecated("Signaling using special file name is discouraged.")
|
||||||
fun setForYourEyesOnly() = apply {
|
fun setForYourEyesOnly() = apply { _fileName = PGPLiteralData.CONSOLE }
|
||||||
_fileName = PGPLiteralData.CONSOLE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the modification date of the encrypted file.
|
* Set the modification date of the encrypted file.
|
||||||
|
@ -165,9 +157,7 @@ class ProducerOptions private constructor(
|
||||||
* @param modificationDate Modification date of the encrypted file.
|
* @param modificationDate Modification date of the encrypted file.
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun setModificationDate(modificationDate: Date) = apply {
|
fun setModificationDate(modificationDate: Date) = apply { _modificationDate = modificationDate }
|
||||||
_modificationDate = modificationDate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the modification date of the encrypted file.
|
* Return the modification date of the encrypted file.
|
||||||
|
@ -178,40 +168,32 @@ class ProducerOptions private constructor(
|
||||||
get() = _modificationDate
|
get() = _modificationDate
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set format metadata field of the literal data packet.
|
* Set format metadata field of the literal data packet. Defaults to [StreamEncoding.BINARY].
|
||||||
* Defaults to [StreamEncoding.BINARY].
|
* <br> This does not change the encoding of the wrapped data itself. To apply CR/LF encoding to
|
||||||
* <br>
|
* your input data before processing, use [applyCRLFEncoding] instead.
|
||||||
* This does not change the encoding of the wrapped data itself.
|
|
||||||
* To apply CR/LF encoding to your input data before processing, use [applyCRLFEncoding] instead.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
|
||||||
*
|
*
|
||||||
* @param encoding encoding
|
* @param encoding encoding
|
||||||
* @return this
|
* @return this
|
||||||
*
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9.
|
||||||
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged.
|
* Literal Data Packet</a>
|
||||||
|
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are
|
||||||
|
* discouraged.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Options other than BINARY are discouraged.")
|
@Deprecated("Options other than BINARY are discouraged.")
|
||||||
fun setEncoding(encoding: StreamEncoding) = apply {
|
fun setEncoding(encoding: StreamEncoding) = apply { encodingField = encoding }
|
||||||
encodingField = encoding
|
|
||||||
}
|
|
||||||
|
|
||||||
val encoding: StreamEncoding
|
val encoding: StreamEncoding
|
||||||
get() = encodingField
|
get() = encodingField
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply special encoding of line endings to the input data.
|
* Apply special encoding of line endings to the input data. By default, this is disabled, which
|
||||||
* By default, this is disabled, which means that the data is not altered.
|
* means that the data is not altered. <br> Enabling it will change the line endings to CR/LF.
|
||||||
* <br>
|
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will
|
||||||
* Enabling it will change the line endings to CR/LF.
|
* result in the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
|
||||||
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in
|
|
||||||
* the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
|
|
||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun applyCRLFEncoding() = apply {
|
fun applyCRLFEncoding() = apply { applyCRLFEncoding = true }
|
||||||
applyCRLFEncoding = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the input encoding that will be applied before signing / encryption.
|
* Return the input encoding that will be applied before signing / encryption.
|
||||||
|
@ -239,9 +221,8 @@ class ProducerOptions private constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to `true`, armor headers like version or comments will be omitted from armored output.
|
* If set to `true`, armor headers like version or comments will be omitted from armored output.
|
||||||
* By default, armor headers are not hidden.
|
* By default, armor headers are not hidden. Note: If comments are added via [setComment], those
|
||||||
* Note: If comments are added via [setComment], those are not omitted, even if
|
* are not omitted, even if [hideArmorHeaders] is set to `true`.
|
||||||
* [hideArmorHeaders] is set to `true`.
|
|
||||||
*
|
*
|
||||||
* @param hideArmorHeaders true or false
|
* @param hideArmorHeaders true or false
|
||||||
* @return this
|
* @return this
|
||||||
|
@ -260,7 +241,7 @@ class ProducerOptions private constructor(
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun signAndEncrypt(encryptionOptions: EncryptionOptions, signingOptions: SigningOptions) =
|
fun signAndEncrypt(encryptionOptions: EncryptionOptions, signingOptions: SigningOptions) =
|
||||||
ProducerOptions(encryptionOptions, signingOptions)
|
ProducerOptions(encryptionOptions, signingOptions)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign some data without encryption.
|
* Sign some data without encryption.
|
||||||
|
@ -268,8 +249,7 @@ class ProducerOptions private constructor(
|
||||||
* @param signingOptions signing options
|
* @param signingOptions signing options
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
|
||||||
fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt some data without signing.
|
* Encrypt some data without signing.
|
||||||
|
@ -281,12 +261,10 @@ class ProducerOptions private constructor(
|
||||||
fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null)
|
fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only wrap the data in an OpenPGP packet.
|
* Only wrap the data in an OpenPGP packet. No encryption or signing will be applied.
|
||||||
* No encryption or signing will be applied.
|
|
||||||
*
|
*
|
||||||
* @return builder
|
* @return builder
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun noEncryptionNoSigning() = ProducerOptions(null, null)
|
||||||
fun noEncryptionNoSigning() = ProducerOptions(null, null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,20 @@ package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
/**
|
/** OutputStream which has the task of updating signature generators for written data. */
|
||||||
* OutputStream which has the task of updating signature generators for written data.
|
|
||||||
*/
|
|
||||||
class SignatureGenerationStream(
|
class SignatureGenerationStream(
|
||||||
private val wrapped: OutputStream,
|
private val wrapped: OutputStream,
|
||||||
private val options: SigningOptions?
|
private val options: SigningOptions?
|
||||||
) : OutputStream() {
|
) : OutputStream() {
|
||||||
|
|
||||||
override fun close() = wrapped.close()
|
override fun close() = wrapped.close()
|
||||||
|
|
||||||
override fun flush() = wrapped.flush()
|
override fun flush() = wrapped.flush()
|
||||||
|
|
||||||
override fun write(b: Int) {
|
override fun write(b: Int) {
|
||||||
wrapped.write(b)
|
wrapped.write(b)
|
||||||
options?.run {
|
options?.run {
|
||||||
signingMethods.values.forEach {
|
signingMethods.values.forEach { it.signatureGenerator.update((b and 0xff).toByte()) }
|
||||||
it.signatureGenerator.update((b and 0xff).toByte())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,10 +27,6 @@ class SignatureGenerationStream(
|
||||||
|
|
||||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||||
wrapped.write(b, off, len)
|
wrapped.write(b, off, len)
|
||||||
options?.run {
|
options?.run { signingMethods.values.forEach { it.signatureGenerator.update(b, off, len) } }
|
||||||
signingMethods.values.forEach {
|
|
||||||
it.signatureGenerator.update(b, off, len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.encryption_signing
|
package org.pgpainless.encryption_signing
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.extensions.unlock
|
import org.bouncycastle.extensions.unlock
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.pgpainless.PGPainless.Companion.getPolicy
|
import org.pgpainless.PGPainless.Companion.getPolicy
|
||||||
|
@ -22,7 +23,6 @@ import org.pgpainless.policy.Policy
|
||||||
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback
|
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SigningOptions {
|
class SigningOptions {
|
||||||
|
|
||||||
|
@ -34,10 +34,10 @@ class SigningOptions {
|
||||||
get() = _hashAlgorithmOverride
|
get() = _hashAlgorithmOverride
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
|
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used. If no
|
||||||
* If no override has been set, an acceptable algorithm will be negotiated instead.
|
* override has been set, an acceptable algorithm will be negotiated instead. Note: To override
|
||||||
* Note: To override the hash algorithm for signing, call this method *before* calling
|
* the hash algorithm for signing, call this method *before* calling [addInlineSignature] or
|
||||||
* [addInlineSignature] or [addDetachedSignature].
|
* [addDetachedSignature].
|
||||||
*
|
*
|
||||||
* @param hashAlgorithmOverride override hash algorithm
|
* @param hashAlgorithmOverride override hash algorithm
|
||||||
* @return this
|
* @return this
|
||||||
|
@ -55,9 +55,7 @@ class SigningOptions {
|
||||||
* @param evaluationDate new evaluation date
|
* @param evaluationDate new evaluation date
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
fun setEvaluationDate(evaluationDate: Date) = apply {
|
fun setEvaluationDate(evaluationDate: Date) = apply { _evaluationDate = evaluationDate }
|
||||||
_evaluationDate = evaluationDate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign the message using an inline signature made by the provided signing key.
|
* Sign the message using an inline signature made by the provided signing key.
|
||||||
|
@ -65,14 +63,15 @@ class SigningOptions {
|
||||||
* @param signingKeyProtector protector to unlock the signing key
|
* @param signingKeyProtector protector to unlock the signing key
|
||||||
* @param signingKey key ring containing the signing key
|
* @param signingKey key ring containing the signing key
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
* @throws KeyException if something is wrong with the key
|
||||||
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
|
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply {
|
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) =
|
||||||
addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
|
apply {
|
||||||
}
|
addInlineSignature(
|
||||||
|
signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add inline signatures with all secret key rings in the provided secret key ring collection.
|
* 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 signingKeys collection of signing keys
|
||||||
* @param signatureType type of signature (binary, canonical text)
|
* @param signatureType type of signature (binary, canonical text)
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with any of the keys
|
* @throws KeyException if something is wrong with any of the keys
|
||||||
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
|
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be
|
||||||
|
* created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector,
|
fun addInlineSignatures(
|
||||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
signatureType: DocumentSignatureType) = apply {
|
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||||
signingKeys.forEach {
|
signatureType: DocumentSignatureType
|
||||||
addInlineSignature(signingKeyProtector, it, null, signatureType)
|
) = apply {
|
||||||
}
|
signingKeys.forEach { addInlineSignature(signingKeyProtector, it, null, signatureType) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an inline-signature.
|
* Add an inline-signature. Inline signatures are being embedded into the message itself and can
|
||||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
* be processed in one pass, thanks to the use of one-pass-signature packets.
|
||||||
* of one-pass-signature packets.
|
|
||||||
*
|
*
|
||||||
* @param signingKeyProtector decryptor to unlock the signing secret key
|
* @param signingKeyProtector decryptor to unlock the signing secret key
|
||||||
* @param signingKey signing key
|
* @param signingKey signing key
|
||||||
* @param signatureType type of signature (binary, canonical text)
|
* @param signatureType type of signature (binary, canonical text)
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
* @throws KeyException if something is wrong with the key
|
||||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addInlineSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
signatureType: DocumentSignatureType) = apply {
|
signingKey: PGPSecretKeyRing,
|
||||||
addInlineSignature(signingKeyProtector, signingKey, null, signatureType)
|
signatureType: DocumentSignatureType
|
||||||
}
|
) = apply { addInlineSignature(signingKeyProtector, signingKey, null, signatureType) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an inline-signature.
|
* Add an inline-signature. Inline signatures are being embedded into the message itself and can
|
||||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
* be processed in one pass, thanks to the use of one-pass-signature packets.
|
||||||
* of one-pass-signature packets.
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||||
*
|
*
|
||||||
|
@ -126,27 +122,28 @@ class SigningOptions {
|
||||||
* @param signingKey signing key
|
* @param signingKey signing key
|
||||||
* @param userId user-id of the signer
|
* @param userId user-id of the signer
|
||||||
* @param signatureType signature type (binary, canonical text)
|
* @param signatureType signature type (binary, canonical text)
|
||||||
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
|
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the
|
||||||
|
* signature
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if the key is invalid
|
* @throws KeyException if the key is invalid
|
||||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addInlineSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
userId: CharSequence? = null,
|
signingKey: PGPSecretKeyRing,
|
||||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
userId: CharSequence? = null,
|
||||||
subpacketsCallback: Callback? = null) = apply {
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null
|
||||||
|
) = apply {
|
||||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||||
throw UnboundUserIdException(
|
throw UnboundUserIdException(
|
||||||
of(signingKey),
|
of(signingKey),
|
||||||
userId.toString(),
|
userId.toString(),
|
||||||
keyRingInfo.getLatestUserIdCertification(userId),
|
keyRingInfo.getLatestUserIdCertification(userId),
|
||||||
keyRingInfo.getUserIdRevocation(userId)
|
keyRingInfo.getUserIdRevocation(userId))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
@ -155,14 +152,16 @@ class SigningOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (signingPubKey in signingPubKeys) {
|
for (signingPubKey in signingPubKeys) {
|
||||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
val signingSecKey: PGPSecretKey =
|
||||||
|
signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||||
val hashAlgorithms =
|
val hashAlgorithms =
|
||||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
addSigningMethod(
|
||||||
|
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,17 +174,22 @@ class SigningOptions {
|
||||||
* @param signatureType signature type
|
* @param signatureType signature type
|
||||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||||
* @return builder
|
* @return builder
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
* created.
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
|
||||||
|
* signing-capable subkeys
|
||||||
|
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
|
||||||
|
* identified secret key
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addInlineSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
keyId: Long,
|
signingKey: PGPSecretKeyRing,
|
||||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
keyId: Long,
|
||||||
subpacketsCallback: Callback? = null) = apply {
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null
|
||||||
|
) = apply {
|
||||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
if (signingPubKeys.isEmpty()) {
|
if (signingPubKeys.isEmpty()) {
|
||||||
|
@ -197,12 +201,14 @@ class SigningOptions {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID)
|
val signingSecKey =
|
||||||
|
signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
val signingSubkey = signingSecKey.unlock(signingKeyProtector)
|
val signingSubkey = signingSecKey.unlock(signingKeyProtector)
|
||||||
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
addSigningMethod(
|
||||||
|
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
throw MissingSecretKeyException(of(signingKey), keyId)
|
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||||
|
@ -215,45 +221,44 @@ class SigningOptions {
|
||||||
* @param signingKeys collection of signing key rings
|
* @param signingKeys collection of signing key rings
|
||||||
* @param signatureType type of the signature (binary, canonical text)
|
* @param signatureType type of the signature (binary, canonical text)
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with any of the keys
|
* @throws KeyException if something is wrong with any of the keys
|
||||||
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
|
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing
|
||||||
|
* method cannot be created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector,
|
fun addDetachedSignatures(
|
||||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
signatureType: DocumentSignatureType) = apply {
|
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||||
signingKeys.forEach {
|
signatureType: DocumentSignatureType
|
||||||
addDetachedSignature(signingKeyProtector, it, null, signatureType)
|
) = apply {
|
||||||
}
|
signingKeys.forEach { addDetachedSignature(signingKeyProtector, it, null, signatureType) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a detached signature.
|
* Create a detached signature. Detached signatures are not being added into the PGP message
|
||||||
* Detached signatures are not being added into the PGP message itself.
|
* itself. Instead, they can be distributed separately to the message. Detached signatures are
|
||||||
* Instead, they can be distributed separately to the message.
|
* useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
|
||||||
*
|
*
|
||||||
* @param signingKeyProtector decryptor to unlock the secret signing key
|
* @param signingKeyProtector decryptor to unlock the secret signing key
|
||||||
* @param signingKey signing key
|
* @param signingKey signing key
|
||||||
* @param signatureType type of data that is signed (binary, canonical text)
|
* @param signatureType type of data that is signed (binary, canonical text)
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
* @throws KeyException if something is wrong with the key
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method
|
||||||
|
* can be created
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addDetachedSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
signatureType: DocumentSignatureType) = apply {
|
signingKey: PGPSecretKeyRing,
|
||||||
addDetachedSignature(signingKeyProtector, signingKey, null, signatureType)
|
signatureType: DocumentSignatureType
|
||||||
}
|
) = apply { addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a detached signature.
|
* Create a detached signature. Detached signatures are not being added into the PGP message
|
||||||
* Detached signatures are not being added into the PGP message itself.
|
* itself. Instead, they can be distributed separately to the message. Detached signatures are
|
||||||
* Instead, they can be distributed separately to the message.
|
* useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||||
*
|
*
|
||||||
|
@ -263,25 +268,26 @@ class SigningOptions {
|
||||||
* @param signatureType type of data that is signed (binary, canonical text)
|
* @param signatureType type of data that is signed (binary, canonical text)
|
||||||
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
|
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
|
||||||
* @return this
|
* @return this
|
||||||
*
|
|
||||||
* @throws KeyException if something is wrong with the key
|
* @throws KeyException if something is wrong with the key
|
||||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method
|
||||||
|
* can be created
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addDetachedSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
userId: String? = null,
|
signingKey: PGPSecretKeyRing,
|
||||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
userId: String? = null,
|
||||||
subpacketCallback: Callback? = null) = apply {
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketCallback: Callback? = null
|
||||||
|
) = apply {
|
||||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||||
throw UnboundUserIdException(
|
throw UnboundUserIdException(
|
||||||
of(signingKey),
|
of(signingKey),
|
||||||
userId.toString(),
|
userId.toString(),
|
||||||
keyRingInfo.getLatestUserIdCertification(userId),
|
keyRingInfo.getLatestUserIdCertification(userId),
|
||||||
keyRingInfo.getUserIdRevocation(userId)
|
keyRingInfo.getUserIdRevocation(userId))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
@ -290,14 +296,16 @@ class SigningOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (signingPubKey in signingPubKeys) {
|
for (signingPubKey in signingPubKeys) {
|
||||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
val signingSecKey: PGPSecretKey =
|
||||||
|
signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||||
val hashAlgorithms =
|
val hashAlgorithms =
|
||||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
|
addSigningMethod(
|
||||||
|
signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,17 +318,22 @@ class SigningOptions {
|
||||||
* @param signatureType signature type
|
* @param signatureType signature type
|
||||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||||
* @return builder
|
* @return builder
|
||||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be
|
||||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
* created.
|
||||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
|
||||||
|
* signing-capable subkeys
|
||||||
|
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
|
||||||
|
* identified secret key
|
||||||
*/
|
*/
|
||||||
@Throws(KeyException::class, PGPException::class)
|
@Throws(KeyException::class, PGPException::class)
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
fun addDetachedSignature(
|
||||||
signingKey: PGPSecretKeyRing,
|
signingKeyProtector: SecretKeyRingProtector,
|
||||||
keyId: Long,
|
signingKey: PGPSecretKeyRing,
|
||||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
keyId: Long,
|
||||||
subpacketsCallback: Callback? = null) = apply {
|
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||||
|
subpacketsCallback: Callback? = null
|
||||||
|
) = apply {
|
||||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||||
|
|
||||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||||
|
@ -330,12 +343,20 @@ class SigningOptions {
|
||||||
|
|
||||||
for (signingPubKey in signingPubKeys) {
|
for (signingPubKey in signingPubKeys) {
|
||||||
if (signingPubKey.keyID == keyId) {
|
if (signingPubKey.keyID == keyId) {
|
||||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
val signingSecKey: PGPSecretKey =
|
||||||
|
signingKey.getSecretKey(signingPubKey.keyID)
|
||||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||||
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
val hashAlgorithm: HashAlgorithm =
|
||||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback)
|
negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||||
|
addSigningMethod(
|
||||||
|
signingKey,
|
||||||
|
signingSubkey,
|
||||||
|
hashAlgorithm,
|
||||||
|
signatureType,
|
||||||
|
true,
|
||||||
|
subpacketsCallback)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,26 +364,30 @@ class SigningOptions {
|
||||||
throw MissingSecretKeyException(of(signingKey), keyId)
|
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addSigningMethod(signingKey: PGPSecretKeyRing,
|
private fun addSigningMethod(
|
||||||
signingSubkey: PGPPrivateKey,
|
signingKey: PGPSecretKeyRing,
|
||||||
hashAlgorithm: HashAlgorithm,
|
signingSubkey: PGPPrivateKey,
|
||||||
signatureType: DocumentSignatureType,
|
hashAlgorithm: HashAlgorithm,
|
||||||
detached: Boolean,
|
signatureType: DocumentSignatureType,
|
||||||
subpacketCallback: Callback? = null) {
|
detached: Boolean,
|
||||||
|
subpacketCallback: Callback? = null
|
||||||
|
) {
|
||||||
val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID)
|
val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID)
|
||||||
val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID)
|
val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID)
|
||||||
val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm)
|
val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm)
|
||||||
val bitStrength = signingSecretKey.publicKey.bitStrength
|
val bitStrength = signingSecretKey.publicKey.bitStrength
|
||||||
if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) {
|
if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) {
|
||||||
throw UnacceptableSigningKeyException(
|
throw UnacceptableSigningKeyException(
|
||||||
PublicKeyAlgorithmPolicyException(
|
PublicKeyAlgorithmPolicyException(
|
||||||
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
|
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
|
||||||
}
|
}
|
||||||
|
|
||||||
val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
|
val generator: PGPSignatureGenerator =
|
||||||
|
createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
|
||||||
|
|
||||||
// Subpackets
|
// Subpackets
|
||||||
val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
|
val hashedSubpackets =
|
||||||
|
SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
|
||||||
val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets()
|
val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets()
|
||||||
if (subpacketCallback != null) {
|
if (subpacketCallback != null) {
|
||||||
subpacketCallback.modifyHashedSubpackets(hashedSubpackets)
|
subpacketCallback.modifyHashedSubpackets(hashedSubpackets)
|
||||||
|
@ -372,80 +397,87 @@ class SigningOptions {
|
||||||
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets))
|
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets))
|
||||||
|
|
||||||
val signingMethod =
|
val signingMethod =
|
||||||
if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm)
|
if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm)
|
||||||
else SigningMethod.inlineSignature(generator, hashAlgorithm)
|
else SigningMethod.inlineSignature(generator, hashAlgorithm)
|
||||||
(signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod
|
(signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Negotiate, which hash algorithm to use.
|
* Negotiate, which hash algorithm to use.
|
||||||
*
|
*
|
||||||
*
|
* This method gives the highest priority to the algorithm override, which can be set via
|
||||||
* This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm].
|
* [.overrideHashAlgorithm]. After that, the signing keys hash algorithm preferences are
|
||||||
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
|
* iterated to find the first acceptable algorithm. Lastly, should no acceptable algorithm be
|
||||||
* Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is
|
* found, the [Policies][Policy] default signature hash algorithm is used as a fallback.
|
||||||
* used as a fallback.
|
|
||||||
*
|
*
|
||||||
* @param preferences preferences
|
* @param preferences preferences
|
||||||
* @param policy policy
|
* @param policy policy
|
||||||
* @return selected hash algorithm
|
* @return selected hash algorithm
|
||||||
*/
|
*/
|
||||||
private fun negotiateHashAlgorithm(preferences: Set<HashAlgorithm>,
|
private fun negotiateHashAlgorithm(
|
||||||
policy: Policy): HashAlgorithm {
|
preferences: Set<HashAlgorithm>,
|
||||||
return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
|
policy: Policy
|
||||||
|
): HashAlgorithm {
|
||||||
|
return _hashAlgorithmOverride
|
||||||
|
?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PGPException::class)
|
@Throws(PGPException::class)
|
||||||
private fun createSignatureGenerator(privateKey: PGPPrivateKey,
|
private fun createSignatureGenerator(
|
||||||
hashAlgorithm: HashAlgorithm,
|
privateKey: PGPPrivateKey,
|
||||||
signatureType: DocumentSignatureType): PGPSignatureGenerator {
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
signatureType: DocumentSignatureType
|
||||||
|
): PGPSignatureGenerator {
|
||||||
return ImplementationFactory.getInstance()
|
return ImplementationFactory.getInstance()
|
||||||
.getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
|
.getPGPContentSignerBuilder(
|
||||||
.let { csb ->
|
privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
|
||||||
PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) }
|
.let { csb ->
|
||||||
|
PGPSignatureGenerator(csb).also {
|
||||||
|
it.init(signatureType.signatureType.code, privateKey)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun get() = SigningOptions()
|
||||||
fun get() = SigningOptions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A method of signing. */
|
||||||
* A method of signing.
|
class SigningMethod
|
||||||
*/
|
private constructor(
|
||||||
class SigningMethod private constructor(
|
val signatureGenerator: PGPSignatureGenerator,
|
||||||
val signatureGenerator: PGPSignatureGenerator,
|
val isDetached: Boolean,
|
||||||
val isDetached: Boolean,
|
val hashAlgorithm: HashAlgorithm
|
||||||
val hashAlgorithm: HashAlgorithm
|
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline-signature method.
|
* Inline-signature method. The resulting signature will be written into the message
|
||||||
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
|
* itself, together with a one-pass-signature packet.
|
||||||
*
|
*
|
||||||
* @param signatureGenerator signature generator
|
* @param signatureGenerator signature generator
|
||||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||||
* @return inline signing method
|
* @return inline signing method
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
fun inlineSignature(
|
||||||
SigningMethod(signatureGenerator, false, hashAlgorithm)
|
signatureGenerator: PGPSignatureGenerator,
|
||||||
|
hashAlgorithm: HashAlgorithm
|
||||||
|
) = SigningMethod(signatureGenerator, false, hashAlgorithm)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detached signing method.
|
* Detached signing method. The resulting signature will not be added to the message,
|
||||||
* The resulting signature will not be added to the message, and instead can be distributed separately
|
* and instead can be distributed separately to the signed message.
|
||||||
* to the signed message.
|
|
||||||
*
|
*
|
||||||
* @param signatureGenerator signature generator
|
* @param signatureGenerator signature generator
|
||||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||||
* @return detached signing method
|
* @return detached signing method
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
fun detachedSignature(
|
||||||
SigningMethod(signatureGenerator, true, hashAlgorithm)
|
signatureGenerator: PGPSignatureGenerator,
|
||||||
|
hashAlgorithm: HashAlgorithm
|
||||||
|
) = SigningMethod(signatureGenerator, true, hashAlgorithm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.implementation
|
package org.pgpainless.implementation
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
||||||
|
@ -27,70 +30,85 @@ import org.pgpainless.algorithm.HashAlgorithm
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import java.io.InputStream
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class BcImplementationFactory : ImplementationFactory() {
|
class BcImplementationFactory : ImplementationFactory() {
|
||||||
override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = BcPGPDigestCalculatorProvider()
|
override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider =
|
||||||
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = BcPGPContentVerifierBuilderProvider()
|
BcPGPDigestCalculatorProvider()
|
||||||
|
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider =
|
||||||
|
BcPGPContentVerifierBuilderProvider()
|
||||||
override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator()
|
override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator()
|
||||||
|
|
||||||
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
override fun getPBESecretKeyEncryptor(
|
||||||
digestCalculator: PGPDigestCalculator,
|
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
digestCalculator: PGPDigestCalculator,
|
||||||
BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
passphrase: Passphrase
|
||||||
.build(passphrase.getChars())
|
): PBESecretKeyEncryptor =
|
||||||
|
BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||||
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm,
|
override fun getPBESecretKeyEncryptor(
|
||||||
hashAlgorithm: HashAlgorithm,
|
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||||
s2kCount: Int,
|
hashAlgorithm: HashAlgorithm,
|
||||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
s2kCount: Int,
|
||||||
BcPBESecretKeyEncryptorBuilder(
|
passphrase: Passphrase
|
||||||
encryptionAlgorithm.algorithmId,
|
): PBESecretKeyEncryptor =
|
||||||
getPGPDigestCalculator(hashAlgorithm),
|
BcPBESecretKeyEncryptorBuilder(
|
||||||
s2kCount)
|
encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
|
||||||
.build(passphrase.getChars())
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
||||||
BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider).build(passphrase.getChars())
|
||||||
.build(passphrase.getChars())
|
|
||||||
|
|
||||||
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder =
|
override fun getPGPContentSignerBuilder(
|
||||||
BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
keyAlgorithm: Int,
|
||||||
|
hashAlgorithm: Int
|
||||||
|
): PGPContentSignerBuilder = BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||||
|
|
||||||
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
||||||
BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider)
|
BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider)
|
||||||
|
|
||||||
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory =
|
override fun getPublicKeyDataDecryptorFactory(
|
||||||
BcPublicKeyDataDecryptorFactory(privateKey)
|
privateKey: PGPPrivateKey
|
||||||
|
): PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(privateKey)
|
||||||
|
|
||||||
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory =
|
override fun getSessionKeyDataDecryptorFactory(
|
||||||
BcSessionKeyDataDecryptorFactory(sessionKey)
|
sessionKey: PGPSessionKey
|
||||||
|
): SessionKeyDataDecryptorFactory = BcSessionKeyDataDecryptorFactory(sessionKey)
|
||||||
|
|
||||||
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator =
|
override fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||||
BcPublicKeyKeyEncryptionMethodGenerator(key)
|
key: PGPPublicKey
|
||||||
|
): PublicKeyKeyEncryptionMethodGenerator = BcPublicKeyKeyEncryptionMethodGenerator(key)
|
||||||
|
|
||||||
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator =
|
override fun getPBEKeyEncryptionMethodGenerator(
|
||||||
BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
passphrase: Passphrase
|
||||||
|
): PBEKeyEncryptionMethodGenerator = BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
||||||
BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
||||||
|
|
||||||
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair =
|
override fun getPGPKeyPair(
|
||||||
BcPGPKeyPair(
|
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||||
publicKeyAlgorithm.algorithmId,
|
keyPair: KeyPair,
|
||||||
jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate),
|
creationDate: Date
|
||||||
creationDate)
|
): 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,
|
private fun jceToBcKeyPair(
|
||||||
keyPair: KeyPair,
|
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||||
creationDate: Date): AsymmetricCipherKeyPair =
|
keyPair: KeyPair,
|
||||||
BcPGPKeyConverter().let { converter ->
|
creationDate: Date
|
||||||
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair ->
|
): AsymmetricCipherKeyPair =
|
||||||
AsymmetricCipherKeyPair(converter.getPublicKey(pair.publicKey), converter.getPrivateKey(pair.privateKey))
|
BcPGPKeyConverter().let { converter ->
|
||||||
}
|
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair ->
|
||||||
|
AsymmetricCipherKeyPair(
|
||||||
|
converter.getPublicKey(pair.publicKey),
|
||||||
|
converter.getPrivateKey(pair.privateKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.implementation
|
package org.pgpainless.implementation
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.bouncycastle.openpgp.operator.*
|
import org.bouncycastle.openpgp.operator.*
|
||||||
import org.pgpainless.algorithm.HashAlgorithm
|
import org.pgpainless.algorithm.HashAlgorithm
|
||||||
|
@ -11,18 +14,13 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import org.pgpainless.util.SessionKey
|
import org.pgpainless.util.SessionKey
|
||||||
import java.io.InputStream
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
abstract class ImplementationFactory {
|
abstract class ImplementationFactory {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic private var instance: ImplementationFactory = BcImplementationFactory()
|
||||||
private var instance: ImplementationFactory = BcImplementationFactory()
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun getInstance() = instance
|
||||||
fun getInstance() = instance
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun setFactoryImplementation(implementation: ImplementationFactory) = apply {
|
fun setFactoryImplementation(implementation: ImplementationFactory) = apply {
|
||||||
|
@ -38,56 +36,82 @@ abstract class ImplementationFactory {
|
||||||
get() = getPGPDigestCalculator(HashAlgorithm.SHA1)
|
get() = getPGPDigestCalculator(HashAlgorithm.SHA1)
|
||||||
|
|
||||||
@Throws(PGPException::class)
|
@Throws(PGPException::class)
|
||||||
abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
abstract fun getPBESecretKeyEncryptor(
|
||||||
digestCalculator: PGPDigestCalculator,
|
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||||
passphrase: Passphrase): PBESecretKeyEncryptor
|
digestCalculator: PGPDigestCalculator,
|
||||||
|
passphrase: Passphrase
|
||||||
|
): PBESecretKeyEncryptor
|
||||||
|
|
||||||
@Throws(PGPException::class)
|
@Throws(PGPException::class)
|
||||||
abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor
|
abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor
|
||||||
|
|
||||||
@Throws(PGPException::class)
|
@Throws(PGPException::class)
|
||||||
abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm,
|
abstract fun getPBESecretKeyEncryptor(
|
||||||
s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor
|
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||||
|
hashAlgorithm: HashAlgorithm,
|
||||||
|
s2kCount: Int,
|
||||||
|
passphrase: Passphrase
|
||||||
|
): PBESecretKeyEncryptor
|
||||||
|
|
||||||
fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator =
|
fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator =
|
||||||
getPGPDigestCalculator(hashAlgorithm.algorithmId)
|
getPGPDigestCalculator(hashAlgorithm.algorithmId)
|
||||||
|
|
||||||
fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator =
|
fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator =
|
||||||
pgpDigestCalculatorProvider.get(hashAlgorithm)
|
pgpDigestCalculatorProvider.get(hashAlgorithm)
|
||||||
|
|
||||||
fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder =
|
fun getPGPContentSignerBuilder(
|
||||||
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
|
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)
|
@Throws(PGPException::class)
|
||||||
abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory
|
abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory
|
||||||
|
|
||||||
abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory
|
abstract fun getPublicKeyDataDecryptorFactory(
|
||||||
|
privateKey: PGPPrivateKey
|
||||||
|
): PublicKeyDataDecryptorFactory
|
||||||
|
|
||||||
fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory =
|
fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory =
|
||||||
getSessionKeyDataDecryptorFactory(PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key))
|
getSessionKeyDataDecryptorFactory(
|
||||||
|
PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key))
|
||||||
|
|
||||||
abstract fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory
|
abstract fun getSessionKeyDataDecryptorFactory(
|
||||||
|
sessionKey: PGPSessionKey
|
||||||
|
): SessionKeyDataDecryptorFactory
|
||||||
|
|
||||||
abstract fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator
|
abstract fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||||
|
key: PGPPublicKey
|
||||||
|
): PublicKeyKeyEncryptionMethodGenerator
|
||||||
|
|
||||||
abstract fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator
|
abstract fun getPBEKeyEncryptionMethodGenerator(
|
||||||
|
passphrase: Passphrase
|
||||||
|
): PBEKeyEncryptionMethodGenerator
|
||||||
|
|
||||||
fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: SymmetricKeyAlgorithm): PGPDataEncryptorBuilder =
|
fun getPGPDataEncryptorBuilder(
|
||||||
getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
|
symmetricKeyAlgorithm: SymmetricKeyAlgorithm
|
||||||
|
): PGPDataEncryptorBuilder = getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
|
||||||
|
|
||||||
abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder
|
abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder
|
||||||
|
|
||||||
@Throws(PGPException::class)
|
@Throws(PGPException::class)
|
||||||
abstract fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair
|
abstract fun getPGPKeyPair(
|
||||||
|
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||||
|
keyPair: KeyPair,
|
||||||
|
creationDate: Date
|
||||||
|
): PGPKeyPair
|
||||||
|
|
||||||
fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory =
|
fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory =
|
||||||
getPGPObjectFactory(bytes.inputStream())
|
getPGPObjectFactory(bytes.inputStream())
|
||||||
|
|
||||||
abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory
|
abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return javaClass.simpleName
|
return javaClass.simpleName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.implementation
|
package org.pgpainless.implementation
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.bouncycastle.openpgp.operator.*
|
import org.bouncycastle.openpgp.operator.*
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
|
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
|
||||||
|
@ -24,79 +27,86 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.provider.ProviderFactory
|
import org.pgpainless.provider.ProviderFactory
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import java.io.InputStream
|
|
||||||
import java.security.KeyPair
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class JceImplementationFactory : ImplementationFactory() {
|
class JceImplementationFactory : ImplementationFactory() {
|
||||||
override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider =
|
override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider =
|
||||||
JcaPGPDigestCalculatorProviderBuilder()
|
JcaPGPDigestCalculatorProviderBuilder().setProvider(ProviderFactory.provider).build()
|
||||||
.setProvider(ProviderFactory.provider)
|
|
||||||
.build()
|
|
||||||
override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider =
|
override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider =
|
||||||
JcaPGPContentVerifierBuilderProvider()
|
JcaPGPContentVerifierBuilderProvider().setProvider(ProviderFactory.provider)
|
||||||
.setProvider(ProviderFactory.provider)
|
|
||||||
override val keyFingerprintCalculator: KeyFingerPrintCalculator =
|
override val keyFingerprintCalculator: KeyFingerPrintCalculator =
|
||||||
JcaKeyFingerprintCalculator()
|
JcaKeyFingerprintCalculator().setProvider(ProviderFactory.provider)
|
||||||
.setProvider(ProviderFactory.provider)
|
|
||||||
|
|
||||||
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
override fun getPBESecretKeyEncryptor(
|
||||||
digestCalculator: PGPDigestCalculator,
|
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
digestCalculator: PGPDigestCalculator,
|
||||||
JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
passphrase: Passphrase
|
||||||
.setProvider(ProviderFactory.provider)
|
): PBESecretKeyEncryptor =
|
||||||
.build(passphrase.getChars())
|
JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||||
|
.setProvider(ProviderFactory.provider)
|
||||||
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm,
|
override fun getPBESecretKeyEncryptor(
|
||||||
hashAlgorithm: HashAlgorithm,
|
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||||
s2kCount: Int,
|
hashAlgorithm: HashAlgorithm,
|
||||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
s2kCount: Int,
|
||||||
JcePBESecretKeyEncryptorBuilder(
|
passphrase: Passphrase
|
||||||
encryptionAlgorithm.algorithmId,
|
): PBESecretKeyEncryptor =
|
||||||
getPGPDigestCalculator(hashAlgorithm),
|
JcePBESecretKeyEncryptorBuilder(
|
||||||
s2kCount)
|
encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
|
||||||
.setProvider(ProviderFactory.provider)
|
.setProvider(ProviderFactory.provider)
|
||||||
.build(passphrase.getChars())
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
||||||
JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
||||||
.setProvider(ProviderFactory.provider)
|
.setProvider(ProviderFactory.provider)
|
||||||
.build(passphrase.getChars())
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder =
|
override fun getPGPContentSignerBuilder(
|
||||||
JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
keyAlgorithm: Int,
|
||||||
.setProvider(ProviderFactory.provider)
|
hashAlgorithm: Int
|
||||||
|
): PGPContentSignerBuilder =
|
||||||
|
JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||||
|
.setProvider(ProviderFactory.provider)
|
||||||
|
|
||||||
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
||||||
JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider)
|
JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider)
|
||||||
.setProvider(ProviderFactory.provider)
|
.setProvider(ProviderFactory.provider)
|
||||||
.build(passphrase.getChars())
|
.build(passphrase.getChars())
|
||||||
|
|
||||||
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory =
|
override fun getPublicKeyDataDecryptorFactory(
|
||||||
JcePublicKeyDataDecryptorFactoryBuilder()
|
privateKey: PGPPrivateKey
|
||||||
.setProvider(ProviderFactory.provider)
|
): PublicKeyDataDecryptorFactory =
|
||||||
.build(privateKey)
|
JcePublicKeyDataDecryptorFactoryBuilder()
|
||||||
|
.setProvider(ProviderFactory.provider)
|
||||||
|
.build(privateKey)
|
||||||
|
|
||||||
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory =
|
override fun getSessionKeyDataDecryptorFactory(
|
||||||
JceSessionKeyDataDecryptorFactoryBuilder()
|
sessionKey: PGPSessionKey
|
||||||
.setProvider(ProviderFactory.provider)
|
): SessionKeyDataDecryptorFactory =
|
||||||
.build(sessionKey)
|
JceSessionKeyDataDecryptorFactoryBuilder()
|
||||||
|
.setProvider(ProviderFactory.provider)
|
||||||
|
.build(sessionKey)
|
||||||
|
|
||||||
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator =
|
override fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||||
JcePublicKeyKeyEncryptionMethodGenerator(key)
|
key: PGPPublicKey
|
||||||
.setProvider(ProviderFactory.provider)
|
): PublicKeyKeyEncryptionMethodGenerator =
|
||||||
|
JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(ProviderFactory.provider)
|
||||||
|
|
||||||
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator =
|
override fun getPBEKeyEncryptionMethodGenerator(
|
||||||
JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
passphrase: Passphrase
|
||||||
.setProvider(ProviderFactory.provider)
|
): PBEKeyEncryptionMethodGenerator =
|
||||||
|
JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||||
|
.setProvider(ProviderFactory.provider)
|
||||||
|
|
||||||
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
||||||
JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm).setProvider(ProviderFactory.provider)
|
||||||
.setProvider(ProviderFactory.provider)
|
|
||||||
|
|
||||||
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair =
|
override fun getPGPKeyPair(
|
||||||
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate)
|
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||||
|
keyPair: KeyPair,
|
||||||
|
creationDate: Date
|
||||||
|
): PGPKeyPair = JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate)
|
||||||
|
|
||||||
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
|
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
|
||||||
PGPObjectFactory(inputStream, keyFingerprintCalculator)
|
PGPObjectFactory(inputStream, keyFingerprintCalculator)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,13 @@
|
||||||
|
|
||||||
package org.pgpainless.key
|
package org.pgpainless.key
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
import org.bouncycastle.util.encoders.Hex
|
import org.bouncycastle.util.encoders.Hex
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
/**
|
/** Abstract super class of different version OpenPGP fingerprints. */
|
||||||
* Abstract super class of different version OpenPGP fingerprints.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
|
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
|
||||||
val fingerprint: String
|
val fingerprint: String
|
||||||
val bytes: ByteArray
|
val bytes: ByteArray
|
||||||
|
@ -26,39 +23,41 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
||||||
abstract fun getVersion(): Int
|
abstract fun getVersion(): Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
|
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. This
|
||||||
* This method can be implemented for V4 and V5 fingerprints.
|
* method can be implemented for V4 and V5 fingerprints. V3 key-IDs cannot be derived from the
|
||||||
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
|
* fingerprint, but we don't care, since V3 is deprecated.
|
||||||
*
|
*
|
||||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
|
||||||
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
|
||||||
* @return key id
|
* @return key id
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2"> RFC-4880 §12.2: Key IDs and
|
||||||
|
* Fingerprints</a>
|
||||||
*/
|
*/
|
||||||
abstract val keyId: Long
|
abstract val keyId: Long
|
||||||
|
|
||||||
constructor(fingerprint: String) {
|
constructor(fingerprint: String) {
|
||||||
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||||
if (!isValid(prep)) {
|
if (!isValid(prep)) {
|
||||||
throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
throw IllegalArgumentException(
|
||||||
|
"Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
||||||
}
|
}
|
||||||
this.fingerprint = prep
|
this.fingerprint = prep
|
||||||
this.bytes = Hex.decode(prep)
|
this.bytes = Hex.decode(prep)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(bytes: ByteArray): this(Hex.toHexString(bytes))
|
constructor(bytes: ByteArray) : this(Hex.toHexString(bytes))
|
||||||
|
|
||||||
constructor(key: PGPPublicKey): this(key.fingerprint) {
|
constructor(key: PGPPublicKey) : this(key.fingerprint) {
|
||||||
if (key.version != getVersion()) {
|
if (key.version != getVersion()) {
|
||||||
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
|
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(key: PGPSecretKey): this(key.publicKey)
|
constructor(key: PGPSecretKey) : this(key.publicKey)
|
||||||
|
|
||||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
constructor(keys: PGPKeyRing) : this(keys.publicKey)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
||||||
|
*
|
||||||
* @param fp fingerprint to check.
|
* @param fp fingerprint to check.
|
||||||
* @return true if fingerprint is valid.
|
* @return true if fingerprint is valid.
|
||||||
*/
|
*/
|
||||||
|
@ -69,7 +68,9 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
||||||
|
|
||||||
override fun get(index: Int) = fingerprint.get(index)
|
override fun get(index: Int) = fingerprint.get(index)
|
||||||
|
|
||||||
override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex)
|
override fun subSequence(startIndex: Int, endIndex: Int) =
|
||||||
|
fingerprint.subSequence(startIndex, endIndex)
|
||||||
|
|
||||||
override fun compareTo(other: OpenPgpFingerprint): Int {
|
override fun compareTo(other: OpenPgpFingerprint): Int {
|
||||||
return fingerprint.compareTo(other.fingerprint)
|
return fingerprint.compareTo(other.fingerprint)
|
||||||
}
|
}
|
||||||
|
@ -87,49 +88,49 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
||||||
abstract fun prettyPrint(): String
|
abstract fun prettyPrint(): String
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic val utf8: Charset = Charset.forName("UTF-8")
|
||||||
val utf8: Charset = Charset.forName("UTF-8")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the fingerprint of the given key.
|
* Return the fingerprint of the given key. This method automatically matches key versions
|
||||||
* This method automatically matches key versions to fingerprint implementations.
|
* to fingerprint implementations.
|
||||||
|
*
|
||||||
|
* @param key key
|
||||||
|
* @return fingerprint
|
||||||
|
*/
|
||||||
|
@JvmStatic fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the fingerprint of the given key. This method automatically matches key versions
|
||||||
|
* to fingerprint implementations.
|
||||||
*
|
*
|
||||||
* @param key key
|
* @param key key
|
||||||
* @return fingerprint
|
* @return fingerprint
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
fun of(key: PGPPublicKey): OpenPgpFingerprint =
|
||||||
|
when (key.version) {
|
||||||
|
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.
|
* Return the fingerprint of the primary key of the given key ring. This method
|
||||||
* This method automatically matches key versions to fingerprint implementations.
|
* automatically matches key versions to fingerprint implementations.
|
||||||
*
|
|
||||||
* @param 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.
|
|
||||||
*
|
*
|
||||||
* @param ring key ring
|
* @param ring key ring
|
||||||
* @return fingerprint
|
* @return fingerprint
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic fun of(keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
||||||
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
|
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. If the
|
||||||
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
|
* trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6
|
||||||
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
|
* fingerprint. In this case, we return a {@link _64DigitFingerprint}. Since this is
|
||||||
* to know the version of the key beforehand.
|
* ambiguous, it is generally recommended to know the version of the key beforehand.
|
||||||
*
|
*
|
||||||
* @param fingerprint fingerprint
|
* @param fingerprint fingerprint
|
||||||
* @return parsed fingerprint
|
* @return parsed fingerprint
|
||||||
|
@ -146,7 +147,8 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
||||||
// Might be v5 or v6 :/
|
// Might be v5 or v6 :/
|
||||||
return _64DigitFingerprint(prep)
|
return _64DigitFingerprint(prep)
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.")
|
throw IllegalArgumentException(
|
||||||
|
"Fingerprint does not appear to match any known fingerprint pattern.")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,6 +161,6 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
|
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
|
||||||
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
|
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
|
||||||
parse(Hex.toHexString(binaryFingerprint))
|
parse(Hex.toHexString(binaryFingerprint))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,26 @@
|
||||||
|
|
||||||
package org.pgpainless.key
|
package org.pgpainless.key
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
|
||||||
import org.bouncycastle.util.encoders.Hex
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.nio.Buffer
|
import java.nio.Buffer
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
import org.bouncycastle.util.encoders.Hex
|
||||||
|
|
||||||
class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
class OpenPgpV4Fingerprint : OpenPgpFingerprint {
|
||||||
|
|
||||||
constructor(fingerprint: String): super(fingerprint)
|
constructor(fingerprint: String) : super(fingerprint)
|
||||||
constructor(bytes: ByteArray): super(bytes)
|
|
||||||
constructor(key: PGPPublicKey): super(key)
|
constructor(bytes: ByteArray) : super(bytes)
|
||||||
constructor(key: PGPSecretKey): super(key)
|
|
||||||
constructor(keys: PGPKeyRing): super(keys)
|
constructor(key: PGPPublicKey) : super(key)
|
||||||
|
|
||||||
|
constructor(key: PGPSecretKey) : super(key)
|
||||||
|
|
||||||
|
constructor(keys: PGPKeyRing) : super(keys)
|
||||||
|
|
||||||
override fun getVersion() = 4
|
override fun getVersion() = 4
|
||||||
|
|
||||||
|
@ -47,7 +51,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||||
}
|
}
|
||||||
append(' ')
|
append(' ')
|
||||||
for (i in 5 .. 8) {
|
for (i in 5..8) {
|
||||||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||||
}
|
}
|
||||||
append(fingerprint, 36, 40)
|
append(fingerprint, 36, 40)
|
||||||
|
@ -55,8 +59,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic val SCHEME = "openpgp4fpr"
|
||||||
val SCHEME = "openpgp4fpr"
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
|
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
|
||||||
|
@ -66,4 +69,4 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||||
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
|
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
|
||||||
/**
|
/** This class represents a hex encoded uppercase OpenPGP v5 fingerprint. */
|
||||||
* This class represents a hex encoded uppercase OpenPGP v5 fingerprint.
|
class OpenPgpV5Fingerprint : _64DigitFingerprint {
|
||||||
*/
|
|
||||||
class OpenPgpV5Fingerprint: _64DigitFingerprint {
|
|
||||||
|
|
||||||
constructor(fingerprint: String): super(fingerprint)
|
constructor(fingerprint: String) : super(fingerprint)
|
||||||
constructor(key: PGPPublicKey): super(key)
|
|
||||||
constructor(key: PGPSecretKey): super(key)
|
constructor(key: PGPPublicKey) : super(key)
|
||||||
constructor(keys: PGPKeyRing): super(keys)
|
|
||||||
constructor(bytes: ByteArray): super(bytes)
|
constructor(key: PGPSecretKey) : super(key)
|
||||||
|
|
||||||
|
constructor(keys: PGPKeyRing) : super(keys)
|
||||||
|
|
||||||
|
constructor(bytes: ByteArray) : super(bytes)
|
||||||
|
|
||||||
override fun getVersion(): Int {
|
override fun getVersion(): Int {
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
|
||||||
/**
|
/** This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. */
|
||||||
* This class represents a hex encoded, uppercase OpenPGP v6 fingerprint.
|
class OpenPgpV6Fingerprint : _64DigitFingerprint {
|
||||||
*/
|
|
||||||
class OpenPgpV6Fingerprint: _64DigitFingerprint {
|
|
||||||
|
|
||||||
constructor(fingerprint: String): super(fingerprint)
|
constructor(fingerprint: String) : super(fingerprint)
|
||||||
constructor(key: PGPPublicKey): super(key)
|
|
||||||
constructor(key: PGPSecretKey): super(key)
|
constructor(key: PGPPublicKey) : super(key)
|
||||||
constructor(keys: PGPKeyRing): super(keys)
|
|
||||||
constructor(bytes: ByteArray): super(bytes)
|
constructor(key: PGPSecretKey) : super(key)
|
||||||
|
|
||||||
|
constructor(keys: PGPKeyRing) : super(keys)
|
||||||
|
|
||||||
|
constructor(bytes: ByteArray) : super(bytes)
|
||||||
|
|
||||||
override fun getVersion(): Int {
|
override fun getVersion(): Int {
|
||||||
return 6
|
return 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,17 +13,30 @@ import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
* as well as the subkeys fingerprint.
|
* as well as the subkeys fingerprint.
|
||||||
*/
|
*/
|
||||||
class SubkeyIdentifier(
|
class SubkeyIdentifier(
|
||||||
val primaryKeyFingerprint: OpenPgpFingerprint,
|
val primaryKeyFingerprint: OpenPgpFingerprint,
|
||||||
val subkeyFingerprint: OpenPgpFingerprint) {
|
val subkeyFingerprint: OpenPgpFingerprint
|
||||||
|
) {
|
||||||
|
|
||||||
constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint)
|
constructor(fingerprint: OpenPgpFingerprint) : this(fingerprint, fingerprint)
|
||||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
|
||||||
constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key))
|
constructor(keys: PGPKeyRing) : this(keys.publicKey)
|
||||||
constructor(keys: PGPKeyRing, keyId: Long): this(
|
|
||||||
OpenPgpFingerprint.of(keys.publicKey),
|
constructor(key: PGPPublicKey) : this(OpenPgpFingerprint.of(key))
|
||||||
OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?:
|
|
||||||
throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}")))
|
constructor(
|
||||||
constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
|
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 keyId = subkeyFingerprint.keyId
|
||||||
val fingerprint = subkeyFingerprint
|
val fingerprint = subkeyFingerprint
|
||||||
|
@ -34,7 +47,7 @@ class SubkeyIdentifier(
|
||||||
val isPrimaryKey = primaryKeyId == subkeyId
|
val isPrimaryKey = primaryKeyId == subkeyId
|
||||||
|
|
||||||
fun matches(fingerprint: OpenPgpFingerprint) =
|
fun matches(fingerprint: OpenPgpFingerprint) =
|
||||||
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
|
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other == null) {
|
if (other == null) {
|
||||||
|
@ -47,7 +60,8 @@ class SubkeyIdentifier(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint
|
return primaryKeyFingerprint == other.primaryKeyFingerprint &&
|
||||||
|
subkeyFingerprint == other.subkeyFingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
@ -55,4 +69,4 @@ class SubkeyIdentifier(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint"
|
override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,47 +2,47 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package org.pgpainless.key;
|
package org.pgpainless.key
|
||||||
|
|
||||||
import java.nio.Buffer;
|
import java.nio.Buffer
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset
|
||||||
import javax.annotation.Nonnull;
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.util.encoders.Hex
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
|
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. Since both
|
||||||
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
|
* fingerprints use the same format, this class is used when parsing the fingerprint without knowing
|
||||||
* key version.
|
* the key version.
|
||||||
*/
|
*/
|
||||||
open class _64DigitFingerprint: OpenPgpFingerprint {
|
open class _64DigitFingerprint : OpenPgpFingerprint {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an {@link _64DigitFingerprint}.
|
* Create an {@link _64DigitFingerprint}.
|
||||||
*
|
*
|
||||||
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
||||||
*/
|
*/
|
||||||
constructor(fingerprint: String): super(fingerprint)
|
constructor(fingerprint: String) : super(fingerprint)
|
||||||
constructor(bytes: ByteArray): super(bytes)
|
|
||||||
constructor(key: PGPPublicKey): super(key)
|
constructor(bytes: ByteArray) : super(bytes)
|
||||||
constructor(key: PGPSecretKey): super(key)
|
|
||||||
constructor(keys: PGPKeyRing): super(keys)
|
constructor(key: PGPPublicKey) : super(key)
|
||||||
|
|
||||||
|
constructor(key: PGPSecretKey) : super(key)
|
||||||
|
|
||||||
|
constructor(keys: PGPKeyRing) : super(keys)
|
||||||
|
|
||||||
override val keyId: Long
|
override val keyId: Long
|
||||||
get() {
|
get() {
|
||||||
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
|
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
|
||||||
val buf = ByteBuffer.wrap(bytes);
|
val buf = ByteBuffer.wrap(bytes)
|
||||||
|
|
||||||
// The key id is the left-most 8 bytes (conveniently a long).
|
// The key id is the left-most 8 bytes (conveniently a long).
|
||||||
// We have to cast here in order to be compatible with java 8
|
// We have to cast here in order to be compatible with java 8
|
||||||
// https://github.com/eclipse/jetty.project/issues/3244
|
// https://github.com/eclipse/jetty.project/issues/3244
|
||||||
(buf as Buffer).position(0);
|
(buf as Buffer).position(0)
|
||||||
|
|
||||||
return buf.getLong()
|
return buf.getLong()
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,13 @@ open class _64DigitFingerprint: OpenPgpFingerprint {
|
||||||
override fun prettyPrint(): String {
|
override fun prettyPrint(): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
for (i in 0 until 4) {
|
for (i in 0 until 4) {
|
||||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
append(fingerprint, i * 8, (i + 1) * 8).append(' ')
|
||||||
}
|
}
|
||||||
append(' ');
|
append(' ')
|
||||||
for (i in 4 until 7) {
|
for (i in 4 until 7) {
|
||||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
append(fingerprint, i * 8, (i + 1) * 8).append(' ')
|
||||||
}
|
}
|
||||||
append(fingerprint, 56, 64);
|
append(fingerprint, 56, 64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.key.certification
|
package org.pgpainless.key.certification
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.PGPException
|
import org.bouncycastle.openpgp.PGPException
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
@ -22,28 +23,27 @@ import org.pgpainless.key.util.KeyRingUtils
|
||||||
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
|
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
|
||||||
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
|
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
|
||||||
import org.pgpainless.signature.subpackets.CertificationSubpackets
|
import org.pgpainless.signature.subpackets.CertificationSubpackets
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API for creating certifications and delegations (Signatures) on keys.
|
* API for creating certifications and delegations (Signatures) on keys. This API can be used to
|
||||||
* This API can be used to sign another persons OpenPGP key.
|
* sign another persons OpenPGP key.
|
||||||
*
|
*
|
||||||
* A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs
|
* A certification over a user-id is thereby used to attest, that the user believes that the user-id
|
||||||
* to the owner of the certificate.
|
* really belongs to the owner of the certificate. A delegation over a key can be used to delegate
|
||||||
* A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer.
|
* trust by marking the certificate as a trusted introducer.
|
||||||
*/
|
*/
|
||||||
class CertifyCertificate {
|
class CertifyCertificate {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a certification over a User-Id.
|
* Create a certification over a User-Id. By default, this method will use
|
||||||
* By default, this method will use [CertificationType.GENERIC] to create the signature.
|
* [CertificationType.GENERIC] to create the signature.
|
||||||
*
|
*
|
||||||
* @param userId user-id to certify
|
* @param userId user-id to certify
|
||||||
* @param certificate certificate
|
* @param certificate certificate
|
||||||
* @return API
|
* @return API
|
||||||
*/
|
*/
|
||||||
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId =
|
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.
|
* Create a certification of the given [CertificationType] over a User-Id.
|
||||||
|
@ -53,36 +53,40 @@ class CertifyCertificate {
|
||||||
* @param certificationType type of signature
|
* @param certificationType type of signature
|
||||||
* @return API
|
* @return API
|
||||||
*/
|
*/
|
||||||
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) =
|
fun userIdOnCertificate(
|
||||||
CertificationOnUserId(userId, certificate, certificationType)
|
userId: String,
|
||||||
|
certificate: PGPPublicKeyRing,
|
||||||
|
certificationType: CertificationType
|
||||||
|
) = CertificationOnUserId(userId, certificate, certificationType)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a delegation (direct key signature) over a certificate.
|
* Create a delegation (direct key signature) over a certificate. This can be used to mark a
|
||||||
* This can be used to mark a certificate as a trusted introducer
|
* certificate as a trusted introducer (see [certificate] method with [Trustworthiness]
|
||||||
* (see [certificate] method with [Trustworthiness] argument).
|
* argument).
|
||||||
*
|
*
|
||||||
* @param certificate certificate
|
* @param certificate certificate
|
||||||
* @return API
|
* @return API
|
||||||
*/
|
*/
|
||||||
fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate =
|
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
|
* Create a delegation (direct key signature) containing a
|
||||||
* over a certificate.
|
* [org.bouncycastle.bcpg.sig.TrustSignature] packet over a certificate. This can be used to
|
||||||
* This can be used to mark a certificate as a trusted introducer.
|
* mark a certificate as a trusted introducer.
|
||||||
*
|
*
|
||||||
* @param certificate certificate
|
* @param certificate certificate
|
||||||
* @param trustworthiness trustworthiness of the certificate
|
* @param trustworthiness trustworthiness of the certificate
|
||||||
* @return API
|
* @return API
|
||||||
*/
|
*/
|
||||||
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
|
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
|
||||||
DelegationOnCertificate(certificate, trustworthiness)
|
DelegationOnCertificate(certificate, trustworthiness)
|
||||||
|
|
||||||
class CertificationOnUserId(
|
class CertificationOnUserId(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val certificate: PGPPublicKeyRing,
|
val certificate: PGPPublicKeyRing,
|
||||||
val certificationType: CertificationType) {
|
val certificationType: CertificationType
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the certification using the given key.
|
* Create the certification using the given key.
|
||||||
|
@ -92,11 +96,14 @@ class CertifyCertificate {
|
||||||
* @return API
|
* @return API
|
||||||
* @throws PGPException in case of an OpenPGP related error
|
* @throws PGPException in case of an OpenPGP related error
|
||||||
*/
|
*/
|
||||||
fun withKey(certificationKey: PGPSecretKeyRing,
|
fun withKey(
|
||||||
protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets {
|
certificationKey: PGPSecretKeyRing,
|
||||||
|
protector: SecretKeyRingProtector
|
||||||
|
): CertificationOnUserIdWithSubpackets {
|
||||||
|
|
||||||
val secretKey = getCertifyingSecretKey(certificationKey)
|
val secretKey = getCertifyingSecretKey(certificationKey)
|
||||||
val sigBuilder = ThirdPartyCertificationSignatureBuilder(
|
val sigBuilder =
|
||||||
|
ThirdPartyCertificationSignatureBuilder(
|
||||||
certificationType.asSignatureType(), secretKey, protector)
|
certificationType.asSignatureType(), secretKey, protector)
|
||||||
|
|
||||||
return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder)
|
return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder)
|
||||||
|
@ -104,9 +111,9 @@ class CertifyCertificate {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CertificationOnUserIdWithSubpackets(
|
class CertificationOnUserIdWithSubpackets(
|
||||||
val certificate: PGPPublicKeyRing,
|
val certificate: PGPPublicKeyRing,
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val sigBuilder: ThirdPartyCertificationSignatureBuilder
|
val sigBuilder: ThirdPartyCertificationSignatureBuilder
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,7 +123,9 @@ class CertifyCertificate {
|
||||||
* @return result
|
* @return result
|
||||||
* @throws PGPException in case of an OpenPGP related error
|
* @throws PGPException in case of an OpenPGP related error
|
||||||
*/
|
*/
|
||||||
fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult {
|
fun buildWithSubpackets(
|
||||||
|
subpacketCallback: CertificationSubpackets.Callback
|
||||||
|
): CertificationResult {
|
||||||
sigBuilder.applyCallback(subpacketCallback)
|
sigBuilder.applyCallback(subpacketCallback)
|
||||||
return build()
|
return build()
|
||||||
}
|
}
|
||||||
|
@ -129,14 +138,16 @@ class CertifyCertificate {
|
||||||
*/
|
*/
|
||||||
fun build(): CertificationResult {
|
fun build(): CertificationResult {
|
||||||
val signature = sigBuilder.build(certificate, userId)
|
val signature = sigBuilder.build(certificate, userId)
|
||||||
val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature)
|
val certifiedCertificate =
|
||||||
|
KeyRingUtils.injectCertification(certificate, userId, signature)
|
||||||
return CertificationResult(certifiedCertificate, signature)
|
return CertificationResult(certifiedCertificate, signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelegationOnCertificate(
|
class DelegationOnCertificate(
|
||||||
val certificate: PGPPublicKeyRing,
|
val certificate: PGPPublicKeyRing,
|
||||||
val trustworthiness: Trustworthiness?) {
|
val trustworthiness: Trustworthiness?
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the delegation using the given certification key.
|
* Build the delegation using the given certification key.
|
||||||
|
@ -146,20 +157,24 @@ class CertifyCertificate {
|
||||||
* @return API
|
* @return API
|
||||||
* @throws PGPException in case of an OpenPGP related error
|
* @throws PGPException in case of an OpenPGP related error
|
||||||
*/
|
*/
|
||||||
fun withKey(certificationKey: PGPSecretKeyRing,
|
fun withKey(
|
||||||
protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets {
|
certificationKey: PGPSecretKeyRing,
|
||||||
|
protector: SecretKeyRingProtector
|
||||||
|
): DelegationOnCertificateWithSubpackets {
|
||||||
val secretKey = getCertifyingSecretKey(certificationKey)
|
val secretKey = getCertifyingSecretKey(certificationKey)
|
||||||
val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector)
|
val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector)
|
||||||
if (trustworthiness != null) {
|
if (trustworthiness != null) {
|
||||||
sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount)
|
sigBuilder.hashedSubpackets.setTrust(
|
||||||
|
true, trustworthiness.depth, trustworthiness.amount)
|
||||||
}
|
}
|
||||||
return DelegationOnCertificateWithSubpackets(certificate, sigBuilder)
|
return DelegationOnCertificateWithSubpackets(certificate, sigBuilder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelegationOnCertificateWithSubpackets(
|
class DelegationOnCertificateWithSubpackets(
|
||||||
val certificate: PGPPublicKeyRing,
|
val certificate: PGPPublicKeyRing,
|
||||||
val sigBuilder: ThirdPartyDirectKeySignatureBuilder) {
|
val sigBuilder: ThirdPartyDirectKeySignatureBuilder
|
||||||
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the given signature subpackets and build the delegation signature.
|
* Apply the given signature subpackets and build the delegation signature.
|
||||||
|
@ -168,7 +183,9 @@ class CertifyCertificate {
|
||||||
* @return result
|
* @return result
|
||||||
* @throws PGPException in case of an OpenPGP related error
|
* @throws PGPException in case of an OpenPGP related error
|
||||||
*/
|
*/
|
||||||
fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult {
|
fun buildWithSubpackets(
|
||||||
|
subpacketsCallback: CertificationSubpackets.Callback
|
||||||
|
): CertificationResult {
|
||||||
sigBuilder.applyCallback(subpacketsCallback)
|
sigBuilder.applyCallback(subpacketsCallback)
|
||||||
return build()
|
return build()
|
||||||
}
|
}
|
||||||
|
@ -182,7 +199,8 @@ class CertifyCertificate {
|
||||||
fun build(): CertificationResult {
|
fun build(): CertificationResult {
|
||||||
val delegatedKey = certificate.publicKey
|
val delegatedKey = certificate.publicKey
|
||||||
val delegation = sigBuilder.build(delegatedKey)
|
val delegation = sigBuilder.build(delegatedKey)
|
||||||
val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
|
val delegatedCertificate =
|
||||||
|
KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
|
||||||
return CertificationResult(delegatedCertificate, delegation)
|
return CertificationResult(delegatedCertificate, delegation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,8 +212,9 @@ class CertifyCertificate {
|
||||||
* @param certification the newly created signature
|
* @param certification the newly created signature
|
||||||
*/
|
*/
|
||||||
data class CertificationResult(
|
data class CertificationResult(
|
||||||
val certifiedCertificate: PGPPublicKeyRing,
|
val certifiedCertificate: PGPPublicKeyRing,
|
||||||
val certification: PGPSignature)
|
val certification: PGPSignature
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -205,9 +224,7 @@ class CertifyCertificate {
|
||||||
|
|
||||||
val fingerprint = info.fingerprint
|
val fingerprint = info.fingerprint
|
||||||
val certificationPubKey = info.getPublicKey(fingerprint)
|
val certificationPubKey = info.getPublicKey(fingerprint)
|
||||||
requireNotNull(certificationPubKey) {
|
requireNotNull(certificationPubKey) { "Primary key cannot be null." }
|
||||||
"Primary key cannot be null."
|
|
||||||
}
|
|
||||||
if (!info.isKeyValidlyBound(certificationPubKey.keyID)) {
|
if (!info.isKeyValidlyBound(certificationPubKey.keyID)) {
|
||||||
throw RevokedKeyException(fingerprint)
|
throw RevokedKeyException(fingerprint)
|
||||||
}
|
}
|
||||||
|
@ -222,7 +239,7 @@ class CertifyCertificate {
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificationKey.getSecretKey(certificationPubKey.keyID)
|
return certificationKey.getSecretKey(certificationPubKey.keyID)
|
||||||
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
|
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,32 +4,38 @@
|
||||||
|
|
||||||
package org.pgpainless.key.collection
|
package org.pgpainless.key.collection
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.pgpainless.implementation.ImplementationFactory
|
import org.pgpainless.implementation.ImplementationFactory
|
||||||
import org.pgpainless.util.ArmorUtils
|
import org.pgpainless.util.ArmorUtils
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was inspired by
|
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was
|
||||||
* [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
|
* inspired by [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
|
||||||
*/
|
*/
|
||||||
class PGPKeyRingCollection(
|
class PGPKeyRingCollection(
|
||||||
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
|
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
|
||||||
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
|
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(encoding: ByteArray, isSilent: Boolean): this(encoding.inputStream(), isSilent)
|
constructor(encoding: ByteArray, isSilent: Boolean) : this(encoding.inputStream(), isSilent)
|
||||||
|
|
||||||
constructor(inputStream: InputStream, isSilent: Boolean): this(parse(inputStream, isSilent))
|
constructor(inputStream: InputStream, isSilent: Boolean) : this(parse(inputStream, isSilent))
|
||||||
|
|
||||||
constructor(collection: Collection<PGPKeyRing>, isSilent: Boolean): this(segment(collection, isSilent))
|
constructor(
|
||||||
|
collection: Collection<PGPKeyRing>,
|
||||||
|
isSilent: Boolean
|
||||||
|
) : this(segment(collection, isSilent))
|
||||||
|
|
||||||
private constructor(arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>): this(arguments.first, arguments.second)
|
private constructor(
|
||||||
|
arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>
|
||||||
|
) : this(arguments.first, arguments.second)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of rings in this collection.
|
* The number of rings in this collection.
|
||||||
*
|
*
|
||||||
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection
|
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this
|
||||||
|
* collection
|
||||||
*/
|
*/
|
||||||
val size: Int
|
val size: Int
|
||||||
get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size()
|
get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size()
|
||||||
|
@ -41,11 +47,16 @@ class PGPKeyRingCollection(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun parse(inputStream: InputStream, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
private fun parse(
|
||||||
|
inputStream: InputStream,
|
||||||
|
isSilent: Boolean
|
||||||
|
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||||
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
||||||
val certificates = mutableListOf<PGPPublicKeyRing>()
|
val certificates = mutableListOf<PGPPublicKeyRing>()
|
||||||
// Double getDecoderStream because of #96
|
// Double getDecoderStream because of #96
|
||||||
val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream))
|
val objectFactory =
|
||||||
|
ImplementationFactory.getInstance()
|
||||||
|
.getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream))
|
||||||
|
|
||||||
for (obj in objectFactory) {
|
for (obj in objectFactory) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
|
@ -68,16 +79,21 @@ class PGPKeyRingCollection(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSilent) {
|
if (!isSilent) {
|
||||||
throw PGPException("${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
throw PGPException(
|
||||||
|
"${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||||
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
|
return PGPSecretKeyRingCollection(secretKeyRings) to
|
||||||
|
PGPPublicKeyRingCollection(certificates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun segment(collection: Collection<PGPKeyRing>, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
private fun segment(
|
||||||
|
collection: Collection<PGPKeyRing>,
|
||||||
|
isSilent: Boolean
|
||||||
|
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||||
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
||||||
val certificates = mutableListOf<PGPPublicKeyRing>()
|
val certificates = mutableListOf<PGPPublicKeyRing>()
|
||||||
|
|
||||||
|
@ -87,12 +103,14 @@ class PGPKeyRingCollection(
|
||||||
} else if (keyRing is PGPPublicKeyRing) {
|
} else if (keyRing is PGPPublicKeyRing) {
|
||||||
certificates.add(keyRing)
|
certificates.add(keyRing)
|
||||||
} else if (!isSilent) {
|
} else if (!isSilent) {
|
||||||
throw PGPException("${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
throw PGPException(
|
||||||
|
"${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||||
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
|
return PGPSecretKeyRingCollection(secretKeyRings) to
|
||||||
|
PGPPublicKeyRingCollection(certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.KeyPairGenerator
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.extensions.unlock
|
import org.bouncycastle.extensions.unlock
|
||||||
import org.bouncycastle.openpgp.*
|
import org.bouncycastle.openpgp.*
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
||||||
|
@ -21,10 +24,6 @@ import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||||
import org.pgpainless.util.Passphrase
|
import org.pgpainless.util.Passphrase
|
||||||
import java.io.IOException
|
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
|
|
||||||
|
@ -49,19 +48,19 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
userIds[userId.toString().trim()] = null
|
userIds[userId.toString().trim()] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addUserId(userId: ByteArray): KeyRingBuilder = addUserId(Strings.fromUTF8ByteArray(userId))
|
override fun addUserId(userId: ByteArray): KeyRingBuilder =
|
||||||
|
addUserId(Strings.fromUTF8ByteArray(userId))
|
||||||
|
|
||||||
override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
|
override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
|
||||||
if (expirationDate == null) {
|
if (expirationDate == null) {
|
||||||
this.expirationDate = null
|
this.expirationDate = null
|
||||||
return@apply
|
return@apply
|
||||||
}
|
}
|
||||||
this.expirationDate = expirationDate.let {
|
this.expirationDate =
|
||||||
require(Date() < expirationDate) {
|
expirationDate.let {
|
||||||
"Expiration date must be in the future."
|
require(Date() < expirationDate) { "Expiration date must be in the future." }
|
||||||
|
expirationDate
|
||||||
}
|
}
|
||||||
expirationDate
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply {
|
override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply {
|
||||||
|
@ -85,17 +84,14 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify
|
private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify
|
||||||
|
|
||||||
override fun build(): PGPSecretKeyRing {
|
override fun build(): PGPSecretKeyRing {
|
||||||
val keyFingerprintCalculator = ImplementationFactory.getInstance()
|
val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator
|
||||||
.v4FingerprintCalculator
|
|
||||||
val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
|
val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
|
||||||
val secretKeyDecryptor = buildSecretKeyDecryptor()
|
val secretKeyDecryptor = buildSecretKeyDecryptor()
|
||||||
|
|
||||||
passphrase.clear() // Passphrase was used above, so we can get rid of it
|
passphrase.clear() // Passphrase was used above, so we can get rid of it
|
||||||
|
|
||||||
// generate primary key
|
// generate primary key
|
||||||
requireNotNull(primaryKeySpec) {
|
requireNotNull(primaryKeySpec) { "Primary Key spec required." }
|
||||||
"Primary Key spec required."
|
|
||||||
}
|
|
||||||
val certKey = generateKeyPair(primaryKeySpec!!)
|
val certKey = generateKeyPair(primaryKeySpec!!)
|
||||||
val signer = buildContentSigner(certKey)
|
val signer = buildContentSigner(certKey)
|
||||||
val signatureGenerator = PGPSignatureGenerator(signer)
|
val signatureGenerator = PGPSignatureGenerator(signer)
|
||||||
|
@ -110,16 +106,28 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
val generator = PGPSignatureSubpacketGenerator()
|
val generator = PGPSignatureSubpacketGenerator()
|
||||||
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
|
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
|
||||||
val hashedSubPackets = generator.generate()
|
val hashedSubPackets = generator.generate()
|
||||||
val ringGenerator = if (userIds.isEmpty()) {
|
val ringGenerator =
|
||||||
PGPKeyRingGenerator(certKey, keyFingerprintCalculator, hashedSubPackets, null, signer,
|
if (userIds.isEmpty()) {
|
||||||
|
PGPKeyRingGenerator(
|
||||||
|
certKey,
|
||||||
|
keyFingerprintCalculator,
|
||||||
|
hashedSubPackets,
|
||||||
|
null,
|
||||||
|
signer,
|
||||||
secretKeyEncryptor)
|
secretKeyEncryptor)
|
||||||
} else {
|
} else {
|
||||||
userIds.keys.first().let { primaryUserId ->
|
userIds.keys.first().let { primaryUserId ->
|
||||||
PGPKeyRingGenerator(SignatureType.POSITIVE_CERTIFICATION.code, certKey, primaryUserId,
|
PGPKeyRingGenerator(
|
||||||
keyFingerprintCalculator, hashedSubPackets, null, signer,
|
SignatureType.POSITIVE_CERTIFICATION.code,
|
||||||
|
certKey,
|
||||||
|
primaryUserId,
|
||||||
|
keyFingerprintCalculator,
|
||||||
|
hashedSubPackets,
|
||||||
|
null,
|
||||||
|
signer,
|
||||||
secretKeyEncryptor)
|
secretKeyEncryptor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
addSubKeys(certKey, ringGenerator)
|
addSubKeys(certKey, ringGenerator)
|
||||||
|
|
||||||
|
@ -138,20 +146,26 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
val additionalUserId = userIdIterator.next()
|
val additionalUserId = userIdIterator.next()
|
||||||
val userIdString = additionalUserId.key
|
val userIdString = additionalUserId.key
|
||||||
val callback = additionalUserId.value
|
val callback = additionalUserId.value
|
||||||
val subpackets = if (callback == null) {
|
val subpackets =
|
||||||
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
|
if (callback == null) {
|
||||||
} else {
|
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
|
||||||
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) }
|
} else {
|
||||||
}
|
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also {
|
||||||
|
callback.modifyHashedSubpackets(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
|
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
|
||||||
signatureGenerator.setHashedSubpackets(
|
signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets))
|
||||||
SignatureSubpacketsHelper.toVector(subpackets))
|
val additionalUserIdSignature =
|
||||||
val additionalUserIdSignature = signatureGenerator.generateCertification(userIdString, primaryPubKey)
|
signatureGenerator.generateCertification(userIdString, primaryPubKey)
|
||||||
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, userIdString, additionalUserIdSignature)
|
primaryPubKey =
|
||||||
|
PGPPublicKey.addCertification(
|
||||||
|
primaryPubKey, userIdString, additionalUserIdSignature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reassemble secret key ring with modified primary key
|
// Reassemble secret key ring with modified primary key
|
||||||
val primarySecretKey = PGPSecretKey(
|
val primarySecretKey =
|
||||||
|
PGPSecretKey(
|
||||||
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
|
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
|
||||||
val secretKeyList = mutableListOf(primarySecretKey)
|
val secretKeyList = mutableListOf(primarySecretKey)
|
||||||
while (secretKeys.hasNext()) {
|
while (secretKeys.hasNext()) {
|
||||||
|
@ -168,25 +182,34 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
} else {
|
} else {
|
||||||
var hashedSubpackets = subKeySpec.subpackets
|
var hashedSubpackets = subKeySpec.subpackets
|
||||||
try {
|
try {
|
||||||
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets)
|
hashedSubpackets =
|
||||||
|
addPrimaryKeyBindingSignatureIfNecessary(
|
||||||
|
primaryKey, subKey, hashedSubpackets)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw PGPException("Exception while adding primary key binding signature to signing subkey.", e)
|
throw PGPException(
|
||||||
|
"Exception while adding primary key binding signature to signing subkey.",
|
||||||
|
e)
|
||||||
}
|
}
|
||||||
ringGenerator.addSubKey(subKey, hashedSubpackets, null)
|
ringGenerator.addSubKey(subKey, hashedSubpackets, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addPrimaryKeyBindingSignatureIfNecessary(primaryKey: PGPKeyPair, subKey: PGPKeyPair, hashedSubpackets: PGPSignatureSubpacketVector): PGPSignatureSubpacketVector {
|
private fun addPrimaryKeyBindingSignatureIfNecessary(
|
||||||
|
primaryKey: PGPKeyPair,
|
||||||
|
subKey: PGPKeyPair,
|
||||||
|
hashedSubpackets: PGPSignatureSubpacketVector
|
||||||
|
): PGPSignatureSubpacketVector {
|
||||||
val keyFlagMask = hashedSubpackets.keyFlags
|
val keyFlagMask = hashedSubpackets.keyFlags
|
||||||
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
|
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
|
||||||
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
|
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
|
||||||
return hashedSubpackets
|
return hashedSubpackets
|
||||||
}
|
}
|
||||||
|
|
||||||
val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
|
val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
|
||||||
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
|
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
|
||||||
val primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
|
val primaryKeyBindingSig =
|
||||||
|
bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
|
||||||
val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
|
val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
|
||||||
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
|
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
|
||||||
return subpacketGenerator.generate()
|
return subpacketGenerator.generate()
|
||||||
|
@ -194,25 +217,29 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
|
|
||||||
private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
|
private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
|
||||||
val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm
|
val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm
|
||||||
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
|
return ImplementationFactory.getInstance()
|
||||||
certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
|
.getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSecretKeyEncryptor(keyFingerprintCalculator: PGPDigestCalculator): PBESecretKeyEncryptor? {
|
private fun buildSecretKeyEncryptor(
|
||||||
val keyEncryptionAlgorithm = PGPainless.getPolicy().symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm
|
keyFingerprintCalculator: PGPDigestCalculator
|
||||||
check(passphrase.isValid) {
|
): PBESecretKeyEncryptor? {
|
||||||
"Passphrase was cleared."
|
val keyEncryptionAlgorithm =
|
||||||
}
|
PGPainless.getPolicy()
|
||||||
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance()
|
.symmetricKeyEncryptionAlgorithmPolicy
|
||||||
.getPBESecretKeyEncryptor(keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
|
.defaultSymmetricKeyAlgorithm
|
||||||
|
check(passphrase.isValid) { "Passphrase was cleared." }
|
||||||
|
return if (passphrase.isEmpty) null
|
||||||
|
else
|
||||||
|
ImplementationFactory.getInstance()
|
||||||
|
.getPBESecretKeyEncryptor(
|
||||||
|
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
|
private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
|
||||||
check(passphrase.isValid) {
|
check(passphrase.isValid) { "Passphrase was cleared." }
|
||||||
"Passphrase was cleared."
|
return if (passphrase.isEmpty) null
|
||||||
}
|
else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase)
|
||||||
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance()
|
|
||||||
.getPBESecretKeyDecryptor(passphrase)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -222,16 +249,16 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
fun generateKeyPair(spec: KeySpec): PGPKeyPair {
|
fun generateKeyPair(spec: KeySpec): PGPKeyPair {
|
||||||
spec.keyType.let { type ->
|
spec.keyType.let { type ->
|
||||||
// Create raw Key Pair
|
// Create raw Key Pair
|
||||||
val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
|
val keyPair =
|
||||||
.also { it.initialize(type.algorithmSpec) }
|
KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
|
||||||
.generateKeyPair()
|
.also { it.initialize(type.algorithmSpec) }
|
||||||
|
.generateKeyPair()
|
||||||
|
|
||||||
val keyCreationDate = spec.keyCreationDate ?: Date()
|
val keyCreationDate = spec.keyCreationDate ?: Date()
|
||||||
// Form PGP Key Pair
|
// Form PGP Key Pair
|
||||||
return ImplementationFactory.getInstance()
|
return ImplementationFactory.getInstance()
|
||||||
.getPGPKeyPair(type.algorithm, keyPair, keyCreationDate)
|
.getPGPKeyPair(type.algorithm, keyPair, keyCreationDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
|
||||||
import org.pgpainless.util.Passphrase
|
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import org.bouncycastle.openpgp.PGPException
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||||
|
import org.pgpainless.util.Passphrase
|
||||||
|
|
||||||
interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
||||||
|
|
||||||
|
@ -29,6 +29,9 @@ interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
||||||
|
|
||||||
fun setPassphrase(passphrase: Passphrase): B
|
fun setPassphrase(passphrase: Passphrase): B
|
||||||
|
|
||||||
@Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class)
|
@Throws(
|
||||||
|
NoSuchAlgorithmException::class,
|
||||||
|
PGPException::class,
|
||||||
|
InvalidAlgorithmParameterException::class)
|
||||||
fun build(): PGPSecretKeyRing
|
fun build(): PGPSecretKeyRing
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ import org.pgpainless.util.Passphrase
|
||||||
class KeyRingTemplates {
|
class KeyRingTemplates {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
|
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
|
||||||
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
* dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||||
*
|
*
|
||||||
* @param userId userId or null
|
* @param userId userId or null
|
||||||
* @param length length of the RSA keys
|
* @param length length of the RSA keys
|
||||||
|
@ -26,160 +26,187 @@ class KeyRingTemplates {
|
||||||
* @return key
|
* @return key
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun rsaKeyRing(userId: CharSequence?,
|
fun rsaKeyRing(
|
||||||
length: RsaLength,
|
userId: CharSequence?,
|
||||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
length: RsaLength,
|
||||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||||
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
|
): PGPSecretKeyRing =
|
||||||
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
|
buildKeyRing()
|
||||||
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
.apply {
|
||||||
setPassphrase(passphrase)
|
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
|
||||||
if (userId != null) {
|
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
|
||||||
addUserId(userId)
|
addSubkey(
|
||||||
}
|
getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||||
}.build()
|
setPassphrase(passphrase)
|
||||||
|
if (userId != null) {
|
||||||
|
addUserId(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
|
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
|
||||||
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
* dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||||
*
|
*
|
||||||
* @param userId userId or null
|
* @param userId userId or null
|
||||||
* @param length length of the RSA keys
|
* @param length length of the RSA keys
|
||||||
* @param password passphrase to encrypt the key with. Can be null or blank for unencrypted keys.
|
* @param password passphrase to encrypt the key with. Can be null or blank for unencrypted
|
||||||
|
* keys.
|
||||||
* @return key
|
* @return key
|
||||||
*/
|
*/
|
||||||
fun rsaKeyRing(userId: CharSequence?,
|
fun rsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?): PGPSecretKeyRing =
|
||||||
length: RsaLength,
|
password.let {
|
||||||
password: String?
|
if (it.isNullOrBlank()) {
|
||||||
): PGPSecretKeyRing = password.let {
|
rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||||
if (it.isNullOrBlank()) {
|
} else {
|
||||||
rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
rsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||||
} else {
|
}
|
||||||
rsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
|
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
|
||||||
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
|
* KeyPair consists of a single RSA master key which is used for signing, encryption and
|
||||||
|
* certification.
|
||||||
*
|
*
|
||||||
* @param userId user id.
|
* @param userId user id.
|
||||||
* @param length length in bits.
|
* @param length length in bits.
|
||||||
* @param password Password of the key. Can be empty for unencrypted keys.
|
* @param password Password of the key. Can be empty for unencrypted keys.
|
||||||
*
|
|
||||||
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun simpleRsaKeyRing(userId: CharSequence?,
|
fun simpleRsaKeyRing(
|
||||||
length: RsaLength,
|
userId: CharSequence?,
|
||||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
length: RsaLength,
|
||||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||||
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS))
|
): PGPSecretKeyRing =
|
||||||
setPassphrase(passphrase)
|
buildKeyRing()
|
||||||
if (userId != null) {
|
.apply {
|
||||||
addUserId(userId.toString())
|
setPrimaryKey(
|
||||||
}
|
getBuilder(
|
||||||
}.build()
|
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}.
|
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
|
||||||
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
|
* KeyPair consists of a single RSA master key which is used for signing, encryption and
|
||||||
|
* certification.
|
||||||
*
|
*
|
||||||
* @param userId user id.
|
* @param userId user id.
|
||||||
* @param length length in bits.
|
* @param length length in bits.
|
||||||
* @param password Password of the key. Can be null or blank for unencrypted keys.
|
* @param password Password of the key. Can be null or blank for unencrypted keys.
|
||||||
*
|
|
||||||
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
||||||
*/
|
*/
|
||||||
fun simpleRsaKeyRing(userId: CharSequence?,
|
fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?) =
|
||||||
length: RsaLength,
|
password.let {
|
||||||
password: String?
|
if (it.isNullOrBlank()) {
|
||||||
) = password.let {
|
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||||
if (it.isNullOrBlank()) {
|
} else {
|
||||||
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||||
} else {
|
}
|
||||||
simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey.
|
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
|
||||||
* The EdDSA primary key is used for signing messages and certifying the sub key.
|
* EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
|
||||||
* The XDH subkey is used for encryption and decryption of messages.
|
* used for encryption and decryption of messages.
|
||||||
*
|
*
|
||||||
* @param userId user-id
|
* @param userId user-id
|
||||||
* @param passphrase Password of the private key. Can be empty for an unencrypted key.
|
* @param passphrase Password of the private key. Can be empty for an unencrypted key.
|
||||||
*
|
|
||||||
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun simpleEcKeyRing(userId: CharSequence?,
|
fun simpleEcKeyRing(
|
||||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
userId: CharSequence?,
|
||||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||||
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
|
): PGPSecretKeyRing =
|
||||||
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
|
buildKeyRing()
|
||||||
setPassphrase(passphrase)
|
.apply {
|
||||||
if (userId != null) {
|
setPrimaryKey(
|
||||||
addUserId(userId.toString())
|
getBuilder(
|
||||||
}
|
KeyType.EDDSA(EdDSACurve._Ed25519),
|
||||||
}.build()
|
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.
|
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
|
||||||
* The EdDSA primary key is used for signing messages and certifying the sub key.
|
* EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
|
||||||
* The XDH subkey is used for encryption and decryption of messages.
|
* used for encryption and decryption of messages.
|
||||||
*
|
*
|
||||||
* @param userId user-id
|
* @param userId user-id
|
||||||
* @param passphrase Password of the private key. Can be null or blank for an unencrypted key.
|
* @param passphrase Password of the private key. Can be null or blank for an unencrypted key.
|
||||||
*
|
|
||||||
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
||||||
*/
|
*/
|
||||||
fun simpleEcKeyRing(userId: CharSequence?,
|
fun simpleEcKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
|
||||||
password: String?
|
password.let {
|
||||||
): PGPSecretKeyRing = password.let {
|
if (it.isNullOrBlank()) {
|
||||||
if (it.isNullOrBlank()) {
|
simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
|
||||||
simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
|
} else {
|
||||||
} else {
|
simpleEcKeyRing(userId, Passphrase.fromPassword(it))
|
||||||
simpleEcKeyRing(userId, Passphrase.fromPassword(it))
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify
|
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
|
||||||
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
* certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||||
*
|
*
|
||||||
* @param userId primary user id
|
* @param userId primary user id
|
||||||
* @param passphrase passphrase for the private key. Can be empty for an unencrypted key.
|
* @param passphrase passphrase for the private key. Can be empty for an unencrypted key.
|
||||||
* @return key ring
|
* @return key ring
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun modernKeyRing(userId: CharSequence?,
|
fun modernKeyRing(
|
||||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
userId: CharSequence?,
|
||||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||||
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
): PGPSecretKeyRing =
|
||||||
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
buildKeyRing()
|
||||||
addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
|
.apply {
|
||||||
setPassphrase(passphrase)
|
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||||
if (userId != null) {
|
addSubkey(
|
||||||
addUserId(userId)
|
getBuilder(
|
||||||
}
|
KeyType.XDH(XDHSpec._X25519),
|
||||||
}.build()
|
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
|
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
|
||||||
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
* certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||||
*
|
*
|
||||||
* @param userId primary user id
|
* @param userId primary user id
|
||||||
* @param password passphrase for the private key. Can be null or blank for an unencrypted key.
|
* @param password passphrase for the private key. Can be null or blank for an unencrypted key.
|
||||||
* @return key ring
|
* @return key ring
|
||||||
*/
|
*/
|
||||||
fun modernKeyRing(userId: CharSequence?,
|
fun modernKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
|
||||||
password: String?
|
password.let {
|
||||||
): PGPSecretKeyRing = password.let {
|
if (it.isNullOrBlank()) {
|
||||||
if (it.isNullOrBlank()) {
|
modernKeyRing(userId, Passphrase.emptyPassphrase())
|
||||||
modernKeyRing(userId, Passphrase.emptyPassphrase())
|
} else {
|
||||||
} else {
|
modernKeyRing(userId, Passphrase.fromPassword(it))
|
||||||
modernKeyRing(userId, Passphrase.fromPassword(it))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,18 +4,18 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector
|
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector
|
||||||
import org.pgpainless.algorithm.KeyFlag
|
import org.pgpainless.algorithm.KeyFlag
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
import org.pgpainless.key.generation.type.KeyType
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class KeySpec(
|
data class KeySpec(
|
||||||
val keyType: KeyType,
|
val keyType: KeyType,
|
||||||
val subpacketGenerator: SignatureSubpackets,
|
val subpacketGenerator: SignatureSubpackets,
|
||||||
val isInheritedSubPackets: Boolean,
|
val isInheritedSubPackets: Boolean,
|
||||||
val keyCreationDate: Date
|
val keyCreationDate: Date
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val subpackets: PGPSignatureSubpacketVector
|
val subpackets: PGPSignatureSubpacketVector
|
||||||
|
@ -25,4 +25,4 @@ data class KeySpec(
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags)
|
fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,41 +4,47 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.pgpainless.PGPainless
|
import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.algorithm.*
|
import org.pgpainless.algorithm.*
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
import org.pgpainless.key.generation.type.KeyType
|
||||||
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class KeySpecBuilder constructor(
|
class KeySpecBuilder
|
||||||
private val type: KeyType,
|
constructor(
|
||||||
private val keyFlags: List<KeyFlag>,
|
private val type: KeyType,
|
||||||
|
private val keyFlags: List<KeyFlag>,
|
||||||
) : KeySpecBuilderInterface {
|
) : KeySpecBuilderInterface {
|
||||||
|
|
||||||
private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets()
|
private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets()
|
||||||
private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite
|
private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite
|
||||||
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> = algorithmSuite.compressionAlgorithms
|
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> =
|
||||||
|
algorithmSuite.compressionAlgorithms
|
||||||
private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms
|
private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms
|
||||||
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> = algorithmSuite.symmetricKeyAlgorithms
|
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> =
|
||||||
|
algorithmSuite.symmetricKeyAlgorithms
|
||||||
private var keyCreationDate = Date()
|
private var keyCreationDate = Date()
|
||||||
|
|
||||||
constructor(type: KeyType, vararg keyFlags: KeyFlag): this(type, listOf(*keyFlags))
|
constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray())
|
SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder = apply {
|
override fun overridePreferredCompressionAlgorithms(
|
||||||
this.preferredCompressionAlgorithms = algorithms.toSet()
|
vararg algorithms: CompressionAlgorithm
|
||||||
}
|
): KeySpecBuilder = apply { this.preferredCompressionAlgorithms = algorithms.toSet() }
|
||||||
|
|
||||||
override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = apply {
|
override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder =
|
||||||
this.preferredHashAlgorithms = algorithms.toSet()
|
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)) {
|
require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) {
|
||||||
"NULL (unencrypted) is an invalid symmetric key algorithm preference."
|
"NULL (unencrypted) is an invalid symmetric key algorithm preference."
|
||||||
}
|
}
|
||||||
|
@ -50,14 +56,14 @@ class KeySpecBuilder constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun build(): KeySpec {
|
override fun build(): KeySpec {
|
||||||
return hashedSubpackets.apply {
|
return hashedSubpackets
|
||||||
setKeyFlags(keyFlags)
|
.apply {
|
||||||
setPreferredCompressionAlgorithms(preferredCompressionAlgorithms)
|
setKeyFlags(keyFlags)
|
||||||
setPreferredHashAlgorithms(preferredHashAlgorithms)
|
setPreferredCompressionAlgorithms(preferredCompressionAlgorithms)
|
||||||
setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms)
|
setPreferredHashAlgorithms(preferredHashAlgorithms)
|
||||||
setFeatures(Feature.MODIFICATION_DETECTION)
|
setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms)
|
||||||
}.let {
|
setFeatures(Feature.MODIFICATION_DETECTION)
|
||||||
KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate)
|
}
|
||||||
}
|
.let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,24 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation
|
package org.pgpainless.key.generation
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||||
import org.pgpainless.algorithm.HashAlgorithm
|
import org.pgpainless.algorithm.HashAlgorithm
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
interface KeySpecBuilderInterface {
|
interface KeySpecBuilderInterface {
|
||||||
|
|
||||||
fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder
|
fun overridePreferredCompressionAlgorithms(
|
||||||
|
vararg algorithms: CompressionAlgorithm
|
||||||
|
): KeySpecBuilder
|
||||||
|
|
||||||
fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder
|
fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder
|
||||||
|
|
||||||
fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder
|
fun overridePreferredSymmetricKeyAlgorithms(
|
||||||
|
vararg algorithms: SymmetricKeyAlgorithm
|
||||||
|
): KeySpecBuilder
|
||||||
|
|
||||||
fun setKeyCreationDate(creationDate: Date): KeySpecBuilder
|
fun setKeyCreationDate(creationDate: Date): KeySpecBuilder
|
||||||
|
|
||||||
fun build(): KeySpec
|
fun build(): KeySpec
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,4 @@ package org.pgpainless.key.generation.type
|
||||||
interface KeyLength {
|
interface KeyLength {
|
||||||
|
|
||||||
val length: Int
|
val length: Int
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type
|
package org.pgpainless.key.generation.type
|
||||||
|
|
||||||
|
import java.security.spec.AlgorithmParameterSpec
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.key.generation.type.ecc.EllipticCurve
|
import org.pgpainless.key.generation.type.ecc.EllipticCurve
|
||||||
import org.pgpainless.key.generation.type.ecc.ecdh.ECDH
|
import org.pgpainless.key.generation.type.ecc.ecdh.ECDH
|
||||||
|
@ -14,7 +15,6 @@ import org.pgpainless.key.generation.type.rsa.RSA
|
||||||
import org.pgpainless.key.generation.type.rsa.RsaLength
|
import org.pgpainless.key.generation.type.rsa.RsaLength
|
||||||
import org.pgpainless.key.generation.type.xdh.XDH
|
import org.pgpainless.key.generation.type.xdh.XDH
|
||||||
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
||||||
import java.security.spec.AlgorithmParameterSpec
|
|
||||||
|
|
||||||
@Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420
|
@Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420
|
||||||
interface KeyType {
|
interface KeyType {
|
||||||
|
@ -35,20 +35,22 @@ interface KeyType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the strength of the key in bits.
|
* Return the strength of the key in bits.
|
||||||
|
*
|
||||||
* @return strength of the key in bits
|
* @return strength of the key in bits
|
||||||
*/
|
*/
|
||||||
val bitStrength: Int
|
val bitStrength: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key.
|
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the
|
||||||
|
* key.
|
||||||
*
|
*
|
||||||
* @return algorithm parameter spec
|
* @return algorithm parameter spec
|
||||||
*/
|
*/
|
||||||
val algorithmSpec: AlgorithmParameterSpec
|
val algorithmSpec: AlgorithmParameterSpec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag.
|
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key
|
||||||
* See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
|
* flag. See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
|
||||||
*
|
*
|
||||||
* @return true if the key can sign.
|
* @return true if the key can sign.
|
||||||
*/
|
*/
|
||||||
|
@ -56,8 +58,8 @@ interface KeyType {
|
||||||
@JvmName("canSign") get() = algorithm.signingCapable
|
@JvmName("canSign") get() = algorithm.signingCapable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag.
|
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER
|
||||||
* See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
|
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
|
||||||
*
|
*
|
||||||
* @return true if the key is able to certify other keys
|
* @return true if the key is able to certify other keys
|
||||||
*/
|
*/
|
||||||
|
@ -65,8 +67,8 @@ interface KeyType {
|
||||||
@JvmName("canCertify") get() = canSign
|
@JvmName("canCertify") get() = canSign
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag.
|
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION
|
||||||
* See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
|
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
|
||||||
*
|
*
|
||||||
* @return true if the key can be used for authentication purposes.
|
* @return true if the key can be used for authentication purposes.
|
||||||
*/
|
*/
|
||||||
|
@ -74,8 +76,8 @@ interface KeyType {
|
||||||
@JvmName("canAuthenticate") get() = canSign
|
@JvmName("canAuthenticate") get() = canSign
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag.
|
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS
|
||||||
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
|
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
|
||||||
*
|
*
|
||||||
* @return true if the key can encrypt communication
|
* @return true if the key can encrypt communication
|
||||||
*/
|
*/
|
||||||
|
@ -83,8 +85,8 @@ interface KeyType {
|
||||||
@JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable
|
@JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag.
|
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE
|
||||||
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
|
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
|
||||||
*
|
*
|
||||||
* @return true if the key can encrypt for storage
|
* @return true if the key can encrypt for storage
|
||||||
*/
|
*/
|
||||||
|
@ -92,19 +94,14 @@ interface KeyType {
|
||||||
@JvmName("canEncryptStorage") get() = algorithm.encryptionCapable
|
@JvmName("canEncryptStorage") get() = algorithm.encryptionCapable
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun RSA(length: RsaLength): RSA = RSA.withLength(length)
|
||||||
fun RSA(length: RsaLength): RSA = RSA.withLength(length)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
|
||||||
fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
|
||||||
fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
|
||||||
fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
|
||||||
fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,20 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type.ecc
|
package org.pgpainless.key.generation.type.ecc
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and
|
* Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and
|
||||||
* [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA].
|
* [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. For curve25519 related curve definitions
|
||||||
* For curve25519 related curve definitions see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve].
|
* see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve].
|
||||||
*/
|
*/
|
||||||
enum class EllipticCurve(
|
enum class EllipticCurve(val curveName: String, val bitStrength: Int) {
|
||||||
val curveName: String,
|
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see
|
||||||
val bitStrength: Int
|
// https://tools.ietf.org/search/rfc4492#page-32
|
||||||
) {
|
_P384(
|
||||||
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32
|
"secp384r1",
|
||||||
_P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
|
384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
|
||||||
_P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
|
_P521(
|
||||||
|
"secp521r1",
|
||||||
|
521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
|
||||||
_SECP256K1("secp256k1", 256),
|
_SECP256K1("secp256k1", 256),
|
||||||
_BRAINPOOLP256R1("brainpoolP256r1", 256),
|
_BRAINPOOLP256R1("brainpoolP256r1", 256),
|
||||||
_BRAINPOOLP384R1("brainpoolP384r1", 384),
|
_BRAINPOOLP384R1("brainpoolP384r1", 384),
|
||||||
|
@ -24,4 +25,4 @@ enum class EllipticCurve(
|
||||||
;
|
;
|
||||||
|
|
||||||
fun getName(): String = curveName
|
fun getName(): String = curveName
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ class ECDH private constructor(val curve: EllipticCurve) : KeyType {
|
||||||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun fromCurve(curve: EllipticCurve) = ECDH(curve)
|
||||||
fun fromCurve(curve: EllipticCurve) = ECDH(curve)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ class ECDSA private constructor(val curve: EllipticCurve) : KeyType {
|
||||||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
|
||||||
fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ class EdDSA private constructor(val curve: EdDSACurve) : KeyType {
|
||||||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
|
||||||
fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type.eddsa
|
package org.pgpainless.key.generation.type.eddsa
|
||||||
|
|
||||||
enum class EdDSACurve(
|
enum class EdDSACurve(val curveName: String, val bitStrength: Int) {
|
||||||
val curveName: String,
|
|
||||||
val bitStrength: Int) {
|
|
||||||
_Ed25519("ed25519", 256),
|
_Ed25519("ed25519", 256),
|
||||||
;
|
;
|
||||||
|
|
||||||
fun getName() = curveName
|
fun getName() = curveName
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ class ElGamal private constructor(length: ElGamalLength) : KeyType {
|
||||||
override val algorithmSpec = ElGamalParameterSpec(length.p, length.g)
|
override val algorithmSpec = ElGamalParameterSpec(length.p, length.g)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun withLength(length: ElGamalLength) = ElGamal(length)
|
||||||
fun withLength(length: ElGamalLength) = ElGamal(length)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,61 +4,54 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type.elgamal
|
package org.pgpainless.key.generation.type.elgamal
|
||||||
|
|
||||||
import org.pgpainless.key.generation.type.KeyLength
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
import org.pgpainless.key.generation.type.KeyLength
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The following primes are taken from RFC-3526.
|
* The following primes are taken from RFC-3526.
|
||||||
*
|
*
|
||||||
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt">
|
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt"> RFC-3526: More Modular Exponential (MODP)
|
||||||
* RFC-3526: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
|
* Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
|
||||||
*
|
|
||||||
* @deprecated the use of ElGamal keys is no longer recommended.
|
* @deprecated the use of ElGamal keys is no longer recommended.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Deprecated("The use of ElGamal keys is no longer recommended.")
|
@Deprecated("The use of ElGamal keys is no longer recommended.")
|
||||||
enum class ElGamalLength(
|
enum class ElGamalLength(override val length: Int, p: String, g: String) : KeyLength {
|
||||||
override val length: Int,
|
|
||||||
p: String,
|
|
||||||
g: String
|
|
||||||
) : KeyLength {
|
|
||||||
|
|
||||||
/**
|
/** prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. generator: 2 */
|
||||||
* prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }.
|
_1536(
|
||||||
* generator: 2
|
1536,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
|
||||||
_1536(1536, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "2"),
|
"2"),
|
||||||
|
|
||||||
/**
|
/** prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. generator: 2 */
|
||||||
* prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }.
|
_2048(
|
||||||
* generator: 2
|
2048,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF",
|
||||||
_2048(2048, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "2"),
|
"2"),
|
||||||
|
|
||||||
/**
|
/** prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. generator: 2 */
|
||||||
* prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }.
|
_3072(
|
||||||
* generator: 2
|
3072,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
|
||||||
_3072(3072, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "2"),
|
"2"),
|
||||||
|
|
||||||
/**
|
/** prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. generator: 2 */
|
||||||
* prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }.
|
_4096(
|
||||||
* generator: 2
|
4096,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF",
|
||||||
_4096(4096, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", "2"),
|
"2"),
|
||||||
|
|
||||||
/**
|
/** prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. generator: 2 */
|
||||||
* prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }.
|
_6144(
|
||||||
* generator: 2
|
6144,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF",
|
||||||
_6144(6144, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", "2"),
|
"2"),
|
||||||
|
|
||||||
/**
|
/** prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. generator: 2 */
|
||||||
* prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }.
|
_8192(
|
||||||
* generator: 2
|
8192,
|
||||||
*/
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF",
|
||||||
_8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2")
|
"2");
|
||||||
;
|
|
||||||
|
|
||||||
val p: BigInteger
|
val p: BigInteger
|
||||||
val g: BigInteger
|
val g: BigInteger
|
||||||
|
@ -67,6 +60,4 @@ enum class ElGamalLength(
|
||||||
this.p = BigInteger(p, 16)
|
this.p = BigInteger(p, 16)
|
||||||
this.g = BigInteger(g, 16)
|
this.g = BigInteger(g, 16)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,14 +4,12 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type.rsa
|
package org.pgpainless.key.generation.type.rsa
|
||||||
|
|
||||||
|
import java.security.spec.RSAKeyGenParameterSpec
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
import org.pgpainless.key.generation.type.KeyType
|
||||||
import java.security.spec.RSAKeyGenParameterSpec
|
|
||||||
|
|
||||||
/**
|
/** Key type that specifies the RSA_GENERAL algorithm. */
|
||||||
* Key type that specifies the RSA_GENERAL algorithm.
|
class RSA private constructor(length: RsaLength) : KeyType {
|
||||||
*/
|
|
||||||
class RSA private constructor(length: RsaLength): KeyType {
|
|
||||||
|
|
||||||
override val name = "RSA"
|
override val name = "RSA"
|
||||||
override val algorithm = PublicKeyAlgorithm.RSA_GENERAL
|
override val algorithm = PublicKeyAlgorithm.RSA_GENERAL
|
||||||
|
@ -19,7 +17,6 @@ class RSA private constructor(length: RsaLength): KeyType {
|
||||||
override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4)
|
override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun withLength(length: RsaLength) = RSA(length)
|
||||||
fun withLength(length: RsaLength) = RSA(length)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,4 @@ enum class RsaLength(override val length: Int) : KeyLength {
|
||||||
_3072(3072),
|
_3072(3072),
|
||||||
_4096(4096),
|
_4096(4096),
|
||||||
_8192(8192)
|
_8192(8192)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,13 @@ import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||||
import org.pgpainless.key.generation.type.KeyType
|
import org.pgpainless.key.generation.type.KeyType
|
||||||
|
|
||||||
class XDH private constructor(spec: XDHSpec): KeyType {
|
class XDH private constructor(spec: XDHSpec) : KeyType {
|
||||||
override val name = "XDH"
|
override val name = "XDH"
|
||||||
override val algorithm = PublicKeyAlgorithm.ECDH
|
override val algorithm = PublicKeyAlgorithm.ECDH
|
||||||
override val bitStrength = spec.bitStrength
|
override val bitStrength = spec.bitStrength
|
||||||
override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName)
|
override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic fun fromSpec(spec: XDHSpec) = XDH(spec)
|
||||||
fun fromSpec(spec: XDHSpec) = XDH(spec)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
|
|
||||||
package org.pgpainless.key.generation.type.xdh
|
package org.pgpainless.key.generation.type.xdh
|
||||||
|
|
||||||
enum class XDHSpec(
|
enum class XDHSpec(val algorithmName: String, val curveName: String, val bitStrength: Int) {
|
||||||
val algorithmName: String,
|
|
||||||
val curveName: String,
|
|
||||||
val bitStrength: Int) {
|
|
||||||
_X25519("X25519", "curve25519", 256),
|
_X25519("X25519", "curve25519", 256),
|
||||||
;
|
;
|
||||||
|
|
||||||
fun getName() = algorithmName
|
fun getName() = algorithmName
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,78 +11,79 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||||
import org.pgpainless.key.SubkeyIdentifier
|
import org.pgpainless.key.SubkeyIdentifier
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||||
|
|
||||||
abstract class KeyAccessor(
|
abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: SubkeyIdentifier) {
|
||||||
protected val info: KeyRingInfo,
|
|
||||||
protected val key: SubkeyIdentifier
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the way we address the key (key-id or user-id), return the respective [PGPSignature]
|
* Depending on the way we address the key (key-id or user-id), return the respective
|
||||||
* which contains the algorithm preferences we are going to use.
|
* [PGPSignature] which contains the algorithm preferences we are going to use.
|
||||||
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification,
|
* If we address a key via its user-id, we want to rely on the algorithm preferences in the
|
||||||
* while we would instead rely on those in the direct-key signature if we'd address the key by key-id.
|
* user-id certification, while we would instead rely on those in the direct-key signature if
|
||||||
|
* we'd address the key by key-id.
|
||||||
*
|
*
|
||||||
* @return signature
|
* @return signature
|
||||||
*/
|
*/
|
||||||
abstract val signatureWithPreferences: PGPSignature
|
abstract val signatureWithPreferences: PGPSignature
|
||||||
|
|
||||||
/**
|
/** Preferred symmetric key encryption algorithms. */
|
||||||
* Preferred symmetric key encryption algorithms.
|
|
||||||
*/
|
|
||||||
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
|
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
|
||||||
get() = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences)
|
get() =
|
||||||
|
SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences)
|
||||||
|
|
||||||
/**
|
/** Preferred hash algorithms. */
|
||||||
* Preferred hash algorithms.
|
|
||||||
*/
|
|
||||||
val preferredHashAlgorithms: Set<HashAlgorithm>
|
val preferredHashAlgorithms: Set<HashAlgorithm>
|
||||||
get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences)
|
get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences)
|
||||||
|
|
||||||
/**
|
/** Preferred compression algorithms. */
|
||||||
* Preferred compression algorithms.
|
|
||||||
*/
|
|
||||||
val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
|
val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
|
||||||
get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences)
|
get() =
|
||||||
|
SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`).
|
* Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`). In this case we are
|
||||||
* In this case we are sourcing preferred algorithms from the user-id certification first.
|
* sourcing preferred algorithms from the user-id certification first.
|
||||||
*/
|
*/
|
||||||
class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence): KeyAccessor(info, key) {
|
class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence) :
|
||||||
|
KeyAccessor(info, key) {
|
||||||
override val signatureWithPreferences: PGPSignature
|
override val signatureWithPreferences: PGPSignature
|
||||||
get() = checkNotNull(info.getLatestUserIdCertification(userId.toString())) {
|
get() =
|
||||||
"No valid user-id certification signature found for '$userId'."
|
checkNotNull(info.getLatestUserIdCertification(userId.toString())) {
|
||||||
}
|
"No valid user-id certification signature found for '$userId'."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Address the key via key-id.
|
* Address the key via key-id. In this case we are sourcing preferred algorithms from the keys
|
||||||
* In this case we are sourcing preferred algorithms from the keys direct-key signature first.
|
* direct-key signature first.
|
||||||
*/
|
*/
|
||||||
class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
|
class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
|
||||||
override val signatureWithPreferences: PGPSignature
|
override val signatureWithPreferences: PGPSignature
|
||||||
get() {
|
get() {
|
||||||
// If the key is located by Key ID, the algorithm of the primary User ID of the key provides the
|
// If the key is located by Key ID, the algorithm of the primary User ID of the key
|
||||||
|
// provides the
|
||||||
// preferred symmetric algorithm.
|
// preferred symmetric algorithm.
|
||||||
info.primaryUserId?.let {
|
info.primaryUserId?.let { userId ->
|
||||||
userId -> info.getLatestUserIdCertification(userId).let { if (it != null) return it }
|
info.getLatestUserIdCertification(userId).let { if (it != null) return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkNotNull(info.latestDirectKeySelfSignature) { "No valid signature found." }
|
return checkNotNull(info.latestDirectKeySelfSignature) {
|
||||||
|
"No valid signature found."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubKey(info: KeyRingInfo, key: SubkeyIdentifier): KeyAccessor(info, key) {
|
class SubKey(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
|
||||||
override val signatureWithPreferences: PGPSignature
|
override val signatureWithPreferences: PGPSignature
|
||||||
get() = checkNotNull(
|
get() =
|
||||||
|
checkNotNull(
|
||||||
if (key.isPrimaryKey) {
|
if (key.isPrimaryKey) {
|
||||||
info.latestDirectKeySelfSignature ?:
|
info.latestDirectKeySelfSignature
|
||||||
info.primaryUserId?.let { info.getLatestUserIdCertification(it) }
|
?: info.primaryUserId?.let { info.getLatestUserIdCertification(it) }
|
||||||
} else {
|
} else {
|
||||||
info.getCurrentSubkeyBindingSignature(key.subkeyId)
|
info.getCurrentSubkeyBindingSignature(key.subkeyId)
|
||||||
|
}) {
|
||||||
|
"No valid signature found."
|
||||||
}
|
}
|
||||||
) { "No valid signature found." }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,64 +12,70 @@ import org.bouncycastle.openpgp.PGPPublicKey
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey
|
import org.bouncycastle.openpgp.PGPSecretKey
|
||||||
|
|
||||||
@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.")
|
@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.")
|
||||||
class KeyInfo private constructor(
|
class KeyInfo private constructor(val secretKey: PGPSecretKey?, val publicKey: PGPPublicKey) {
|
||||||
val secretKey: PGPSecretKey?,
|
|
||||||
val publicKey: PGPPublicKey) {
|
|
||||||
|
|
||||||
constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey)
|
constructor(secretKey: PGPSecretKey) : this(secretKey, secretKey.publicKey)
|
||||||
constructor(publicKey: PGPPublicKey): this(null, publicKey)
|
|
||||||
|
constructor(publicKey: PGPPublicKey) : this(null, publicKey)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key
|
* Return the name of the elliptic curve used by this key, or throw an
|
||||||
* is not based on elliptic curves, or on an unknown curve.
|
* [IllegalArgumentException] if the key is not based on elliptic curves, or on an unknown
|
||||||
|
* curve.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("publicKey.getCurveName()"))
|
"Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.",
|
||||||
|
ReplaceWith("publicKey.getCurveName()"))
|
||||||
val curveName: String
|
val curveName: String
|
||||||
get() = publicKey.getCurveName()
|
get() = publicKey.getCurveName()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the secret key is encrypted.
|
* Return true, if the secret key is encrypted. This method returns false, if the secret key is
|
||||||
* This method returns false, if the secret key is null.
|
* null.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.isEncrypted()"))
|
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.isEncrypted()"))
|
||||||
val isEncrypted: Boolean
|
val isEncrypted: Boolean
|
||||||
get() = secretKey?.isEncrypted() ?: false
|
get() = secretKey?.isEncrypted() ?: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the secret key is decrypted.
|
* Return true, if the secret key is decrypted. This method returns true, if the secret key is
|
||||||
* This method returns true, if the secret key is null.
|
* null.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.isDecrypted()"))
|
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.isDecrypted()"))
|
||||||
val isDecrypted: Boolean
|
val isDecrypted: Boolean
|
||||||
get() = secretKey?.isDecrypted() ?: true
|
get() = secretKey?.isDecrypted() ?: true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the secret key is using the GNU_DUMMY_S2K s2k type.
|
* Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. This method returns
|
||||||
* This method returns false, if the secret key is null.
|
* false, if the secret key is null.
|
||||||
*/
|
*/
|
||||||
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||||
val hasDummyS2K: Boolean
|
val hasDummyS2K: Boolean
|
||||||
@JvmName("hasDummyS2K")
|
@JvmName("hasDummyS2K") get() = secretKey?.hasDummyS2K() ?: false
|
||||||
get() = secretKey?.hasDummyS2K() ?: false
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.isEncrypted()"))
|
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.isEncrypted()"))
|
||||||
fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted()
|
fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.isDecrypted()"))
|
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.isDecrypted()"))
|
||||||
fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted()
|
fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
@Deprecated(
|
||||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||||
|
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||||
fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K()
|
fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue