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.
|
||||
*
|
||||
* Since '0' is a special date value in the OpenPGP specification
|
||||
* (e.g. '0' means no expiration for expiration dates), this method will return 'null' if seconds is 0.
|
||||
* Since '0' is a special date value in the OpenPGP specification (e.g. '0' means no expiration for
|
||||
* expiration dates), this method will return 'null' if seconds is 0.
|
||||
*
|
||||
* @param date date
|
||||
* @param seconds number of seconds to be added
|
||||
* @return date plus seconds or null if seconds is '0'
|
||||
*/
|
||||
fun Date.plusSeconds(seconds: Long): Date? {
|
||||
require(Long.MAX_VALUE - time > seconds) { "Adding $seconds seconds to this date would cause time to overflow." }
|
||||
return if (seconds == 0L) null
|
||||
else Date(this.time + 1000 * seconds)
|
||||
require(Long.MAX_VALUE - time > seconds) {
|
||||
"Adding $seconds seconds to this date would cause time to overflow."
|
||||
}
|
||||
return if (seconds == 0L) null else Date(this.time + 1000 * seconds)
|
||||
}
|
||||
|
||||
val Date.asSeconds: Long
|
||||
get() = time / 1000
|
||||
|
||||
fun Date.secondsTill(later: Date): Long {
|
||||
require(this <= later) {
|
||||
"Timestamp MUST be before the later timestamp."
|
||||
}
|
||||
require(this <= later) { "Timestamp MUST be before the later timestamp." }
|
||||
return later.asSeconds - this.asSeconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new [Date] instance with this instance's time floored down to seconds precision.
|
||||
*/
|
||||
/** Return a new [Date] instance with this instance's time floored down to seconds precision. */
|
||||
fun Date.toSecondsPrecision(): Date {
|
||||
return Date(asSeconds * 1000)
|
||||
}
|
||||
|
||||
internal val parser: SimpleDateFormat
|
||||
// Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation.
|
||||
get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss z")
|
||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
// Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every
|
||||
// invocation.
|
||||
get() =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
|
||||
/**
|
||||
* Format a date as UTC timestamp.
|
||||
|
@ -55,12 +53,13 @@ fun Date.formatUTC(): String = parser.format(this)
|
|||
|
||||
/**
|
||||
* Parse a UTC timestamp into a date.
|
||||
*
|
||||
* @return date
|
||||
*/
|
||||
fun String.parseUTC(): Date {
|
||||
return try {
|
||||
parser.parse(this)
|
||||
} catch (e : ParseException) {
|
||||
} catch (e: ParseException) {
|
||||
throw IllegalArgumentException("Malformed UTC timestamp: $this", e)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,22 +4,18 @@
|
|||
|
||||
package openpgp
|
||||
|
||||
/**
|
||||
* Format this Long as an OpenPGP key-ID (16 digit uppercase hex number).
|
||||
*/
|
||||
/** Format this Long as an OpenPGP key-ID (16 digit uppercase hex number). */
|
||||
fun Long.openPgpKeyId(): String {
|
||||
return String.format("%016X", this).uppercase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a Long form a 16 digit hex encoded OpenPgp key-ID.
|
||||
*/
|
||||
/** Parse a Long form a 16 digit hex encoded OpenPgp key-ID. */
|
||||
fun Long.Companion.fromOpenPgpKeyId(hexKeyId: String): Long {
|
||||
require("^[0-9A-Fa-f]{16}$".toRegex().matches(hexKeyId)) {
|
||||
"Provided long key-id does not match expected format. " +
|
||||
"A long key-id consists of 16 hexadecimal characters."
|
||||
"A long key-id consists of 16 hexadecimal characters."
|
||||
}
|
||||
// Calling toLong() only fails with a NumberFormatException.
|
||||
// Therefore, we call toULong(16).toLong(), which seems to work.
|
||||
return hexKeyId.toULong(16).toLong()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,40 +11,46 @@ import org.pgpainless.decryption_verification.CustomPublicKeyDataDecryptorFactor
|
|||
import org.pgpainless.key.SubkeyIdentifier
|
||||
|
||||
/**
|
||||
* Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys.
|
||||
* That way, if a message needs to be decrypted multiple times, expensive private key operations can be omitted.
|
||||
* Implementation of the [PublicKeyDataDecryptorFactory] which caches decrypted session keys. That
|
||||
* way, if a message needs to be decrypted multiple times, expensive private key operations can be
|
||||
* omitted.
|
||||
*
|
||||
* This implementation changes the behavior or [recoverSessionData] to first return any
|
||||
* cache hits.
|
||||
* If no hit is found, the method call is delegated to the underlying [PublicKeyDataDecryptorFactory].
|
||||
* The result of that is then placed in the cache and returned.
|
||||
* This implementation changes the behavior or [recoverSessionData] to first return any cache hits.
|
||||
* If no hit is found, the method call is delegated to the underlying
|
||||
* [PublicKeyDataDecryptorFactory]. The result of that is then placed in the cache and returned.
|
||||
*/
|
||||
class CachingBcPublicKeyDataDecryptorFactory(
|
||||
privateKey: PGPPrivateKey,
|
||||
override val subkeyIdentifier: SubkeyIdentifier
|
||||
privateKey: PGPPrivateKey,
|
||||
override val subkeyIdentifier: SubkeyIdentifier
|
||||
) : BcPublicKeyDataDecryptorFactory(privateKey), CustomPublicKeyDataDecryptorFactory {
|
||||
|
||||
private val cachedSessions: MutableMap<String, ByteArray> = mutableMapOf()
|
||||
|
||||
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray =
|
||||
lookupSessionKeyData(secKeyData) ?:
|
||||
costlyRecoverSessionData(keyAlgorithm, secKeyData)
|
||||
.also { cacheSessionKeyData(secKeyData, it) }
|
||||
override fun recoverSessionData(
|
||||
keyAlgorithm: Int,
|
||||
secKeyData: Array<out ByteArray>
|
||||
): ByteArray =
|
||||
lookupSessionKeyData(secKeyData)
|
||||
?: costlyRecoverSessionData(keyAlgorithm, secKeyData).also {
|
||||
cacheSessionKeyData(secKeyData, it)
|
||||
}
|
||||
|
||||
private fun lookupSessionKeyData(secKeyData: Array<out ByteArray>): ByteArray? =
|
||||
cachedSessions[toKey(secKeyData)]?.clone()
|
||||
cachedSessions[toKey(secKeyData)]?.clone()
|
||||
|
||||
private fun costlyRecoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray =
|
||||
super.recoverSessionData(keyAlgorithm, secKeyData)
|
||||
private fun costlyRecoverSessionData(
|
||||
keyAlgorithm: Int,
|
||||
secKeyData: Array<out ByteArray>
|
||||
): ByteArray = super.recoverSessionData(keyAlgorithm, secKeyData)
|
||||
|
||||
private fun cacheSessionKeyData(secKeyData: Array<out ByteArray>, sessionKey: ByteArray) {
|
||||
cachedSessions[toKey(secKeyData)] = sessionKey.clone()
|
||||
}
|
||||
|
||||
private fun toKey(secKeyData: Array<out ByteArray>): String =
|
||||
Base64.toBase64String(secKeyData[0])
|
||||
Base64.toBase64String(secKeyData[0])
|
||||
|
||||
fun clear() {
|
||||
cachedSessions.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,10 @@ import org.pgpainless.PGPainless
|
|||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
|
||||
/**
|
||||
* Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier].
|
||||
*/
|
||||
/** Return true, if this [PGPKeyRing] contains the subkey identified by the [SubkeyIdentifier]. */
|
||||
fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
|
||||
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
|
||||
this.getPublicKey(subkeyIdentifier.subkeyId) != null
|
||||
this.publicKey.keyID == subkeyIdentifier.primaryKeyId &&
|
||||
this.getPublicKey(subkeyIdentifier.subkeyId) != null
|
||||
|
||||
/**
|
||||
* Return true, if the [PGPKeyRing] contains a public key with the given key-ID.
|
||||
|
@ -26,8 +24,7 @@ fun PGPKeyRing.matches(subkeyIdentifier: SubkeyIdentifier): Boolean =
|
|||
* @param keyId keyId
|
||||
* @return true if key with the given key-ID is present, false otherwise
|
||||
*/
|
||||
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
|
||||
this.getPublicKey(keyId) != null
|
||||
fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean = this.getPublicKey(keyId) != null
|
||||
|
||||
/**
|
||||
* Return true, if the [PGPKeyRing] contains a public key with the given fingerprint.
|
||||
|
@ -36,7 +33,7 @@ fun PGPKeyRing.hasPublicKey(keyId: Long): Boolean =
|
|||
* @return true if key with the given fingerprint is present, false otherwise
|
||||
*/
|
||||
fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean =
|
||||
this.getPublicKey(fingerprint) != null
|
||||
this.getPublicKey(fingerprint) != null
|
||||
|
||||
/**
|
||||
* Return the [PGPPublicKey] with the given [OpenPgpFingerprint] or null, if no such key is present.
|
||||
|
@ -45,36 +42,33 @@ fun PGPKeyRing.hasPublicKey(fingerprint: OpenPgpFingerprint): Boolean =
|
|||
* @return public key
|
||||
*/
|
||||
fun PGPKeyRing.getPublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey? =
|
||||
this.getPublicKey(fingerprint.bytes)
|
||||
this.getPublicKey(fingerprint.bytes)
|
||||
|
||||
fun PGPKeyRing.requirePublicKey(keyId: Long): PGPPublicKey =
|
||||
getPublicKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||
getPublicKey(keyId)
|
||||
?: throw NoSuchElementException(
|
||||
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||
|
||||
fun PGPKeyRing.requirePublicKey(fingerprint: OpenPgpFingerprint): PGPPublicKey =
|
||||
getPublicKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||
getPublicKey(fingerprint)
|
||||
?: throw NoSuchElementException(
|
||||
"OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||
|
||||
/**
|
||||
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature].
|
||||
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to
|
||||
* identify the [PGPPublicKey] via its key-ID.
|
||||
* Return the [PGPPublicKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
|
||||
* the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
|
||||
* subpacket to identify the [PGPPublicKey] via its key-ID.
|
||||
*/
|
||||
fun PGPKeyRing.getPublicKeyFor(signature: PGPSignature): PGPPublicKey? =
|
||||
signature.fingerprint?.let { this.getPublicKey(it) } ?:
|
||||
this.getPublicKey(signature.keyID)
|
||||
signature.fingerprint?.let { this.getPublicKey(it) } ?: this.getPublicKey(signature.keyID)
|
||||
|
||||
/**
|
||||
* Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
|
||||
*/
|
||||
/** Return the [PGPPublicKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
|
||||
fun PGPKeyRing.getPublicKeyFor(onePassSignature: PGPOnePassSignature): PGPPublicKey? =
|
||||
this.getPublicKey(onePassSignature.keyID)
|
||||
this.getPublicKey(onePassSignature.keyID)
|
||||
|
||||
/**
|
||||
* Return the [OpenPgpFingerprint] of this OpenPGP key.
|
||||
*/
|
||||
/** Return the [OpenPgpFingerprint] of this OpenPGP key. */
|
||||
val PGPKeyRing.openPgpFingerprint: OpenPgpFingerprint
|
||||
get() = OpenPgpFingerprint.of(this)
|
||||
|
||||
/**
|
||||
* Return this OpenPGP key as an ASCII armored String.
|
||||
*/
|
||||
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)
|
||||
/** Return this OpenPGP key as an ASCII armored String. */
|
||||
fun PGPKeyRing.toAsciiArmor(): String = PGPainless.asciiArmor(this)
|
||||
|
|
|
@ -15,8 +15,8 @@ import org.pgpainless.key.OpenPgpFingerprint
|
|||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
||||
|
||||
/**
|
||||
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and [PublicKeyAlgorithm.EDDSA],
|
||||
* this method returns the name of the underlying elliptic curve.
|
||||
* For secret keys of types [PublicKeyAlgorithm.ECDSA], [PublicKeyAlgorithm.ECDH] and
|
||||
* [PublicKeyAlgorithm.EDDSA], this method returns the name of the underlying elliptic curve.
|
||||
*
|
||||
* For other key types or unknown curves, this method throws an [IllegalArgumentException].
|
||||
*
|
||||
|
@ -24,27 +24,29 @@ import org.pgpainless.key.generation.type.eddsa.EdDSACurve
|
|||
*/
|
||||
fun PGPPublicKey.getCurveName(): String {
|
||||
PublicKeyAlgorithm.requireFromId(algorithm)
|
||||
.let {
|
||||
when (it) {
|
||||
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
|
||||
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
|
||||
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
|
||||
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
|
||||
}
|
||||
.let {
|
||||
when (it) {
|
||||
PublicKeyAlgorithm.ECDSA -> publicKeyPacket.key as ECDSAPublicBCPGKey
|
||||
PublicKeyAlgorithm.ECDH -> publicKeyPacket.key as ECDHPublicBCPGKey
|
||||
PublicKeyAlgorithm.EDDSA -> publicKeyPacket.key as EdDSAPublicBCPGKey
|
||||
else -> throw IllegalArgumentException("No an elliptic curve public key ($it).")
|
||||
}
|
||||
.let { if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName else it.curveOID}
|
||||
.let { it to ECUtil.getCurveName(it) }
|
||||
.let { if (it.second != null) return it.second else throw IllegalArgumentException("Unknown curve: ${it.first}") }
|
||||
}
|
||||
.let {
|
||||
if (it.curveOID == GNUObjectIdentifiers.Ed25519) return EdDSACurve._Ed25519.curveName
|
||||
else it.curveOID
|
||||
}
|
||||
.let { it to ECUtil.getCurveName(it) }
|
||||
.let {
|
||||
if (it.second != null) return it.second
|
||||
else throw IllegalArgumentException("Unknown curve: ${it.first}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [PublicKeyAlgorithm] of this key.
|
||||
*/
|
||||
/** Return the [PublicKeyAlgorithm] of this key. */
|
||||
val PGPPublicKey.publicKeyAlgorithm: PublicKeyAlgorithm
|
||||
get() = PublicKeyAlgorithm.requireFromId(algorithm)
|
||||
|
||||
/**
|
||||
* Return the [OpenPgpFingerprint] of this key.
|
||||
*/
|
||||
/** Return the [OpenPgpFingerprint] of this key. */
|
||||
val PGPPublicKey.openPgpFingerprint: OpenPgpFingerprint
|
||||
get() = OpenPgpFingerprint.of(this)
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.bouncycastle.extensions
|
|||
import org.bouncycastle.bcpg.S2K
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
|
@ -28,7 +27,7 @@ import org.pgpainless.util.Passphrase
|
|||
*/
|
||||
@Throws(PGPException::class, KeyIntegrityException::class)
|
||||
fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
|
||||
UnlockSecretKey.unlockSecretKey(this, passphrase)
|
||||
UnlockSecretKey.unlockSecretKey(this, passphrase)
|
||||
|
||||
/**
|
||||
* Unlock the secret key to get its [PGPPrivateKey].
|
||||
|
@ -39,8 +38,9 @@ fun PGPSecretKey.unlock(passphrase: Passphrase): PGPPrivateKey =
|
|||
*/
|
||||
@Throws(PGPException::class, KeyIntegrityException::class)
|
||||
@JvmOverloads
|
||||
fun PGPSecretKey.unlock(protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()): PGPPrivateKey =
|
||||
UnlockSecretKey.unlockSecretKey(this, protector)
|
||||
fun PGPSecretKey.unlock(
|
||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||
): PGPPrivateKey = UnlockSecretKey.unlockSecretKey(this, protector)
|
||||
|
||||
/**
|
||||
* Unlock the secret key to get its [PGPPrivateKey].
|
||||
|
@ -74,14 +74,10 @@ fun PGPSecretKey?.isDecrypted(): Boolean = (this == null) || (s2KUsage == 0)
|
|||
*/
|
||||
fun PGPSecretKey?.hasDummyS2K(): Boolean = (this != null) && (s2K?.type == S2K.GNU_DUMMY_S2K)
|
||||
|
||||
/**
|
||||
* Return the [PublicKeyAlgorithm] of this key.
|
||||
*/
|
||||
/** Return the [PublicKeyAlgorithm] of this key. */
|
||||
val PGPSecretKey.publicKeyAlgorithm: PublicKeyAlgorithm
|
||||
get() = publicKey.publicKeyAlgorithm
|
||||
|
||||
/**
|
||||
* Return the [OpenPgpFingerprint] of this key.
|
||||
*/
|
||||
/** Return the [OpenPgpFingerprint] of this key. */
|
||||
val PGPSecretKey.openPgpFingerprint: OpenPgpFingerprint
|
||||
get() = OpenPgpFingerprint.of(this)
|
||||
|
|
|
@ -8,9 +8,7 @@ import openpgp.openPgpKeyId
|
|||
import org.bouncycastle.openpgp.*
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
|
||||
/**
|
||||
* OpenPGP certificate containing the public keys of this OpenPGP key.
|
||||
*/
|
||||
/** OpenPGP certificate containing the public keys of this OpenPGP key. */
|
||||
val PGPSecretKeyRing.certificate: PGPPublicKeyRing
|
||||
get() = PGPPublicKeyRing(this.publicKeys.asSequence().toList())
|
||||
|
||||
|
@ -20,8 +18,7 @@ val PGPSecretKeyRing.certificate: PGPPublicKeyRing
|
|||
* @param keyId keyId of the secret key
|
||||
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
||||
*/
|
||||
fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean =
|
||||
this.getSecretKey(keyId) != null
|
||||
fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean = this.getSecretKey(keyId) != null
|
||||
|
||||
/**
|
||||
* Return true, if the [PGPSecretKeyRing] contains a [PGPSecretKey] with the given fingerprint.
|
||||
|
@ -30,7 +27,7 @@ fun PGPSecretKeyRing.hasSecretKey(keyId: Long): Boolean =
|
|||
* @return true, if the [PGPSecretKeyRing] has a matching [PGPSecretKey], false otherwise
|
||||
*/
|
||||
fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
|
||||
this.getSecretKey(fingerprint) != null
|
||||
this.getSecretKey(fingerprint) != null
|
||||
|
||||
/**
|
||||
* Return the [PGPSecretKey] with the given [OpenPgpFingerprint].
|
||||
|
@ -39,41 +36,44 @@ fun PGPSecretKeyRing.hasSecretKey(fingerprint: OpenPgpFingerprint): Boolean =
|
|||
* @return the secret key or null
|
||||
*/
|
||||
fun PGPSecretKeyRing.getSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey? =
|
||||
this.getSecretKey(fingerprint.bytes)
|
||||
this.getSecretKey(fingerprint.bytes)
|
||||
|
||||
/**
|
||||
* Return the [PGPSecretKey] with the given key-ID.
|
||||
*
|
||||
* @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given key-ID
|
||||
* @throws NoSuchElementException if the OpenPGP key doesn't contain a secret key with the given
|
||||
* key-ID
|
||||
*/
|
||||
fun PGPSecretKeyRing.requireSecretKey(keyId: Long): PGPSecretKey =
|
||||
getSecretKey(keyId) ?: throw NoSuchElementException("OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||
getSecretKey(keyId)
|
||||
?: throw NoSuchElementException(
|
||||
"OpenPGP key does not contain key with id ${keyId.openPgpKeyId()}.")
|
||||
|
||||
/**
|
||||
* Return the [PGPSecretKey] with the given fingerprint.
|
||||
*
|
||||
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given fingerprint
|
||||
* @throws NoSuchElementException of the OpenPGP key doesn't contain a secret key with the given
|
||||
* fingerprint
|
||||
*/
|
||||
fun PGPSecretKeyRing.requireSecretKey(fingerprint: OpenPgpFingerprint): PGPSecretKey =
|
||||
getSecretKey(fingerprint) ?: throw NoSuchElementException("OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||
getSecretKey(fingerprint)
|
||||
?: throw NoSuchElementException(
|
||||
"OpenPGP key does not contain key with fingerprint $fingerprint.")
|
||||
|
||||
/**
|
||||
* Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature].
|
||||
* If the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID subpacket to
|
||||
* identify the [PGPSecretKey] via its key-ID.
|
||||
* Return the [PGPSecretKey] that matches the [OpenPgpFingerprint] of the given [PGPSignature]. If
|
||||
* the [PGPSignature] does not carry an issuer-fingerprint subpacket, fall back to the issuer-keyID
|
||||
* subpacket to identify the [PGPSecretKey] via its key-ID.
|
||||
*/
|
||||
fun PGPSecretKeyRing.getSecretKeyFor(signature: PGPSignature): PGPSecretKey? =
|
||||
signature.fingerprint?.let { this.getSecretKey(it) } ?:
|
||||
this.getSecretKey(signature.keyID)
|
||||
signature.fingerprint?.let { this.getSecretKey(it) } ?: this.getSecretKey(signature.keyID)
|
||||
|
||||
/**
|
||||
* Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet.
|
||||
*/
|
||||
/** Return the [PGPSecretKey] that matches the key-ID of the given [PGPOnePassSignature] packet. */
|
||||
fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGPSecretKey? =
|
||||
this.getSecretKey(onePassSignature.keyID)
|
||||
this.getSecretKey(onePassSignature.keyID)
|
||||
|
||||
fun PGPSecretKeyRing.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): PGPSecretKey? =
|
||||
when(pkesk.version) {
|
||||
3 -> this.getSecretKey(pkesk.keyID)
|
||||
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
|
||||
}
|
||||
when (pkesk.version) {
|
||||
3 -> this.getSecretKey(pkesk.keyID)
|
||||
else -> throw NotImplementedError("Version 6 PKESKs are not yet supported.")
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.bouncycastle.extensions
|
||||
|
||||
import java.util.*
|
||||
import openpgp.plusSeconds
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
|
@ -12,84 +13,84 @@ import org.pgpainless.algorithm.SignatureType
|
|||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import org.pgpainless.key.util.RevocationAttributes.Reason
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Return the value of the KeyExpirationDate subpacket, or null, if the signature does not carry
|
||||
* such a subpacket.
|
||||
*/
|
||||
fun PGPSignature.getKeyExpirationDate(keyCreationDate: Date): Date? =
|
||||
SignatureSubpacketsUtil.getKeyExpirationTime(this)
|
||||
?.let { keyCreationDate.plusSeconds(it.time) }
|
||||
SignatureSubpacketsUtil.getKeyExpirationTime(this)?.let { keyCreationDate.plusSeconds(it.time) }
|
||||
|
||||
/**
|
||||
* Return the value of the signature ExpirationTime subpacket, or null, if the signature
|
||||
* does not carry such a subpacket.
|
||||
* Return the value of the signature ExpirationTime subpacket, or null, if the signature does not
|
||||
* carry such a subpacket.
|
||||
*/
|
||||
val PGPSignature.signatureExpirationDate: Date?
|
||||
get() = SignatureSubpacketsUtil.getSignatureExpirationTime(this)
|
||||
?.let { this.creationTime.plusSeconds(it.time) }
|
||||
get() =
|
||||
SignatureSubpacketsUtil.getSignatureExpirationTime(this)?.let {
|
||||
this.creationTime.plusSeconds(it.time)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the signature is expired at the given reference time.
|
||||
*/
|
||||
/** Return true, if the signature is expired at the given reference time. */
|
||||
fun PGPSignature.isExpired(referenceTime: Date = Date()) =
|
||||
signatureExpirationDate?.let { referenceTime >= it } ?: false
|
||||
signatureExpirationDate?.let { referenceTime >= it } ?: false
|
||||
|
||||
/**
|
||||
* Return the key-ID of the issuer, determined by examining the IssuerKeyId and IssuerFingerprint
|
||||
* subpackets of the signature.
|
||||
*/
|
||||
val PGPSignature.issuerKeyId: Long
|
||||
get() = when (version) {
|
||||
2, 3 -> keyID
|
||||
else -> {
|
||||
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)
|
||||
?.let { if (it != 0L) it else null }
|
||||
?: fingerprint?.keyId
|
||||
?: 0L
|
||||
get() =
|
||||
when (version) {
|
||||
2,
|
||||
3 -> keyID
|
||||
else -> {
|
||||
SignatureSubpacketsUtil.getIssuerKeyIdAsLong(this)?.let {
|
||||
if (it != 0L) it else null
|
||||
}
|
||||
?: fingerprint?.keyId ?: 0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if the signature was likely issued by a key with the given fingerprint.
|
||||
*/
|
||||
/** Return true, if the signature was likely issued by a key with the given fingerprint. */
|
||||
fun PGPSignature.wasIssuedBy(fingerprint: OpenPgpFingerprint): Boolean =
|
||||
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId)
|
||||
this.fingerprint?.let { it.keyId == fingerprint.keyId } ?: (keyID == fingerprint.keyId)
|
||||
|
||||
/**
|
||||
* Return true, if the signature was likely issued by a key with the given fingerprint.
|
||||
*
|
||||
* @param fingerprint fingerprint bytes
|
||||
*/
|
||||
@Deprecated("Discouraged in favor of method taking an OpenPgpFingerprint.")
|
||||
fun PGPSignature.wasIssuedBy(fingerprint: ByteArray): Boolean =
|
||||
try {
|
||||
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
|
||||
} catch (e : IllegalArgumentException) {
|
||||
// Unknown fingerprint length / format
|
||||
false
|
||||
}
|
||||
|
||||
fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean =
|
||||
wasIssuedBy(OpenPgpFingerprint.of(key))
|
||||
|
||||
/**
|
||||
* Return true, if this signature is a hard revocation.
|
||||
*/
|
||||
val PGPSignature.isHardRevocation
|
||||
get() = when (SignatureType.requireFromCode(signatureType)) {
|
||||
SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION, SignatureType.CERTIFICATION_REVOCATION -> {
|
||||
SignatureSubpacketsUtil.getRevocationReason(this)
|
||||
?.let { Reason.isHardRevocation(it.revocationReason) }
|
||||
?: true // no reason -> hard revocation
|
||||
}
|
||||
else -> false // Not a revocation
|
||||
try {
|
||||
wasIssuedBy(OpenPgpFingerprint.parseFromBinary(fingerprint))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Unknown fingerprint length / format
|
||||
false
|
||||
}
|
||||
|
||||
fun PGPSignature.wasIssuedBy(key: PGPPublicKey): Boolean = wasIssuedBy(OpenPgpFingerprint.of(key))
|
||||
|
||||
/** Return true, if this signature is a hard revocation. */
|
||||
val PGPSignature.isHardRevocation
|
||||
get() =
|
||||
when (SignatureType.requireFromCode(signatureType)) {
|
||||
SignatureType.KEY_REVOCATION,
|
||||
SignatureType.SUBKEY_REVOCATION,
|
||||
SignatureType.CERTIFICATION_REVOCATION -> {
|
||||
SignatureSubpacketsUtil.getRevocationReason(this)?.let {
|
||||
Reason.isHardRevocation(it.revocationReason)
|
||||
}
|
||||
?: true // no reason -> hard revocation
|
||||
}
|
||||
else -> false // Not a revocation
|
||||
}
|
||||
|
||||
fun PGPSignature?.toRevocationState() =
|
||||
if (this == null) RevocationState.notRevoked()
|
||||
else if (isHardRevocation) RevocationState.hardRevoked()
|
||||
else RevocationState.softRevoked(creationTime)
|
||||
if (this == null) RevocationState.notRevoked()
|
||||
else if (isHardRevocation) RevocationState.hardRevoked()
|
||||
else RevocationState.softRevoked(creationTime)
|
||||
|
||||
val PGPSignature.fingerprint: OpenPgpFingerprint?
|
||||
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
|
||||
get() = SignatureSubpacketsUtil.getIssuerFingerprintAsOpenPgpFingerprint(this)
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package org.pgpainless
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||
|
@ -19,8 +21,6 @@ import org.pgpainless.key.parsing.KeyRingReader
|
|||
import org.pgpainless.key.util.KeyRingUtils
|
||||
import org.pgpainless.policy.Policy
|
||||
import org.pgpainless.util.ArmorUtils
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
|
||||
class PGPainless private constructor() {
|
||||
|
||||
|
@ -28,25 +28,24 @@ class PGPainless private constructor() {
|
|||
|
||||
/**
|
||||
* Generate a fresh OpenPGP key ring from predefined templates.
|
||||
*
|
||||
* @return templates
|
||||
*/
|
||||
@JvmStatic
|
||||
fun generateKeyRing() = KeyRingTemplates()
|
||||
@JvmStatic fun generateKeyRing() = KeyRingTemplates()
|
||||
|
||||
/**
|
||||
* Build a custom OpenPGP key ring.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun buildKeyRing() = KeyRingBuilder()
|
||||
@JvmStatic fun buildKeyRing() = KeyRingBuilder()
|
||||
|
||||
/**
|
||||
* Read an existing OpenPGP key ring.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun readKeyRing() = KeyRingReader()
|
||||
@JvmStatic fun readKeyRing() = KeyRingReader()
|
||||
|
||||
/**
|
||||
* Extract a public key certificate from a secret key.
|
||||
|
@ -56,10 +55,11 @@ class PGPainless private constructor() {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun extractCertificate(secretKey: PGPSecretKeyRing) =
|
||||
KeyRingUtils.publicKeyRingFrom(secretKey)
|
||||
KeyRingUtils.publicKeyRingFrom(secretKey)
|
||||
|
||||
/**
|
||||
* Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key server) together.
|
||||
* Merge two copies of the same certificate (e.g. an old copy, and one retrieved from a key
|
||||
* server) together.
|
||||
*
|
||||
* @param originalCopy local, older copy of the cert
|
||||
* @param updatedCopy updated, newer copy of the cert
|
||||
|
@ -67,31 +67,27 @@ class PGPainless private constructor() {
|
|||
* @throws PGPException in case of an error
|
||||
*/
|
||||
@JvmStatic
|
||||
fun mergeCertificate(originalCopy: PGPPublicKeyRing,
|
||||
updatedCopy: PGPPublicKeyRing) =
|
||||
PGPPublicKeyRing.join(originalCopy, updatedCopy)
|
||||
fun mergeCertificate(originalCopy: PGPPublicKeyRing, updatedCopy: PGPPublicKeyRing) =
|
||||
PGPPublicKeyRing.join(originalCopy, updatedCopy)
|
||||
|
||||
/**
|
||||
* Wrap a key or certificate in ASCII armor.
|
||||
*
|
||||
* @param key key or certificate
|
||||
* @return ascii armored string
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asciiArmor(key: PGPKeyRing) =
|
||||
if (key is PGPSecretKeyRing)
|
||||
ArmorUtils.toAsciiArmoredString(key)
|
||||
else
|
||||
ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
|
||||
if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key)
|
||||
else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
|
||||
|
||||
/**
|
||||
* Wrap a key of certificate in ASCII armor and write the result into the given [OutputStream].
|
||||
* Wrap a key of certificate in ASCII armor and write the result into the given
|
||||
* [OutputStream].
|
||||
*
|
||||
* @param key key or certificate
|
||||
* @param outputStream output stream
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
|
@ -106,33 +102,34 @@ class PGPainless private constructor() {
|
|||
*
|
||||
* @param signature detached signature
|
||||
* @return ascii armored string
|
||||
*
|
||||
* @throws IOException in case of an error during the armoring process
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asciiArmor(signature: PGPSignature) = ArmorUtils.toAsciiArmoredString(signature)
|
||||
|
||||
/**
|
||||
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using OpenPGP.
|
||||
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using
|
||||
* OpenPGP.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encryptAndOrSign() = EncryptionBuilder()
|
||||
@JvmStatic fun encryptAndOrSign() = EncryptionBuilder()
|
||||
|
||||
/**
|
||||
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using OpenPGP.
|
||||
* Create a [DecryptionBuilder], which can be used to decrypt and/or verify data using
|
||||
* OpenPGP.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decryptAndOrVerify() = DecryptionBuilder()
|
||||
@JvmStatic fun decryptAndOrVerify() = DecryptionBuilder()
|
||||
|
||||
/**
|
||||
* Make changes to a secret key at the given reference time.
|
||||
* This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
|
||||
* Make changes to a secret key at the given reference time. This method can be used to
|
||||
* change key expiration dates and passphrases, or add/revoke user-ids and subkeys.
|
||||
*
|
||||
* <p>
|
||||
* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}.
|
||||
* After making the desired changes in the builder, the modified key can be extracted using
|
||||
* {@link SecretKeyRingEditorInterface#done()}.
|
||||
*
|
||||
* @param secretKeys secret key ring
|
||||
* @param referenceTime reference time used as signature creation date
|
||||
|
@ -141,11 +138,12 @@ class PGPainless private constructor() {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun modifyKeyRing(secretKey: PGPSecretKeyRing, referenceTime: Date = Date()) =
|
||||
SecretKeyRingEditor(secretKey, referenceTime)
|
||||
SecretKeyRingEditor(secretKey, referenceTime)
|
||||
|
||||
/**
|
||||
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] / [PGPSecretKeyRing].
|
||||
* This method can be used to determine expiration dates, key flags and other information about a key at a specific time.
|
||||
* Quickly access information about a [org.bouncycastle.openpgp.PGPPublicKeyRing] /
|
||||
* [PGPSecretKeyRing]. This method can be used to determine expiration dates, key flags and
|
||||
* other information about a key at a specific time.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param referenceTime date of inspection
|
||||
|
@ -154,22 +152,20 @@ class PGPainless private constructor() {
|
|||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
|
||||
KeyRingInfo(key, referenceTime)
|
||||
KeyRingInfo(key, referenceTime)
|
||||
|
||||
/**
|
||||
* Access, and make changes to PGPainless policy on acceptable/default algorithms etc.
|
||||
*
|
||||
* @return policy
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPolicy() = Policy.getInstance()
|
||||
@JvmStatic fun getPolicy() = Policy.getInstance()
|
||||
|
||||
/**
|
||||
* Create different kinds of signatures on other keys.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun certify() = CertifyCertificate()
|
||||
@JvmStatic fun certify() = CertifyCertificate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class AEADAlgorithm(
|
||||
val algorithmId: Int,
|
||||
val ivLength: Int,
|
||||
val tagLength: Int) {
|
||||
enum class AEADAlgorithm(val algorithmId: Int, val ivLength: Int, val tagLength: Int) {
|
||||
EAX(1, 16, 16),
|
||||
OCB(2, 15, 16),
|
||||
GCM(3, 12, 16),
|
||||
|
@ -16,15 +13,12 @@ enum class AEADAlgorithm(
|
|||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): AEADAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
algorithm -> algorithm.algorithmId == id
|
||||
}
|
||||
return values().firstOrNull { algorithm -> algorithm.algorithmId == id }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): AEADAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No AEADAlgorithm found for id $id")
|
||||
return fromId(id) ?: throw NoSuchElementException("No AEADAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
package org.pgpainless.algorithm
|
||||
|
||||
class AlgorithmSuite(
|
||||
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
|
||||
hashAlgorithms: List<HashAlgorithm>,
|
||||
compressionAlgorithms: List<CompressionAlgorithm>) {
|
||||
symmetricKeyAlgorithms: List<SymmetricKeyAlgorithm>,
|
||||
hashAlgorithms: List<HashAlgorithm>,
|
||||
compressionAlgorithms: List<CompressionAlgorithm>
|
||||
) {
|
||||
|
||||
val symmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm> = symmetricKeyAlgorithms.toSet()
|
||||
val hashAlgorithms: Set<HashAlgorithm> = hashAlgorithms.toSet()
|
||||
|
@ -16,30 +17,31 @@ class AlgorithmSuite(
|
|||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
val defaultSymmetricKeyAlgorithms = listOf(
|
||||
val defaultSymmetricKeyAlgorithms =
|
||||
listOf(
|
||||
SymmetricKeyAlgorithm.AES_256,
|
||||
SymmetricKeyAlgorithm.AES_192,
|
||||
SymmetricKeyAlgorithm.AES_128)
|
||||
|
||||
@JvmStatic
|
||||
val defaultHashAlgorithms = listOf(
|
||||
val defaultHashAlgorithms =
|
||||
listOf(
|
||||
HashAlgorithm.SHA512,
|
||||
HashAlgorithm.SHA384,
|
||||
HashAlgorithm.SHA256,
|
||||
HashAlgorithm.SHA224)
|
||||
|
||||
@JvmStatic
|
||||
val defaultCompressionAlgorithms = listOf(
|
||||
val defaultCompressionAlgorithms =
|
||||
listOf(
|
||||
CompressionAlgorithm.ZLIB,
|
||||
CompressionAlgorithm.BZIP2,
|
||||
CompressionAlgorithm.ZIP,
|
||||
CompressionAlgorithm.UNCOMPRESSED)
|
||||
|
||||
@JvmStatic
|
||||
val defaultAlgorithmSuite = AlgorithmSuite(
|
||||
defaultSymmetricKeyAlgorithms,
|
||||
defaultHashAlgorithms,
|
||||
defaultCompressionAlgorithms)
|
||||
val defaultAlgorithmSuite =
|
||||
AlgorithmSuite(
|
||||
defaultSymmetricKeyAlgorithms, defaultHashAlgorithms, defaultCompressionAlgorithms)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,17 @@
|
|||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
enum class CertificationType(
|
||||
val signatureType: SignatureType
|
||||
) {
|
||||
enum class CertificationType(val signatureType: SignatureType) {
|
||||
/**
|
||||
* The issuer of this certification does not make any particular assertion as to how well the certifier has
|
||||
* checked that the owner of the key is in fact the person described by the User ID.
|
||||
* The issuer of this certification does not make any particular assertion as to how well the
|
||||
* certifier has checked that the owner of the key is in fact the person described by the User
|
||||
* ID.
|
||||
*/
|
||||
GENERIC(SignatureType.GENERIC_CERTIFICATION),
|
||||
|
||||
/**
|
||||
* The issuer of this certification has not done any verification of the claim that the owner of this key is
|
||||
* the User ID specified.
|
||||
* The issuer of this certification has not done any verification of the claim that the owner of
|
||||
* this key is the User ID specified.
|
||||
*/
|
||||
NONE(SignatureType.NO_CERTIFICATION),
|
||||
|
||||
|
@ -31,4 +30,4 @@ enum class CertificationType(
|
|||
;
|
||||
|
||||
fun asSignatureType() = signatureType
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,22 +20,20 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
|
|||
companion object {
|
||||
|
||||
/**
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
||||
* If an invalid id is provided, null is returned.
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
|
||||
* an invalid id is provided, null is returned.
|
||||
*
|
||||
* @param id id
|
||||
* @return compression algorithm
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): CompressionAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
c -> c.algorithmId == id
|
||||
}
|
||||
return values().firstOrNull { c -> c.algorithmId == id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id.
|
||||
* If an invalid id is provided, throw an [NoSuchElementException].
|
||||
* Return the [CompressionAlgorithm] value that corresponds to the provided numerical id. If
|
||||
* an invalid id is provided, throw an [NoSuchElementException].
|
||||
*
|
||||
* @param id id
|
||||
* @return compression algorithm
|
||||
|
@ -43,8 +41,8 @@ enum class CompressionAlgorithm(val algorithmId: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): CompressionAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No CompressionAlgorithm found for id $id")
|
||||
return fromId(id)
|
||||
?: throw NoSuchElementException("No CompressionAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,11 @@ package org.pgpainless.algorithm
|
|||
|
||||
enum class DocumentSignatureType(val signatureType: SignatureType) {
|
||||
|
||||
/**
|
||||
* Signature is calculated over the unchanged binary data.
|
||||
*/
|
||||
/** Signature is calculated over the unchanged binary data. */
|
||||
BINARY_DOCUMENT(SignatureType.BINARY_DOCUMENT),
|
||||
|
||||
/**
|
||||
* The signature is calculated over the text data with its line endings
|
||||
* converted to `<CR><LF>`.
|
||||
* The signature is calculated over the text data with its line endings converted to `<CR><LF>`.
|
||||
*/
|
||||
CANONICAL_TEXT_DOCUMENT(SignatureType.CANONICAL_TEXT_DOCUMENT),
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,10 @@
|
|||
package org.pgpainless.algorithm
|
||||
|
||||
enum class EncryptionPurpose {
|
||||
/**
|
||||
* The stream will encrypt communication that goes over the wire.
|
||||
* E.g. EMail, Chat...
|
||||
*/
|
||||
/** The stream will encrypt communication that goes over the wire. E.g. EMail, Chat... */
|
||||
COMMUNICATIONS,
|
||||
/**
|
||||
* The stream will encrypt data at rest.
|
||||
* E.g. Encrypted backup...
|
||||
*/
|
||||
/** The stream will encrypt data at rest. E.g. Encrypted backup... */
|
||||
STORAGE,
|
||||
/**
|
||||
* The stream will use keys with either flags to encrypt the data.
|
||||
*/
|
||||
/** The stream will use keys with either flags to encrypt the data. */
|
||||
ANY
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,42 +12,44 @@ package org.pgpainless.algorithm
|
|||
enum class Feature(val featureId: Byte) {
|
||||
|
||||
/**
|
||||
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using Modification
|
||||
* Detection Code Packets.
|
||||
* Support for Symmetrically Encrypted Integrity Protected Data Packets (version 1) using
|
||||
* Modification Detection Code Packets.
|
||||
*
|
||||
* See [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
|
||||
* See
|
||||
* [RFC-4880 §5.14: Modification Detection Code Packet](https://tools.ietf.org/html/rfc4880#section-5.14)
|
||||
*/
|
||||
MODIFICATION_DETECTION(0x01),
|
||||
|
||||
/**
|
||||
* Support for Authenticated Encryption with Additional Data (AEAD).
|
||||
* If a key announces this feature, it signals support for consuming AEAD Encrypted Data Packets.
|
||||
* Support for Authenticated Encryption with Additional Data (AEAD). If a key announces this
|
||||
* feature, it signals support for consuming AEAD Encrypted Data Packets.
|
||||
*
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
||||
* NOTE: This value is currently RESERVED.
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
|
||||
*
|
||||
* See [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
|
||||
* See
|
||||
* [AEAD Encrypted Data Packet](https://openpgp-wg.gitlab.io/rfc4880bis/#name-aead-encrypted-data-packet-)
|
||||
*/
|
||||
GNUPG_AEAD_ENCRYPTED_DATA(0x02),
|
||||
|
||||
/**
|
||||
* If a key announces this feature, it is a version 5 public key.
|
||||
* The version 5 format is similar to the version 4 format except for the addition of a count for the key material.
|
||||
* This count helps to parse secret key packets (which are an extension of the public key packet format) in the case
|
||||
* of an unknown algorithm.
|
||||
* In addition, fingerprints of version 5 keys are calculated differently from version 4 keys.
|
||||
* If a key announces this feature, it is a version 5 public key. The version 5 format is
|
||||
* similar to the version 4 format except for the addition of a count for the key material. This
|
||||
* count helps to parse secret key packets (which are an extension of the public key packet
|
||||
* format) in the case of an unknown algorithm. In addition, fingerprints of version 5 keys are
|
||||
* calculated differently from version 4 keys.
|
||||
*
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!!
|
||||
* NOTE: This value is currently RESERVED.
|
||||
* NOTE: PGPAINLESS DOES NOT YET SUPPORT THIS FEATURE!!! NOTE: This value is currently RESERVED.
|
||||
*
|
||||
* See [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
|
||||
* See
|
||||
* [Public-Key Packet Formats](https://openpgp-wg.gitlab.io/rfc4880bis/#name-public-key-packet-formats)
|
||||
*/
|
||||
GNUPG_VERSION_5_PUBLIC_KEY(0x04),
|
||||
|
||||
/**
|
||||
* Support for Symmetrically Encrypted Integrity Protected Data packet version 2.
|
||||
*
|
||||
* See [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
|
||||
* See
|
||||
* [crypto-refresh-06 §5.13.2. Version 2 Sym. Encrypted Integrity Protected Data Packet Format](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-06.html#version-two-seipd)
|
||||
*/
|
||||
MODIFICATION_DETECTION_2(0x08),
|
||||
;
|
||||
|
@ -55,29 +57,26 @@ enum class Feature(val featureId: Byte) {
|
|||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Byte): Feature? {
|
||||
return values().firstOrNull {
|
||||
f -> f.featureId == id
|
||||
}
|
||||
return values().firstOrNull { f -> f.featureId == id }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Byte): Feature {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("Unknown feature id encountered: $id")
|
||||
return fromId(id) ?: throw NoSuchElementException("Unknown feature id encountered: $id")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fromBitmask(bitmask: Int): List<Feature> {
|
||||
return values().filter {
|
||||
it.featureId.toInt() and bitmask != 0
|
||||
}
|
||||
return values().filter { it.featureId.toInt() and bitmask != 0 }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toBitmask(vararg features: Feature): Byte {
|
||||
return features.map { it.featureId.toInt() }
|
||||
.reduceOrNull { mask, f -> mask or f }?.toByte()
|
||||
?: 0
|
||||
return features
|
||||
.map { it.featureId.toInt() }
|
||||
.reduceOrNull { mask, f -> mask or f }
|
||||
?.toByte()
|
||||
?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,36 +11,33 @@ package org.pgpainless.algorithm
|
|||
*/
|
||||
enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
||||
|
||||
@Deprecated("MD5 is deprecated")
|
||||
MD5 (1, "MD5"),
|
||||
SHA1 (2, "SHA1"),
|
||||
RIPEMD160 (3, "RIPEMD160"),
|
||||
SHA256 (8, "SHA256"),
|
||||
SHA384 (9, "SHA384"),
|
||||
SHA512 (10, "SHA512"),
|
||||
SHA224 (11, "SHA224"),
|
||||
SHA3_256 (12, "SHA3-256"),
|
||||
SHA3_512 (14, "SHA3-512"),
|
||||
@Deprecated("MD5 is deprecated") MD5(1, "MD5"),
|
||||
SHA1(2, "SHA1"),
|
||||
RIPEMD160(3, "RIPEMD160"),
|
||||
SHA256(8, "SHA256"),
|
||||
SHA384(9, "SHA384"),
|
||||
SHA512(10, "SHA512"),
|
||||
SHA224(11, "SHA224"),
|
||||
SHA3_256(12, "SHA3-256"),
|
||||
SHA3_512(14, "SHA3-512"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
||||
* If an invalid algorithm id was provided, null is returned.
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
|
||||
* invalid algorithm id was provided, null is returned.
|
||||
*
|
||||
* @param id numeric id
|
||||
* @return enum value
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): HashAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
h -> h.algorithmId == id
|
||||
}
|
||||
return values().firstOrNull { h -> h.algorithmId == id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id.
|
||||
* If an invalid algorithm id was provided, throw a [NoSuchElementException].
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided algorithm id. If an
|
||||
* invalid algorithm id was provided, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param id algorithm id
|
||||
* @return enum value
|
||||
|
@ -48,15 +45,15 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): HashAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No HashAlgorithm found for id $id")
|
||||
return fromId(id) ?: throw NoSuchElementException("No HashAlgorithm found for id $id")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided name.
|
||||
* If an invalid algorithm name was provided, null is returned.
|
||||
* Return the [HashAlgorithm] value that corresponds to the provided name. If an invalid
|
||||
* algorithm name was provided, null is returned.
|
||||
*
|
||||
* See [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
|
||||
* See
|
||||
* [RFC4880: §9.4 Hash Algorithms](https://datatracker.ietf.org/doc/html/rfc4880#section-9.4)
|
||||
* for a list of algorithms and names.
|
||||
*
|
||||
* @param name text name
|
||||
|
@ -65,12 +62,9 @@ enum class HashAlgorithm(val algorithmId: Int, val algorithmName: String) {
|
|||
@JvmStatic
|
||||
fun fromName(name: String): HashAlgorithm? {
|
||||
return name.uppercase().let { algoName ->
|
||||
values().firstOrNull {
|
||||
it.algorithmName == algoName
|
||||
} ?: values().firstOrNull {
|
||||
it.algorithmName == algoName.replace("-", "")
|
||||
}
|
||||
values().firstOrNull { it.algorithmName == algoName }
|
||||
?: values().firstOrNull { it.algorithmName == algoName.replace("-", "") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,40 +6,26 @@ package org.pgpainless.algorithm
|
|||
|
||||
enum class KeyFlag(val flag: Int) {
|
||||
|
||||
/**
|
||||
* This key may be used to certify third-party keys.
|
||||
*/
|
||||
CERTIFY_OTHER (1),
|
||||
/** This key may be used to certify third-party keys. */
|
||||
CERTIFY_OTHER(1),
|
||||
|
||||
/**
|
||||
* This key may be used to sign data.
|
||||
*/
|
||||
SIGN_DATA (2),
|
||||
/** This key may be used to sign data. */
|
||||
SIGN_DATA(2),
|
||||
|
||||
/**
|
||||
* This key may be used to encrypt communications.
|
||||
*/
|
||||
ENCRYPT_COMMS (4),
|
||||
/** This key may be used to encrypt communications. */
|
||||
ENCRYPT_COMMS(4),
|
||||
|
||||
/**
|
||||
* This key may be used to encrypt storage.
|
||||
*/
|
||||
/** This key may be used to encrypt storage. */
|
||||
ENCRYPT_STORAGE(8),
|
||||
|
||||
/**
|
||||
* The private component of this key may have been split by a secret-sharing mechanism.
|
||||
*/
|
||||
SPLIT (16),
|
||||
/** The private component of this key may have been split by a secret-sharing mechanism. */
|
||||
SPLIT(16),
|
||||
|
||||
/**
|
||||
* This key may be used for authentication.
|
||||
*/
|
||||
AUTHENTICATION (32),
|
||||
/** This key may be used for authentication. */
|
||||
AUTHENTICATION(32),
|
||||
|
||||
/**
|
||||
* The private component of this key may be in the possession of more than one person.
|
||||
*/
|
||||
SHARED (128),
|
||||
/** The private component of this key may be in the possession of more than one person. */
|
||||
SHARED(128),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
@ -52,9 +38,7 @@ enum class KeyFlag(val flag: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun fromBitmask(bitmask: Int): List<KeyFlag> {
|
||||
return values().filter {
|
||||
it.flag and bitmask != 0
|
||||
}
|
||||
return values().filter { it.flag and bitmask != 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,13 +49,12 @@ enum class KeyFlag(val flag: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun toBitmask(vararg flags: KeyFlag): Int {
|
||||
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f }
|
||||
?: 0
|
||||
return flags.map { it.flag }.reduceOrNull { mask, f -> mask or f } ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the provided bitmask has the bit for the provided flag set.
|
||||
* Return false if the mask does not contain the flag.
|
||||
* Return true if the provided bitmask has the bit for the provided flag set. Return false
|
||||
* if the mask does not contain the flag.
|
||||
*
|
||||
* @param mask bitmask
|
||||
* @param flag flag to be tested for
|
||||
|
@ -84,9 +67,7 @@ enum class KeyFlag(val flag: Int) {
|
|||
|
||||
@JvmStatic
|
||||
fun containsAny(mask: Int, vararg flags: KeyFlag): Boolean {
|
||||
return flags.any {
|
||||
hasKeyFlag(mask, it)
|
||||
}
|
||||
return flags.any { hasKeyFlag(mask, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ enum class OpenPgpPacket(val tag: Int) {
|
|||
UATTR(17),
|
||||
SEIPD(18),
|
||||
MDC(19),
|
||||
|
||||
EXP_1(60),
|
||||
EXP_2(61),
|
||||
EXP_3(62),
|
||||
|
@ -32,15 +31,13 @@ enum class OpenPgpPacket(val tag: Int) {
|
|||
companion object {
|
||||
@JvmStatic
|
||||
fun fromTag(tag: Int): OpenPgpPacket? {
|
||||
return values().firstOrNull {
|
||||
it.tag == tag
|
||||
}
|
||||
return values().firstOrNull { it.tag == tag }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromTag(tag: Int): OpenPgpPacket {
|
||||
return fromTag(tag) ?:
|
||||
throw NoSuchElementException("No OpenPGP packet known for tag $tag")
|
||||
return fromTag(tag)
|
||||
?: throw NoSuchElementException("No OpenPGP packet known for tag $tag")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,87 +10,73 @@ package org.pgpainless.algorithm
|
|||
* See [RFC4880: Public-Key Algorithms](https://tools.ietf.org/html/rfc4880#section-9.1)
|
||||
*/
|
||||
enum class PublicKeyAlgorithm(
|
||||
val algorithmId: Int,
|
||||
val signingCapable: Boolean,
|
||||
val encryptionCapable: Boolean) {
|
||||
val algorithmId: Int,
|
||||
val signingCapable: Boolean,
|
||||
val encryptionCapable: Boolean
|
||||
) {
|
||||
|
||||
/**
|
||||
* RSA capable of encryption and signatures.
|
||||
*/
|
||||
RSA_GENERAL (1, true, true),
|
||||
/** RSA capable of encryption and signatures. */
|
||||
RSA_GENERAL(1, true, true),
|
||||
|
||||
/**
|
||||
* RSA with usage encryption.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
|
||||
* notice</a>
|
||||
*/
|
||||
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL",
|
||||
ReplaceWith("RSA_GENERAL"))
|
||||
RSA_ENCRYPT (2, false, true),
|
||||
@Deprecated("RSA_ENCRYPT is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
|
||||
RSA_ENCRYPT(2, false, true),
|
||||
|
||||
/**
|
||||
* RSA with usage of creating signatures.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation notice</a>
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.5">Deprecation
|
||||
* notice</a>
|
||||
*/
|
||||
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL",
|
||||
ReplaceWith("RSA_GENERAL"))
|
||||
RSA_SIGN (3, true, false),
|
||||
@Deprecated("RSA_SIGN is deprecated in favor of RSA_GENERAL", ReplaceWith("RSA_GENERAL"))
|
||||
RSA_SIGN(3, true, false),
|
||||
|
||||
/**
|
||||
* ElGamal with usage encryption.
|
||||
*/
|
||||
ELGAMAL_ENCRYPT (16, false, true),
|
||||
/** ElGamal with usage encryption. */
|
||||
ELGAMAL_ENCRYPT(16, false, true),
|
||||
|
||||
/**
|
||||
* Digital Signature Algorithm.
|
||||
*/
|
||||
DSA (17, true, false),
|
||||
/** Digital Signature Algorithm. */
|
||||
DSA(17, true, false),
|
||||
|
||||
/**
|
||||
* Elliptic Curve Diffie-Hellman.
|
||||
*/
|
||||
ECDH (18, false, true),
|
||||
/** Elliptic Curve Diffie-Hellman. */
|
||||
ECDH(18, false, true),
|
||||
|
||||
/**
|
||||
* Elliptic Curve Digital Signature Algorithm.
|
||||
*/
|
||||
ECDSA (19, true, false),
|
||||
/** Elliptic Curve Digital Signature Algorithm. */
|
||||
ECDSA(19, true, false),
|
||||
|
||||
/**
|
||||
* ElGamal General.
|
||||
*
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation notice</a>
|
||||
* @deprecated see <a href="https://tools.ietf.org/html/rfc4880#section-13.8">Deprecation
|
||||
* notice</a>
|
||||
*/
|
||||
@Deprecated("ElGamal is deprecated")
|
||||
ELGAMAL_GENERAL (20, true, true),
|
||||
@Deprecated("ElGamal is deprecated") ELGAMAL_GENERAL(20, true, true),
|
||||
|
||||
/**
|
||||
* Diffie-Hellman key exchange algorithm.
|
||||
*/
|
||||
DIFFIE_HELLMAN (21, false, true),
|
||||
/** Diffie-Hellman key exchange algorithm. */
|
||||
DIFFIE_HELLMAN(21, false, true),
|
||||
|
||||
/**
|
||||
* Digital Signature Algorithm based on twisted Edwards Curves.
|
||||
*/
|
||||
EDDSA (22, true, false),
|
||||
/** Digital Signature Algorithm based on twisted Edwards Curves. */
|
||||
EDDSA(22, true, false),
|
||||
;
|
||||
|
||||
fun isSigningCapable(): Boolean = signingCapable
|
||||
|
||||
fun isEncryptionCapable(): Boolean = encryptionCapable
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): PublicKeyAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
it.algorithmId == id
|
||||
}
|
||||
return values().firstOrNull { it.algorithmId == id }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): PublicKeyAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
|
||||
return fromId(id)
|
||||
?: throw NoSuchElementException("No PublicKeyAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,53 +4,48 @@
|
|||
|
||||
package org.pgpainless.algorithm
|
||||
|
||||
import org.pgpainless.util.DateUtil
|
||||
import java.lang.AssertionError
|
||||
import java.util.*
|
||||
import kotlin.NoSuchElementException
|
||||
import org.pgpainless.util.DateUtil
|
||||
|
||||
class RevocationState private constructor(
|
||||
val type: RevocationStateType,
|
||||
private val _date: Date?): Comparable<RevocationState> {
|
||||
class RevocationState private constructor(val type: RevocationStateType, private val _date: Date?) :
|
||||
Comparable<RevocationState> {
|
||||
|
||||
val date: Date
|
||||
get() {
|
||||
if (!isSoftRevocation()) {
|
||||
throw NoSuchElementException("RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
|
||||
throw NoSuchElementException(
|
||||
"RevocationStateType is not equal to 'softRevoked'. Cannot extract date.")
|
||||
}
|
||||
return _date!!
|
||||
}
|
||||
|
||||
private constructor(type: RevocationStateType): this(type, null)
|
||||
private constructor(type: RevocationStateType) : this(type, null)
|
||||
|
||||
fun isSoftRevocation() = type == RevocationStateType.softRevoked
|
||||
|
||||
fun isHardRevocation() = type == RevocationStateType.hardRevoked
|
||||
|
||||
fun isNotRevoked() = type == RevocationStateType.notRevoked
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
|
||||
@JvmStatic fun notRevoked() = RevocationState(RevocationStateType.notRevoked)
|
||||
|
||||
@JvmStatic
|
||||
fun softRevoked(date: Date) = RevocationState(RevocationStateType.softRevoked, date)
|
||||
|
||||
@JvmStatic
|
||||
fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
|
||||
@JvmStatic fun hardRevoked() = RevocationState(RevocationStateType.hardRevoked)
|
||||
}
|
||||
|
||||
override fun compareTo(other: RevocationState): Int {
|
||||
return when(type) {
|
||||
RevocationStateType.notRevoked ->
|
||||
if (other.isNotRevoked()) 0
|
||||
else -1
|
||||
return when (type) {
|
||||
RevocationStateType.notRevoked -> if (other.isNotRevoked()) 0 else -1
|
||||
RevocationStateType.softRevoked ->
|
||||
if (other.isNotRevoked()) 1
|
||||
// Compare soft dates in reverse
|
||||
else if (other.isSoftRevocation()) other.date.compareTo(date)
|
||||
else -1
|
||||
RevocationStateType.hardRevoked ->
|
||||
if (other.isHardRevocation()) 0
|
||||
else 1
|
||||
else if (other.isSoftRevocation()) other.date.compareTo(date) else -1
|
||||
RevocationStateType.hardRevoked -> if (other.isHardRevocation()) 0 else 1
|
||||
else -> throw AssertionError("Unknown type: $type")
|
||||
}
|
||||
}
|
||||
|
@ -80,8 +75,9 @@ class RevocationState private constructor(
|
|||
return false
|
||||
}
|
||||
if (isSoftRevocation()) {
|
||||
return DateUtil.toSecondsPrecision(date).time == DateUtil.toSecondsPrecision(other.date).time
|
||||
return DateUtil.toSecondsPrecision(date).time ==
|
||||
DateUtil.toSecondsPrecision(other.date).time
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,12 @@
|
|||
package org.pgpainless.algorithm
|
||||
|
||||
enum class RevocationStateType {
|
||||
/**
|
||||
* Certificate is not revoked.
|
||||
*/
|
||||
/** Certificate is not revoked. */
|
||||
notRevoked,
|
||||
|
||||
/**
|
||||
* Certificate is revoked with a soft revocation.
|
||||
*/
|
||||
/** Certificate is revoked with a soft revocation. */
|
||||
softRevoked,
|
||||
|
||||
/**
|
||||
* Certificate is revoked with a hard revocation.
|
||||
*/
|
||||
/** Certificate is revoked with a hard revocation. */
|
||||
hardRevoked
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,24 @@ package org.pgpainless.algorithm
|
|||
import org.bouncycastle.bcpg.SignatureSubpacketTags.*
|
||||
|
||||
/**
|
||||
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an OpenPGP signature.
|
||||
* Enumeration of possible subpackets that might be found in the hashed and unhashed area of an
|
||||
* OpenPGP signature.
|
||||
*
|
||||
* See [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
|
||||
* See
|
||||
* [RFC4880: Signature Subpacket Specification](https://tools.ietf.org/html/rfc4880#section-5.2.3.1)
|
||||
*/
|
||||
enum class SignatureSubpacket(val code: Int) {
|
||||
/**
|
||||
* The time the signature was made.
|
||||
* MUST be present in the hashed area of the signature.
|
||||
* The time the signature was made. MUST be present in the hashed area of the signature.
|
||||
*
|
||||
* See [Signature Creation Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.4)
|
||||
*/
|
||||
signatureCreationTime(2),
|
||||
|
||||
/**
|
||||
* The validity period of the signature. This is the number of seconds
|
||||
* after the signature creation time that the signature expires. If
|
||||
* this is not present or has a value of zero, it never expires.
|
||||
* The validity period of the signature. This is the number of seconds after the signature
|
||||
* creation time that the signature expires. If this is not present or has a value of zero, it
|
||||
* never expires.
|
||||
*
|
||||
* See [Signature Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.10)
|
||||
*/
|
||||
|
@ -37,90 +38,76 @@ enum class SignatureSubpacket(val code: Int) {
|
|||
exportableCertification(4),
|
||||
|
||||
/**
|
||||
* Signer asserts that the key is not only valid but also trustworthy at
|
||||
* the specified level. Level 0 has the same meaning as an ordinary
|
||||
* validity signature. Level 1 means that the signed key is asserted to
|
||||
* be a valid, trusted introducer, with the 2nd octet of the body
|
||||
* specifying the degree of trust. Level 2 means that the signed key is
|
||||
* asserted to be trusted to issue level 1 trust signatures, i.e., that
|
||||
* it is a "meta introducer". Generally, a level n trust signature
|
||||
* asserts that a key is trusted to issue level n-1 trust signatures.
|
||||
* The trust amount is in a range from 0-255, interpreted such that
|
||||
* values less than 120 indicate partial trust and values of 120 or
|
||||
* greater indicate complete trust. Implementations SHOULD emit values
|
||||
* of 60 for partial trust and 120 for complete trust.
|
||||
* Signer asserts that the key is not only valid but also trustworthy at the specified level.
|
||||
* Level 0 has the same meaning as an ordinary validity signature. Level 1 means that the signed
|
||||
* key is asserted to be a valid, trusted introducer, with the 2nd octet of the body specifying
|
||||
* the degree of trust. Level 2 means that the signed key is asserted to be trusted to issue
|
||||
* level 1 trust signatures, i.e., that it is a "meta introducer". Generally, a level n trust
|
||||
* signature asserts that a key is trusted to issue level n-1 trust signatures. The trust amount
|
||||
* is in a range from 0-255, interpreted such that values less than 120 indicate partial trust
|
||||
* and values of 120 or greater indicate complete trust. Implementations SHOULD emit values of
|
||||
* 60 for partial trust and 120 for complete trust.
|
||||
*
|
||||
* See [Trust Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.13)
|
||||
*/
|
||||
trustSignature(5),
|
||||
|
||||
/**
|
||||
* Used in conjunction with trust Signature packets (of level greater 0) to
|
||||
* limit the scope of trust that is extended. Only signatures by the
|
||||
* target key on User IDs that match the regular expression in the body
|
||||
* of this packet have trust extended by the trust Signature subpacket.
|
||||
* The regular expression uses the same syntax as the Henry Spencer's
|
||||
* "almost public domain" regular expression [REGEX] package. A
|
||||
* description of the syntax is found in Section 8 below.
|
||||
* Used in conjunction with trust Signature packets (of level greater 0) to limit the scope of
|
||||
* trust that is extended. Only signatures by the target key on User IDs that match the regular
|
||||
* expression in the body of this packet have trust extended by the trust Signature subpacket.
|
||||
* The regular expression uses the same syntax as the Henry Spencer's "almost public domain"
|
||||
* regular expression [REGEX] package. A description of the syntax is found in Section 8 below.
|
||||
*
|
||||
* See [Regular Expression](https://tools.ietf.org/html/rfc4880#section-5.2.3.14)
|
||||
*/
|
||||
regularExpression(6),
|
||||
|
||||
/**
|
||||
* Signature's revocability status. The packet body contains a Boolean
|
||||
* flag indicating whether the signature is revocable. Signatures that
|
||||
* are not revocable have any later revocation signatures ignored. They
|
||||
* represent a commitment by the signer that he cannot revoke his
|
||||
* signature for the life of his key. If this packet is not present,
|
||||
* the signature is revocable.
|
||||
* Signature's revocability status. The packet body contains a Boolean flag indicating whether
|
||||
* the signature is revocable. Signatures that are not revocable have any later revocation
|
||||
* signatures ignored. They represent a commitment by the signer that he cannot revoke his
|
||||
* signature for the life of his key. If this packet is not present, the signature is revocable.
|
||||
*
|
||||
* See [Revocable](https://tools.ietf.org/html/rfc4880#section-5.2.3.12)
|
||||
*/
|
||||
revocable(7),
|
||||
|
||||
/**
|
||||
* The validity period of the key. This is the number of seconds after
|
||||
* the key creation time that the key expires. If this is not present
|
||||
* or has a value of zero, the key never expires. This is found only on
|
||||
* a self-signature.
|
||||
* The validity period of the key. This is the number of seconds after the key creation time
|
||||
* that the key expires. If this is not present or has a value of zero, the key never expires.
|
||||
* This is found only on a self-signature.
|
||||
*
|
||||
* See [Key Expiration Time](https://tools.ietf.org/html/rfc4880#section-5.2.3.6)
|
||||
*/
|
||||
keyExpirationTime(9),
|
||||
|
||||
/**
|
||||
* Placeholder for backwards compatibility.
|
||||
*/
|
||||
/** Placeholder for backwards compatibility. */
|
||||
placeholder(10),
|
||||
|
||||
/**
|
||||
* Symmetric algorithm numbers that indicate which algorithms the keyholder
|
||||
* prefers to use. The subpackets body is an ordered list of
|
||||
* octets with the most preferred listed first. It is assumed that only
|
||||
* algorithms listed are supported by the recipient's software.
|
||||
* This is only found on a self-signature.
|
||||
* Symmetric algorithm numbers that indicate which algorithms the keyholder prefers to use. The
|
||||
* subpackets body is an ordered list of octets with the most preferred listed first. It is
|
||||
* assumed that only algorithms listed are supported by the recipient's software. This is only
|
||||
* found on a self-signature.
|
||||
*
|
||||
* See [Preferred Symmetric Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.7)
|
||||
*/
|
||||
preferredSymmetricAlgorithms(11),
|
||||
|
||||
/**
|
||||
* Authorizes the specified key to issue revocation signatures for this
|
||||
* key. Class octet must have bit 0x80 set. If the bit 0x40 is set,
|
||||
* then this means that the revocation information is sensitive. Other
|
||||
* bits are for future expansion to other kinds of authorizations. This
|
||||
* is found on a self-signature.
|
||||
* Authorizes the specified key to issue revocation signatures for this key. Class octet must
|
||||
* have bit 0x80 set. If the bit 0x40 is set, then this means that the revocation information is
|
||||
* sensitive. Other bits are for future expansion to other kinds of authorizations. This is
|
||||
* found on a self-signature.
|
||||
*
|
||||
* If the "sensitive" flag is set, the keyholder feels this subpacket
|
||||
* contains private trust information that describes a real-world
|
||||
* sensitive relationship. If this flag is set, implementations SHOULD
|
||||
* NOT export this signature to other users except in cases where the
|
||||
* data needs to be available: when the signature is being sent to the
|
||||
* designated revoker, or when it is accompanied by a revocation
|
||||
* signature from that revoker. Note that it may be appropriate to
|
||||
* isolate this subpacket within a separate signature so that it is not
|
||||
* combined with other subpackets that need to be exported.
|
||||
* If the "sensitive" flag is set, the keyholder feels this subpacket contains private trust
|
||||
* information that describes a real-world sensitive relationship. If this flag is set,
|
||||
* implementations SHOULD NOT export this signature to other users except in cases where the
|
||||
* data needs to be available: when the signature is being sent to the designated revoker, or
|
||||
* when it is accompanied by a revocation signature from that revoker. Note that it may be
|
||||
* appropriate to isolate this subpacket within a separate signature so that it is not combined
|
||||
* with other subpackets that need to be exported.
|
||||
*
|
||||
* See [Revocation Key](https://tools.ietf.org/html/rfc4880#section-5.2.3.15)
|
||||
*/
|
||||
|
@ -134,54 +121,48 @@ enum class SignatureSubpacket(val code: Int) {
|
|||
issuerKeyId(16),
|
||||
|
||||
/**
|
||||
* This subpacket describes a "notation" on the signature that the
|
||||
* issuer wishes to make. The notation has a name and a value, each of
|
||||
* which are strings of octets. There may be more than one notation in
|
||||
* a signature. Notations can be used for any extension the issuer of
|
||||
* the signature cares to make. The "flags" field holds four octets of
|
||||
* flags.
|
||||
* This subpacket describes a "notation" on the signature that the issuer wishes to make. The
|
||||
* notation has a name and a value, each of which are strings of octets. There may be more than
|
||||
* one notation in a signature. Notations can be used for any extension the issuer of the
|
||||
* signature cares to make. The "flags" field holds four octets of flags.
|
||||
*
|
||||
* See [Notation Data](https://tools.ietf.org/html/rfc4880#section-5.2.3.16)
|
||||
*/
|
||||
notationData(20),
|
||||
|
||||
/**
|
||||
* Message digest algorithm numbers that indicate which algorithms the
|
||||
* keyholder prefers to receive. Like the preferred symmetric
|
||||
* algorithms, the list is ordered.
|
||||
* This is only found on a self-signature.
|
||||
* Message digest algorithm numbers that indicate which algorithms the keyholder prefers to
|
||||
* receive. Like the preferred symmetric algorithms, the list is ordered. This is only found on
|
||||
* a self-signature.
|
||||
*
|
||||
* See [Preferred Hash Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.8)
|
||||
*/
|
||||
preferredHashAlgorithms(21),
|
||||
|
||||
/**
|
||||
* Compression algorithm numbers that indicate which algorithms the
|
||||
* keyholder prefers to use. Like the preferred symmetric algorithms, the
|
||||
* list is ordered. If this subpacket is not included, ZIP is preferred.
|
||||
* A zero denotes that uncompressed data is preferred; the keyholder's
|
||||
* software might have no compression software in that implementation.
|
||||
* This is only found on a self-signature.
|
||||
* Compression algorithm numbers that indicate which algorithms the keyholder prefers to use.
|
||||
* Like the preferred symmetric algorithms, the list is ordered. If this subpacket is not
|
||||
* included, ZIP is preferred. A zero denotes that uncompressed data is preferred; the
|
||||
* keyholder's software might have no compression software in that implementation. This is only
|
||||
* found on a self-signature.
|
||||
*
|
||||
* See [Preferred Compressio Algorithms](https://tools.ietf.org/html/rfc4880#section-5.2.3.9)
|
||||
*/
|
||||
preferredCompressionAlgorithms(22),
|
||||
|
||||
/**
|
||||
* This is a list of one-bit flags that indicate preferences that the
|
||||
* keyholder has about how the key is handled on a key server. All
|
||||
* undefined flags MUST be zero.
|
||||
* This is found only on a self-signature.
|
||||
* This is a list of one-bit flags that indicate preferences that the keyholder has about how
|
||||
* the key is handled on a key server. All undefined flags MUST be zero. This is found only on a
|
||||
* self-signature.
|
||||
*
|
||||
* See [Key Server Preferences](https://tools.ietf.org/html/rfc4880#section-5.2.3.17)
|
||||
*/
|
||||
keyServerPreferences(23),
|
||||
|
||||
/**
|
||||
* This is a URI of a key server that the keyholder prefers be used for
|
||||
* updates. Note that keys with multiple User IDs can have a preferred
|
||||
* key server for each User ID. Note also that since this is a URI, the
|
||||
* key server can actually be a copy of the key retrieved by ftp, http,
|
||||
* This is a URI of a key server that the keyholder prefers be used for updates. Note that keys
|
||||
* with multiple User IDs can have a preferred key server for each User ID. Note also that since
|
||||
* this is a URI, the key server can actually be a copy of the key retrieved by ftp, http,
|
||||
* finger, etc.
|
||||
*
|
||||
* See [Preferred Key Server](https://tools.ietf.org/html/rfc4880#section-5.2.3.18)
|
||||
|
@ -189,63 +170,55 @@ enum class SignatureSubpacket(val code: Int) {
|
|||
preferredKeyServers(24),
|
||||
|
||||
/**
|
||||
* This is a flag in a User ID's self-signature that states whether this
|
||||
* User ID is the main User ID for this key. It is reasonable for an
|
||||
* implementation to resolve ambiguities in preferences, etc. by
|
||||
* referring to the primary User ID. If this flag is absent, its value
|
||||
* is zero. If more than one User ID in a key is marked as primary, the
|
||||
* implementation may resolve the ambiguity in any way it sees fit, but
|
||||
* it is RECOMMENDED that priority be given to the User ID with the most
|
||||
* recent self-signature.
|
||||
* This is a flag in a User ID's self-signature that states whether this User ID is the main
|
||||
* User ID for this key. It is reasonable for an implementation to resolve ambiguities in
|
||||
* preferences, etc. by referring to the primary User ID. If this flag is absent, its value is
|
||||
* zero. If more than one User ID in a key is marked as primary, the implementation may resolve
|
||||
* the ambiguity in any way it sees fit, but it is RECOMMENDED that priority be given to the
|
||||
* User ID with the most recent self-signature.
|
||||
*
|
||||
* When appearing on a self-signature on a User ID packet, this
|
||||
* subpacket applies only to User ID packets. When appearing on a
|
||||
* self-signature on a User Attribute packet, this subpacket applies
|
||||
* only to User Attribute packets. That is to say, there are two
|
||||
* different and independent "primaries" -- one for User IDs, and one
|
||||
* for User Attributes.
|
||||
* When appearing on a self-signature on a User ID packet, this subpacket applies only to User
|
||||
* ID packets. When appearing on a self-signature on a User Attribute packet, this subpacket
|
||||
* applies only to User Attribute packets. That is to say, there are two different and
|
||||
* independent "primaries" -- one for User IDs, and one for User Attributes.
|
||||
*
|
||||
* See [Primary User-ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.19)
|
||||
*/
|
||||
primaryUserId(25),
|
||||
|
||||
/**
|
||||
* This subpacket contains a URI of a document that describes the policy
|
||||
* under which the signature was issued.
|
||||
* This subpacket contains a URI of a document that describes the policy under which the
|
||||
* signature was issued.
|
||||
*
|
||||
* See [Policy URL](https://tools.ietf.org/html/rfc4880#section-5.2.3.20)
|
||||
*/
|
||||
policyUrl(26),
|
||||
|
||||
/**
|
||||
* This subpacket contains a list of binary flags that hold information
|
||||
* about a key. It is a string of octets, and an implementation MUST
|
||||
* NOT assume a fixed size. This is so it can grow over time. If a
|
||||
* list is shorter than an implementation expects, the unstated flags
|
||||
* are considered to be zero.
|
||||
* This subpacket contains a list of binary flags that hold information about a key. It is a
|
||||
* string of octets, and an implementation MUST NOT assume a fixed size. This is so it can grow
|
||||
* over time. If a list is shorter than an implementation expects, the unstated flags are
|
||||
* considered to be zero.
|
||||
*
|
||||
* See [Key Flags](https://tools.ietf.org/html/rfc4880#section-5.2.3.21)
|
||||
*/
|
||||
keyFlags(27),
|
||||
|
||||
/**
|
||||
* This subpacket allows a keyholder to state which User ID is
|
||||
* responsible for the signing. Many keyholders use a single key for
|
||||
* different purposes, such as business communications as well as
|
||||
* personal communications. This subpacket allows such a keyholder to
|
||||
* state which of their roles is making a signature.
|
||||
* This subpacket allows a keyholder to state which User ID is responsible for the signing. Many
|
||||
* keyholders use a single key for different purposes, such as business communications as well
|
||||
* as personal communications. This subpacket allows such a keyholder to state which of their
|
||||
* roles is making a signature.
|
||||
*
|
||||
* See [Signer's User ID](https://tools.ietf.org/html/rfc4880#section-5.2.3.22)
|
||||
*/
|
||||
signerUserId(28),
|
||||
|
||||
/**
|
||||
* This subpacket is used only in key revocation and certification
|
||||
* revocation signatures. It describes the reason why the key or
|
||||
* certificate was revoked.
|
||||
* This subpacket is used only in key revocation and certification revocation signatures. It
|
||||
* describes the reason why the key or certificate was revoked.
|
||||
*
|
||||
* The first octet contains a machine-readable code that denotes the
|
||||
* reason for the revocation:
|
||||
* The first octet contains a machine-readable code that denotes the reason for the revocation:
|
||||
*
|
||||
* 0 - No reason specified (key revocations or cert revocations)
|
||||
* 1 - Key is superseded (key revocations)
|
||||
|
@ -259,117 +232,105 @@ enum class SignatureSubpacket(val code: Int) {
|
|||
revocationReason(29),
|
||||
|
||||
/**
|
||||
* The Features subpacket denotes which advanced OpenPGP features a
|
||||
* user's implementation supports. This is so that as features are
|
||||
* added to OpenPGP that cannot be backwards-compatible, a user can
|
||||
* state that they can use that feature. The flags are single bits that
|
||||
* indicate that a given feature is supported.
|
||||
* The Features subpacket denotes which advanced OpenPGP features a user's implementation
|
||||
* supports. This is so that as features are added to OpenPGP that cannot be
|
||||
* backwards-compatible, a user can state that they can use that feature. The flags are single
|
||||
* bits that indicate that a given feature is supported.
|
||||
*
|
||||
* This subpacket is similar to a preferences subpacket, and only
|
||||
* appears in a self-signature.
|
||||
* This subpacket is similar to a preferences subpacket, and only appears in a self-signature.
|
||||
*
|
||||
* See [Features](https://tools.ietf.org/html/rfc4880#section-5.2.3.24)
|
||||
*/
|
||||
features(30),
|
||||
|
||||
/**
|
||||
* This subpacket identifies a specific target signature to which a
|
||||
* signature refers. For revocation signatures, this subpacket
|
||||
* provides explicit designation of which signature is being revoked.
|
||||
* For a third-party or timestamp signature, this designates what
|
||||
* signature is signed. All arguments are an identifier of that target
|
||||
* signature.
|
||||
* This subpacket identifies a specific target signature to which a signature refers. For
|
||||
* revocation signatures, this subpacket provides explicit designation of which signature is
|
||||
* being revoked. For a third-party or timestamp signature, this designates what signature is
|
||||
* signed. All arguments are an identifier of that target signature.
|
||||
*
|
||||
* The N octets of hash data MUST be the size of the hash of the
|
||||
* signature. For example, a target signature with a SHA-1 hash MUST
|
||||
* have 20 octets of hash data.
|
||||
* The N octets of hash data MUST be the size of the hash of the signature. For example, a
|
||||
* target signature with a SHA-1 hash MUST have 20 octets of hash data.
|
||||
*
|
||||
* See [Signature Target](https://tools.ietf.org/html/rfc4880#section-5.2.3.25)
|
||||
*/
|
||||
signatureTarget(31),
|
||||
|
||||
/**
|
||||
* This subpacket contains a complete Signature packet body as
|
||||
* specified in Section 5.2 above. It is useful when one signature
|
||||
* needs to refer to, or be incorporated in, another signature.
|
||||
* This subpacket contains a complete Signature packet body as specified in Section 5.2 above.
|
||||
* It is useful when one signature needs to refer to, or be incorporated in, another signature.
|
||||
*
|
||||
* See [Embedded Signature](https://tools.ietf.org/html/rfc4880#section-5.2.3.26)
|
||||
*/
|
||||
embeddedSignature(32),
|
||||
|
||||
/**
|
||||
* The OpenPGP Key fingerprint of the key issuing the signature. This
|
||||
* subpacket SHOULD be included in all signatures. If the version of
|
||||
* the issuing key is 4 and an Issuer subpacket is also included in the
|
||||
* signature, the key ID of the Issuer subpacket MUST match the low 64
|
||||
* bits of the fingerprint.
|
||||
* The OpenPGP Key fingerprint of the key issuing the signature. This subpacket SHOULD be
|
||||
* included in all signatures. If the version of the issuing key is 4 and an Issuer subpacket is
|
||||
* also included in the signature, the key ID of the Issuer subpacket MUST match the low 64 bits
|
||||
* of the fingerprint.
|
||||
*
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
||||
* octets; for a version 5 key N is 32.
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
|
||||
* key N is 32.
|
||||
*
|
||||
* See [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
|
||||
* See
|
||||
* [Issuer Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.28)
|
||||
*/
|
||||
issuerFingerprint(33),
|
||||
|
||||
/**
|
||||
* AEAD algorithm numbers that indicate which AEAD algorithms the
|
||||
* keyholder prefers to use. The subpackets body is an ordered list of
|
||||
* octets with the most preferred listed first. It is assumed that only
|
||||
* algorithms listed are supported by the recipient's software.
|
||||
* This is only found on a self-signature.
|
||||
* Note that support for the AEAD Encrypted Data packet in the general
|
||||
* is indicated by a Feature Flag.
|
||||
* AEAD algorithm numbers that indicate which AEAD algorithms the keyholder prefers to use. The
|
||||
* subpackets body is an ordered list of octets with the most preferred listed first. It is
|
||||
* assumed that only algorithms listed are supported by the recipient's software. This is only
|
||||
* found on a self-signature. Note that support for the AEAD Encrypted Data packet in the
|
||||
* general is indicated by a Feature Flag.
|
||||
*
|
||||
* See [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
|
||||
* See
|
||||
* [Preferred AEAD Algorithms](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.8)
|
||||
*/
|
||||
preferredAEADAlgorithms(39),
|
||||
|
||||
/**
|
||||
* The OpenPGP Key fingerprint of the intended recipient primary key.
|
||||
* If one or more subpackets of this type are included in a signature,
|
||||
* it SHOULD be considered valid only in an encrypted context, where the
|
||||
* key it was encrypted to is one of the indicated primary keys, or one
|
||||
* of their subkeys. This can be used to prevent forwarding a signature
|
||||
* outside its intended, encrypted context.
|
||||
* The OpenPGP Key fingerprint of the intended recipient primary key. If one or more subpackets
|
||||
* of this type are included in a signature, it SHOULD be considered valid only in an encrypted
|
||||
* context, where the key it was encrypted to is one of the indicated primary keys, or one of
|
||||
* their subkeys. This can be used to prevent forwarding a signature outside its intended,
|
||||
* encrypted context.
|
||||
*
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20
|
||||
* octets; for a version 5 key N is 32.
|
||||
* Note that the length N of the fingerprint for a version 4 key is 20 octets; for a version 5
|
||||
* key N is 32.
|
||||
*
|
||||
* See [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
|
||||
* See
|
||||
* [Intended Recipient Fingerprint](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.29)
|
||||
*/
|
||||
intendedRecipientFingerprint(35),
|
||||
|
||||
/**
|
||||
* This subpacket MUST only appear as a hashed subpacket of an
|
||||
* Attestation Key Signature. It has no meaning in any other signature
|
||||
* type. It is used by the primary key to attest to a set of third-
|
||||
* party certifications over the associated User ID or User Attribute.
|
||||
* This enables the holder of an OpenPGP primary key to mark specific
|
||||
* third-party certifications as re-distributable with the rest of the
|
||||
* Transferable Public Key (see the "No-modify" flag in "Key Server
|
||||
* Preferences", above). Implementations MUST include exactly one
|
||||
* Attested Certification subpacket in any generated Attestation Key
|
||||
* Signature.
|
||||
* This subpacket MUST only appear as a hashed subpacket of an Attestation Key Signature. It has
|
||||
* no meaning in any other signature type. It is used by the primary key to attest to a set of
|
||||
* third- party certifications over the associated User ID or User Attribute. This enables the
|
||||
* holder of an OpenPGP primary key to mark specific third-party certifications as
|
||||
* re-distributable with the rest of the Transferable Public Key (see the "No-modify" flag in
|
||||
* "Key Server Preferences", above). Implementations MUST include exactly one Attested
|
||||
* Certification subpacket in any generated Attestation Key Signature.
|
||||
*
|
||||
* See [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
|
||||
* See
|
||||
* [Attested Certification](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-10#section-5.2.3.30)
|
||||
*/
|
||||
attestedCertification(37)
|
||||
;
|
||||
|
||||
attestedCertification(37);
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
/**
|
||||
* Return the [SignatureSubpacket] that corresponds to the provided id.
|
||||
* If an unmatched code is presented, return null.
|
||||
* Return the [SignatureSubpacket] that corresponds to the provided id. If an unmatched code
|
||||
* is presented, return null.
|
||||
*
|
||||
* @param code id
|
||||
* @return signature subpacket
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): SignatureSubpacket? {
|
||||
return values().firstOrNull {
|
||||
it.code == code
|
||||
}
|
||||
return values().firstOrNull { it.code == code }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -381,21 +342,20 @@ enum class SignatureSubpacket(val code: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): SignatureSubpacket {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
|
||||
return fromCode(code)
|
||||
?: throw NoSuchElementException("No SignatureSubpacket tag found with code $code")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of signature subpacket tags into a list of [SignatureSubpacket SignatureSubpackets].
|
||||
* Convert an array of signature subpacket tags into a list of
|
||||
* [SignatureSubpacket SignatureSubpackets].
|
||||
*
|
||||
* @param codes array of codes
|
||||
* @return list of subpackets
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCodes(vararg codes: Int): List<SignatureSubpacket> {
|
||||
return codes.toList().mapNotNull {
|
||||
fromCode(it)
|
||||
}
|
||||
return codes.toList().mapNotNull { fromCode(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,150 +7,120 @@ package org.pgpainless.algorithm
|
|||
import org.bouncycastle.openpgp.PGPSignature
|
||||
|
||||
/**
|
||||
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1
|
||||
* See [PGPSignature] for comparison.
|
||||
* Enum that enlists all the Signature Types defined in rfc4880 section 5.2.1 See [PGPSignature] for
|
||||
* comparison.
|
||||
*
|
||||
* See [rfc4880 §5.2.1. Signature Types](https://tools.ietf.org/html/rfc4880#section-5.11)
|
||||
*/
|
||||
enum class SignatureType(val code: Int) {
|
||||
|
||||
/**
|
||||
* Signature of a binary document.
|
||||
* This means the signer owns it, created it, or certifies that it
|
||||
* has not been modified.
|
||||
* Signature of a binary document. This means the signer owns it, created it, or certifies that
|
||||
* it has not been modified.
|
||||
*/
|
||||
BINARY_DOCUMENT(0x00),
|
||||
|
||||
/**
|
||||
* Signature of a canonical text document.
|
||||
* This means the signer owns it, created it, or certifies that it
|
||||
* has not been modified. The signature is calculated over the text
|
||||
* data with its line endings converted to {@code <CR><LF>}.
|
||||
* Signature of a canonical text document. This means the signer owns it, created it, or
|
||||
* certifies that it has not been modified. The signature is calculated over the text data with
|
||||
* its line endings converted to {@code <CR><LF>}.
|
||||
*/
|
||||
CANONICAL_TEXT_DOCUMENT(0x01),
|
||||
|
||||
/**
|
||||
* Standalone signature.
|
||||
* This signature is a signature of only its own subpacket contents.
|
||||
* It is calculated identically to a signature over a zero-length
|
||||
* binary document. Note that it doesn't make sense to have a V3
|
||||
* standalone signature.
|
||||
* Standalone signature. This signature is a signature of only its own subpacket contents. It is
|
||||
* calculated identically to a signature over a zero-length binary document. Note that it
|
||||
* doesn't make sense to have a V3 standalone signature.
|
||||
*/
|
||||
STANDALONE(0x02),
|
||||
|
||||
/**
|
||||
* Generic certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification does not make any particular
|
||||
* assertion as to how well the certifier has checked that the owner
|
||||
* of the key is in fact the person described by the User ID.
|
||||
* Generic certification of a User ID and Public-Key packet. The issuer of this certification
|
||||
* does not make any particular assertion as to how well the certifier has checked that the
|
||||
* owner of the key is in fact the person described by the User ID.
|
||||
*/
|
||||
GENERIC_CERTIFICATION(0x10),
|
||||
|
||||
/**
|
||||
* Persona certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has not done any verification of
|
||||
* the claim that the owner of this key is the User ID specified.
|
||||
* Persona certification of a User ID and Public-Key packet. The issuer of this certification
|
||||
* has not done any verification of the claim that the owner of this key is the User ID
|
||||
* specified.
|
||||
*/
|
||||
NO_CERTIFICATION(0x11),
|
||||
|
||||
/**
|
||||
* Casual certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has done some casual
|
||||
* verification of the claim of identity.
|
||||
* Casual certification of a User ID and Public-Key packet. The issuer of this certification has
|
||||
* done some casual verification of the claim of identity.
|
||||
*/
|
||||
CASUAL_CERTIFICATION(0x12),
|
||||
|
||||
/**
|
||||
* Positive certification of a User ID and Public-Key packet.
|
||||
* The issuer of this certification has done substantial
|
||||
* verification of the claim of identity.
|
||||
* Positive certification of a User ID and Public-Key packet. The issuer of this certification
|
||||
* has done substantial verification of the claim of identity.
|
||||
*/
|
||||
POSITIVE_CERTIFICATION(0x13),
|
||||
|
||||
/**
|
||||
* Subkey Binding Signature.
|
||||
* This signature is a statement by the top-level signing key that
|
||||
* indicates that it owns the subkey. This signature is calculated
|
||||
* directly on the primary key and subkey, and not on any User ID or
|
||||
* other packets. A signature that binds a signing subkey MUST have
|
||||
* an Embedded Signature subpacket in this binding signature that
|
||||
* contains a [#PRIMARYKEY_BINDING] signature made by the
|
||||
* signing subkey on the primary key and subkey.
|
||||
* Subkey Binding Signature. This signature is a statement by the top-level signing key that
|
||||
* indicates that it owns the subkey. This signature is calculated directly on the primary key
|
||||
* and subkey, and not on any User ID or other packets. A signature that binds a signing subkey
|
||||
* MUST have an Embedded Signature subpacket in this binding signature that contains a
|
||||
* [#PRIMARYKEY_BINDING] signature made by the signing subkey on the primary key and subkey.
|
||||
*/
|
||||
SUBKEY_BINDING(0x18),
|
||||
|
||||
/**
|
||||
* Primary Key Binding Signature
|
||||
* This signature is a statement by a signing subkey, indicating
|
||||
* that it is owned by the primary key and subkey. This signature
|
||||
* is calculated the same way as a [#SUBKEY_BINDING] signature:
|
||||
* directly on the primary key and subkey, and not on any User ID or
|
||||
* other packets.
|
||||
* Primary Key Binding Signature This signature is a statement by a signing subkey, indicating
|
||||
* that it is owned by the primary key and subkey. This signature is calculated the same way as
|
||||
* a [#SUBKEY_BINDING] signature: directly on the primary key and subkey, and not on any User ID
|
||||
* or other packets.
|
||||
*/
|
||||
PRIMARYKEY_BINDING(0x19),
|
||||
|
||||
/**
|
||||
* Signature directly on a key
|
||||
* This signature is calculated directly on a key. It binds the
|
||||
* information in the Signature subpackets to the key, and is
|
||||
* appropriate to be used for subpackets that provide information
|
||||
* about the key, such as the Revocation Key subpacket. It is also
|
||||
* appropriate for statements that non-self certifiers want to make
|
||||
* about the key itself, rather than the binding between a key and a
|
||||
* name.
|
||||
* Signature directly on a key This signature is calculated directly on a key. It binds the
|
||||
* information in the Signature subpackets to the key, and is appropriate to be used for
|
||||
* subpackets that provide information about the key, such as the Revocation Key subpacket. It
|
||||
* is also appropriate for statements that non-self certifiers want to make about the key
|
||||
* itself, rather than the binding between a key and a name.
|
||||
*/
|
||||
DIRECT_KEY(0x1f),
|
||||
|
||||
/**
|
||||
* Key revocation signature
|
||||
* The signature is calculated directly on the key being revoked. A
|
||||
* revoked key is not to be used. Only revocation signatures by the
|
||||
* key being revoked, or by an authorized revocation key, should be
|
||||
* considered valid revocation signatures.
|
||||
* Key revocation signature The signature is calculated directly on the key being revoked. A
|
||||
* revoked key is not to be used. Only revocation signatures by the key being revoked, or by an
|
||||
* authorized revocation key, should be considered valid revocation signatures.
|
||||
*/
|
||||
KEY_REVOCATION(0x20),
|
||||
|
||||
/**
|
||||
* Subkey revocation signature
|
||||
* The signature is calculated directly on the subkey being revoked.
|
||||
* A revoked subkey is not to be used. Only revocation signatures
|
||||
* by the top-level signature key that is bound to this subkey, or
|
||||
* by an authorized revocation key, should be considered valid
|
||||
* Subkey revocation signature The signature is calculated directly on the subkey being revoked.
|
||||
* A revoked subkey is not to be used. Only revocation signatures by the top-level signature key
|
||||
* that is bound to this subkey, or by an authorized revocation key, should be considered valid
|
||||
* revocation signatures.
|
||||
*/
|
||||
SUBKEY_REVOCATION(0x28),
|
||||
|
||||
/**
|
||||
* Certification revocation signature
|
||||
* This signature revokes an earlier User ID certification signature
|
||||
* (signature class 0x10 through 0x13) or signature [#DIRECT_KEY].
|
||||
* It should be issued by the same key that issued the
|
||||
* revoked signature or an authorized revocation key. The signature
|
||||
* is computed over the same data as the certificate that it
|
||||
* revokes, and should have a later creation date than that
|
||||
* certificate.
|
||||
* Certification revocation signature This signature revokes an earlier User ID certification
|
||||
* signature (signature class 0x10 through 0x13) or signature [#DIRECT_KEY]. It should be issued
|
||||
* by the same key that issued the revoked signature or an authorized revocation key. The
|
||||
* signature is computed over the same data as the certificate that it revokes, and should have
|
||||
* a later creation date than that certificate.
|
||||
*/
|
||||
CERTIFICATION_REVOCATION(0x30),
|
||||
|
||||
/**
|
||||
* Timestamp signature.
|
||||
* This signature is only meaningful for the timestamp contained in
|
||||
* it.
|
||||
*/
|
||||
/** Timestamp signature. This signature is only meaningful for the timestamp contained in it. */
|
||||
TIMESTAMP(0x40),
|
||||
|
||||
/**
|
||||
* Third-Party Confirmation signature.
|
||||
* This signature is a signature over some other OpenPGP Signature
|
||||
* packet(s). It is analogous to a notary seal on the signed data.
|
||||
* A third-party signature SHOULD include Signature Target
|
||||
* subpacket(s) to give easy identification. Note that we really do
|
||||
* mean SHOULD. There are plausible uses for this (such as a blind
|
||||
* party that only sees the signature, not the key or source
|
||||
* document) that cannot include a target subpacket.
|
||||
* Third-Party Confirmation signature. This signature is a signature over some other OpenPGP
|
||||
* Signature packet(s). It is analogous to a notary seal on the signed data. A third-party
|
||||
* signature SHOULD include Signature Target subpacket(s) to give easy identification. Note that
|
||||
* we really do mean SHOULD. There are plausible uses for this (such as a blind party that only
|
||||
* sees the signature, not the key or source document) that cannot include a target subpacket.
|
||||
*/
|
||||
THIRD_PARTY_CONFIRMATION(0x50)
|
||||
;
|
||||
THIRD_PARTY_CONFIRMATION(0x50);
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -162,9 +132,7 @@ enum class SignatureType(val code: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): SignatureType? {
|
||||
return values().firstOrNull {
|
||||
it.code == code
|
||||
}
|
||||
return values().firstOrNull { it.code == code }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,8 +144,9 @@ enum class SignatureType(val code: Int) {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): SignatureType {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
|
||||
return fromCode(code)
|
||||
?: throw NoSuchElementException(
|
||||
"Signature type 0x${Integer.toHexString(code)} appears to be invalid.")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -188,8 +157,7 @@ enum class SignatureType(val code: Int) {
|
|||
* @throws IllegalArgumentException in case of an unmatched signature type code
|
||||
*/
|
||||
@JvmStatic
|
||||
@Deprecated("Deprecated in favor of requireFromCode",
|
||||
ReplaceWith("requireFromCode"))
|
||||
@Deprecated("Deprecated in favor of requireFromCode", ReplaceWith("requireFromCode"))
|
||||
fun valueOf(code: Int): SignatureType {
|
||||
try {
|
||||
return requireFromCode(code)
|
||||
|
@ -197,12 +165,12 @@ enum class SignatureType(val code: Int) {
|
|||
throw IllegalArgumentException(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun isRevocationSignature(signatureType: Int): Boolean {
|
||||
return isRevocationSignature(valueOf(signatureType))
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun isRevocationSignature(signatureType: SignatureType): Boolean {
|
||||
return when (signatureType) {
|
||||
|
@ -218,11 +186,11 @@ enum class SignatureType(val code: Int) {
|
|||
DIRECT_KEY,
|
||||
TIMESTAMP,
|
||||
THIRD_PARTY_CONFIRMATION -> false
|
||||
KEY_REVOCATION,
|
||||
SUBKEY_REVOCATION,
|
||||
KEY_REVOCATION,
|
||||
SUBKEY_REVOCATION,
|
||||
CERTIFICATION_REVOCATION -> true
|
||||
else -> throw IllegalArgumentException("Unknown signature type: $signatureType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,61 +11,52 @@ package org.pgpainless.algorithm
|
|||
*/
|
||||
enum class StreamEncoding(val code: Char) {
|
||||
|
||||
/**
|
||||
* The Literal packet contains binary data.
|
||||
*/
|
||||
/** The Literal packet contains binary data. */
|
||||
BINARY('b'),
|
||||
|
||||
/**
|
||||
* The Literal packet contains text data, and thus may need line ends converted to local form, or other
|
||||
* text-mode changes.
|
||||
* The Literal packet contains text data, and thus may need line ends converted to local form,
|
||||
* or other text-mode changes.
|
||||
*/
|
||||
TEXT('t'),
|
||||
|
||||
/**
|
||||
* Indication that the implementation believes that the literal data contains UTF-8 text.
|
||||
*/
|
||||
/** Indication that the implementation believes that the literal data contains UTF-8 text. */
|
||||
UTF8('u'),
|
||||
|
||||
/**
|
||||
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local conversions.
|
||||
* RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral one).
|
||||
* Both of these local modes are deprecated.
|
||||
* Early versions of PGP also defined a value of 'l' as a 'local' mode for machine-local
|
||||
* conversions. RFC 1991 [RFC1991] incorrectly stated this local mode flag as '1' (ASCII numeral
|
||||
* one). Both of these local modes are deprecated.
|
||||
*/
|
||||
@Deprecated("LOCAL is deprecated.")
|
||||
LOCAL('l'),
|
||||
@Deprecated("LOCAL is deprecated.") LOCAL('l'),
|
||||
;
|
||||
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
||||
* If no matching encoding is found, return null.
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
|
||||
* encoding is found, return null.
|
||||
*
|
||||
* @param code identifier
|
||||
* @return encoding enum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromCode(code: Int): StreamEncoding? {
|
||||
return values().firstOrNull {
|
||||
it.code == code.toChar()
|
||||
} ?: if (code == 1) return LOCAL else null
|
||||
return values().firstOrNull { it.code == code.toChar() }
|
||||
?: if (code == 1) return LOCAL else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier.
|
||||
* If no matching encoding is found, throw a [NoSuchElementException].
|
||||
* Return the [StreamEncoding] corresponding to the provided code identifier. If no matching
|
||||
* encoding is found, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param code identifier
|
||||
* @return encoding enum
|
||||
*
|
||||
* @throws NoSuchElementException in case of an unmatched identifier
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromCode(code: Int): StreamEncoding {
|
||||
return fromCode(code) ?:
|
||||
throw NoSuchElementException("No StreamEncoding found for code $code")
|
||||
return fromCode(code)
|
||||
?: throw NoSuchElementException("No StreamEncoding found for code $code")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,110 +11,79 @@ package org.pgpainless.algorithm
|
|||
*/
|
||||
enum class SymmetricKeyAlgorithm(val algorithmId: Int) {
|
||||
|
||||
/**
|
||||
* Plaintext or unencrypted data.
|
||||
*/
|
||||
NULL (0),
|
||||
/** Plaintext or unencrypted data. */
|
||||
NULL(0),
|
||||
|
||||
/**
|
||||
* IDEA is deprecated.
|
||||
*
|
||||
* @deprecated use a different algorithm.
|
||||
*/
|
||||
@Deprecated("IDEA is deprecated.")
|
||||
IDEA (1),
|
||||
@Deprecated("IDEA is deprecated.") IDEA(1),
|
||||
|
||||
/**
|
||||
* TripleDES (DES-EDE - 168 bit key derived from 192).
|
||||
*/
|
||||
TRIPLE_DES (2),
|
||||
/** TripleDES (DES-EDE - 168 bit key derived from 192). */
|
||||
TRIPLE_DES(2),
|
||||
|
||||
/**
|
||||
* CAST5 (128-bit key, as per RFC2144).
|
||||
*/
|
||||
CAST5 (3),
|
||||
/** CAST5 (128-bit key, as per RFC2144). */
|
||||
CAST5(3),
|
||||
|
||||
/**
|
||||
* Blowfish (128-bit key, 16 rounds).
|
||||
*/
|
||||
BLOWFISH (4),
|
||||
/** Blowfish (128-bit key, 16 rounds). */
|
||||
BLOWFISH(4),
|
||||
|
||||
/**
|
||||
* Reserved in RFC4880.
|
||||
* SAFER-SK128 (13 rounds)
|
||||
*/
|
||||
SAFER (5),
|
||||
/** Reserved in RFC4880. SAFER-SK128 (13 rounds) */
|
||||
SAFER(5),
|
||||
|
||||
/**
|
||||
* Reserved in RFC4880.
|
||||
* Reserved for DES/SK
|
||||
*/
|
||||
DES (6),
|
||||
/** Reserved in RFC4880. Reserved for DES/SK */
|
||||
DES(6),
|
||||
|
||||
/**
|
||||
* AES with 128-bit key.
|
||||
*/
|
||||
AES_128 (7),
|
||||
/** AES with 128-bit key. */
|
||||
AES_128(7),
|
||||
|
||||
/**
|
||||
* AES with 192-bit key.
|
||||
*/
|
||||
AES_192 (8),
|
||||
/** AES with 192-bit key. */
|
||||
AES_192(8),
|
||||
|
||||
/**
|
||||
* AES with 256-bit key.
|
||||
*/
|
||||
AES_256 (9),
|
||||
/** AES with 256-bit key. */
|
||||
AES_256(9),
|
||||
|
||||
/**
|
||||
* Twofish with 256-bit key.
|
||||
*/
|
||||
TWOFISH (10),
|
||||
/** Twofish with 256-bit key. */
|
||||
TWOFISH(10),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 128-bit key.
|
||||
*/
|
||||
CAMELLIA_128 (11),
|
||||
/** Reserved for Camellia with 128-bit key. */
|
||||
CAMELLIA_128(11),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 192-bit key.
|
||||
*/
|
||||
CAMELLIA_192 (12),
|
||||
/** Reserved for Camellia with 192-bit key. */
|
||||
CAMELLIA_192(12),
|
||||
|
||||
/**
|
||||
* Reserved for Camellia with 256-bit key.
|
||||
*/
|
||||
CAMELLIA_256 (13),
|
||||
/** Reserved for Camellia with 256-bit key. */
|
||||
CAMELLIA_256(13),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
||||
* If an invalid id is provided, null is returned.
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
|
||||
* an invalid id is provided, null is returned.
|
||||
*
|
||||
* @param id numeric algorithm id
|
||||
* @return symmetric key algorithm enum
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fromId(id: Int): SymmetricKeyAlgorithm? {
|
||||
return values().firstOrNull {
|
||||
it.algorithmId == id
|
||||
}
|
||||
return values().firstOrNull { it.algorithmId == id }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id.
|
||||
* If an invalid id is provided, throw a [NoSuchElementException].
|
||||
* Return the [SymmetricKeyAlgorithm] enum that corresponds to the provided numeric id. If
|
||||
* an invalid id is provided, throw a [NoSuchElementException].
|
||||
*
|
||||
* @param id numeric algorithm id
|
||||
* @return symmetric key algorithm enum
|
||||
*
|
||||
* @throws NoSuchElementException if an unmatched id is provided
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireFromId(id: Int): SymmetricKeyAlgorithm {
|
||||
return fromId(id) ?:
|
||||
throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
|
||||
return fromId(id)
|
||||
?: throw NoSuchElementException("No SymmetricKeyAlgorithm found for id $id")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,26 +5,25 @@
|
|||
package org.pgpainless.algorithm
|
||||
|
||||
/**
|
||||
* Facade class for [org.bouncycastle.bcpg.sig.TrustSignature].
|
||||
* A trust signature subpacket marks the trustworthiness of a certificate and defines its capabilities to act
|
||||
* as a trusted introducer.
|
||||
* Facade class for [org.bouncycastle.bcpg.sig.TrustSignature]. A trust signature subpacket marks
|
||||
* the trustworthiness of a certificate and defines its capabilities to act as a trusted introducer.
|
||||
*/
|
||||
class Trustworthiness(amount: Int, depth: Int) {
|
||||
val depth = capDepth(depth)
|
||||
val amount = capAmount(amount)
|
||||
|
||||
/**
|
||||
* Returns true, if the trust amount is equal to 0.
|
||||
* This means the key is not trusted.
|
||||
* Returns true, if the trust amount is equal to 0. This means the key is not trusted.
|
||||
*
|
||||
* Otherwise return false
|
||||
*
|
||||
* @return true if untrusted
|
||||
*/
|
||||
fun isNotTrusted() = amount == NOT_TRUSTED
|
||||
|
||||
/**
|
||||
* Return true if the certificate is at least marginally trusted.
|
||||
* That is the case, if the trust amount is greater than 0.
|
||||
* Return true if the certificate is at least marginally trusted. That is the case, if the trust
|
||||
* amount is greater than 0.
|
||||
*
|
||||
* @return true if the cert is at least marginally trusted
|
||||
*/
|
||||
|
@ -46,7 +45,8 @@ class Trustworthiness(amount: Int, depth: Int) {
|
|||
fun isIntroducer() = depth >= 1
|
||||
|
||||
/**
|
||||
* Return true, if the certified cert can introduce certificates with trust depth of <pre>otherDepth</pre>.
|
||||
* Return true, if the certified cert can introduce certificates with trust depth of
|
||||
* <pre>otherDepth</pre>.
|
||||
*
|
||||
* @param otherDepth other certifications trust depth
|
||||
* @return true if the cert can introduce the other
|
||||
|
@ -54,7 +54,8 @@ class Trustworthiness(amount: Int, depth: Int) {
|
|||
fun canIntroduce(otherDepth: Int) = depth > otherDepth
|
||||
|
||||
/**
|
||||
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre> trust depth.
|
||||
* Return true, if the certified cert can introduce certificates with the given <pre>other</pre>
|
||||
* trust depth.
|
||||
*
|
||||
* @param other other certificates trust depth
|
||||
* @return true if the cert can introduce the other
|
||||
|
@ -66,33 +67,29 @@ class Trustworthiness(amount: Int, depth: Int) {
|
|||
const val MARGINALLY_CONVINCED = 60 // default value for marginally convinced
|
||||
const val NOT_TRUSTED = 0 // 0 is not trusted
|
||||
|
||||
@JvmStatic
|
||||
private val validRange = 0..255
|
||||
@JvmStatic private val validRange = 0..255
|
||||
|
||||
/**
|
||||
* This means that we are fully convinced of the trustworthiness of the key.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
|
||||
@JvmStatic fun fullyTrusted() = Builder(THRESHOLD_FULLY_CONVINCED)
|
||||
|
||||
/**
|
||||
* This means that we are marginally (partially) convinced of the trustworthiness of the key.
|
||||
* This means that we are marginally (partially) convinced of the trustworthiness of the
|
||||
* key.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
|
||||
@JvmStatic fun marginallyTrusted() = Builder(MARGINALLY_CONVINCED)
|
||||
|
||||
/**
|
||||
* This means that we do not trust the key.
|
||||
* Can be used to overwrite previous trust.
|
||||
* This means that we do not trust the key. Can be used to overwrite previous trust.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun untrusted() = Builder(NOT_TRUSTED)
|
||||
@JvmStatic fun untrusted() = Builder(NOT_TRUSTED)
|
||||
|
||||
@JvmStatic
|
||||
private fun capAmount(amount: Int): Int {
|
||||
|
@ -114,29 +111,28 @@ class Trustworthiness(amount: Int, depth: Int) {
|
|||
class Builder(val amount: Int) {
|
||||
|
||||
/**
|
||||
* The key is a trusted introducer (depth 1).
|
||||
* Certifications made by this key are considered trustworthy.
|
||||
* The key is a trusted introducer (depth 1). Certifications made by this key are considered
|
||||
* trustworthy.
|
||||
*
|
||||
* @return trust
|
||||
*/
|
||||
fun introducer() = Trustworthiness(amount, 1)
|
||||
|
||||
/**
|
||||
* The key is a meta introducer (depth 2).
|
||||
* This key can introduce trusted introducers of depth 1.
|
||||
* The key is a meta introducer (depth 2). This key can introduce trusted introducers of
|
||||
* depth 1.
|
||||
*
|
||||
* @return trust
|
||||
*/
|
||||
fun metaIntroducer() = Trustworthiness(amount, 2)
|
||||
|
||||
/**
|
||||
* The key is a meta introducer of depth <pre>n</pre>.
|
||||
* This key can introduce meta introducers of depth <pre>n - 1</pre>.
|
||||
* The key is a meta introducer of depth <pre>n</pre>. This key can introduce meta
|
||||
* introducers of depth <pre>n - 1</pre>.
|
||||
*
|
||||
* @param n depth
|
||||
* @return trust
|
||||
*/
|
||||
fun metaIntroducerOfDepth(d: Int) = Trustworthiness(amount, d)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ interface HashAlgorithmNegotiator {
|
|||
companion object {
|
||||
|
||||
/**
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for non-revocation signatures
|
||||
* based on the given [Policy].
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for
|
||||
* non-revocation signatures based on the given [Policy].
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @return negotiator
|
||||
|
@ -38,8 +38,8 @@ interface HashAlgorithmNegotiator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation signatures
|
||||
* based on the given [Policy].
|
||||
* Return an instance that negotiates [HashAlgorithms][HashAlgorithm] used for revocation
|
||||
* signatures based on the given [Policy].
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @return negotiator
|
||||
|
@ -57,15 +57,17 @@ interface HashAlgorithmNegotiator {
|
|||
* @return negotiator
|
||||
*/
|
||||
@JvmStatic
|
||||
fun negotiateByPolicy(hashAlgorithmPolicy: Policy.HashAlgorithmPolicy): HashAlgorithmNegotiator {
|
||||
return object: HashAlgorithmNegotiator {
|
||||
override fun negotiateHashAlgorithm(orderedPrefs: Set<HashAlgorithm>): HashAlgorithm {
|
||||
return orderedPrefs.firstOrNull {
|
||||
hashAlgorithmPolicy.isAcceptable(it)
|
||||
} ?: hashAlgorithmPolicy.defaultHashAlgorithm()
|
||||
fun negotiateByPolicy(
|
||||
hashAlgorithmPolicy: Policy.HashAlgorithmPolicy
|
||||
): HashAlgorithmNegotiator {
|
||||
return object : HashAlgorithmNegotiator {
|
||||
override fun negotiateHashAlgorithm(
|
||||
orderedPrefs: Set<HashAlgorithm>
|
||||
): HashAlgorithm {
|
||||
return orderedPrefs.firstOrNull { hashAlgorithmPolicy.isAcceptable(it) }
|
||||
?: hashAlgorithmPolicy.defaultHashAlgorithm()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,38 +4,41 @@
|
|||
|
||||
package org.pgpainless.algorithm.negotiation
|
||||
|
||||
import java.lang.IllegalArgumentException
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.policy.Policy
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
interface SymmetricKeyAlgorithmNegotiator {
|
||||
|
||||
/**
|
||||
* Negotiate a symmetric encryption algorithm.
|
||||
* If the override is non-null, it will be returned instead of performing an actual negotiation.
|
||||
* Otherwise, the list of ordered sets containing the preferences of different recipient keys will be
|
||||
* used to determine a suitable symmetric encryption algorithm.
|
||||
* Negotiate a symmetric encryption algorithm. If the override is non-null, it will be returned
|
||||
* instead of performing an actual negotiation. Otherwise, the list of ordered sets containing
|
||||
* the preferences of different recipient keys will be used to determine a suitable symmetric
|
||||
* encryption algorithm.
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @param override algorithm override (if not null, return this)
|
||||
* @param keyPreferences list of preferences per key
|
||||
* @return negotiated algorithm
|
||||
*/
|
||||
fun negotiate(policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>): SymmetricKeyAlgorithm
|
||||
fun negotiate(
|
||||
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>
|
||||
): SymmetricKeyAlgorithm
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun byPopularity(): SymmetricKeyAlgorithmNegotiator {
|
||||
return object: SymmetricKeyAlgorithmNegotiator {
|
||||
return object : SymmetricKeyAlgorithmNegotiator {
|
||||
override fun negotiate(
|
||||
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>):
|
||||
SymmetricKeyAlgorithm {
|
||||
policy: Policy.SymmetricKeyAlgorithmPolicy,
|
||||
override: SymmetricKeyAlgorithm?,
|
||||
keyPreferences: List<Set<SymmetricKeyAlgorithm>>
|
||||
): SymmetricKeyAlgorithm {
|
||||
if (override == SymmetricKeyAlgorithm.NULL) {
|
||||
throw IllegalArgumentException("Algorithm override cannot be NULL (plaintext).")
|
||||
throw IllegalArgumentException(
|
||||
"Algorithm override cannot be NULL (plaintext).")
|
||||
}
|
||||
|
||||
if (override != null) {
|
||||
|
@ -53,7 +56,9 @@ interface SymmetricKeyAlgorithmNegotiator {
|
|||
|
||||
// Pivot map and sort by popularity ascending
|
||||
// score to list(algo)
|
||||
val byScore = supportWeight.toList()
|
||||
val byScore =
|
||||
supportWeight
|
||||
.toList()
|
||||
.map { e -> e.second to e.first }
|
||||
.groupBy { e -> e.first }
|
||||
.map { e -> e.key to e.value.map { it.second }.toList() }
|
||||
|
@ -70,8 +75,7 @@ interface SymmetricKeyAlgorithmNegotiator {
|
|||
|
||||
return policy.defaultSymmetricKeyAlgorithm
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,19 @@ package org.pgpainless.authentication
|
|||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
|
||||
class CertificateAuthenticity(val userId: String,
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val certificationChains: Map<CertificationChain, Int>,
|
||||
val targetAmount: Int) {
|
||||
class CertificateAuthenticity(
|
||||
val userId: String,
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val certificationChains: Map<CertificationChain, Int>,
|
||||
val targetAmount: Int
|
||||
) {
|
||||
|
||||
val totalTrustAmount: Int
|
||||
get() = certificationChains.values.sum()
|
||||
|
||||
|
||||
/**
|
||||
* Return the degree of authentication of the binding in percent.
|
||||
* 100% means full authentication.
|
||||
* Values smaller than 100% mean partial authentication.
|
||||
* Return the degree of authentication of the binding in percent. 100% means full
|
||||
* authentication. Values smaller than 100% mean partial authentication.
|
||||
*
|
||||
* @return authenticity in percent
|
||||
*/
|
||||
|
@ -42,16 +42,7 @@ class CertificateAuthenticity(val userId: String,
|
|||
* @param trustAmount actual trust amount of the chain
|
||||
* @param chainLinks links of the chain, starting at the trust-root, ending at the target.
|
||||
*/
|
||||
class CertificationChain(
|
||||
val trustAmount: Int,
|
||||
val chainLinks: List<ChainLink>) {
|
||||
class CertificationChain(val trustAmount: Int, val chainLinks: List<ChainLink>) {}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A chain link contains a node in the trust chain.
|
||||
*/
|
||||
class ChainLink(
|
||||
val certificate: PGPPublicKeyRing) {
|
||||
|
||||
}
|
||||
/** A chain link contains a node in the trust chain. */
|
||||
class ChainLink(val certificate: PGPPublicKeyRing) {}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.authentication;
|
||||
package org.pgpainless.authentication
|
||||
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import java.util.*
|
||||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
|
||||
/**
|
||||
* Interface for a CA that can authenticate trust-worthy certificates.
|
||||
* Such a CA might be a fixed list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
|
||||
* Interface for a CA that can authenticate trust-worthy certificates. Such a CA might be a fixed
|
||||
* list of trustworthy certificates, or a dynamic implementation like the Web-of-Trust.
|
||||
*
|
||||
* @see <a href="https://github.com/pgpainless/pgpainless-wot">PGPainless-WOT</a>
|
||||
* @see <a href="https://sequoia-pgp.gitlab.io/sequoia-wot/">OpenPGP Web of Trust</a>
|
||||
|
@ -17,52 +17,58 @@ import java.util.*
|
|||
interface CertificateAuthority {
|
||||
|
||||
/**
|
||||
* Determine the authenticity of the binding between the given fingerprint and the userId.
|
||||
* In other words, determine, how much evidence can be gathered, that the certificate with the given
|
||||
* fingerprint really belongs to the user with the given userId.
|
||||
* Determine the authenticity of the binding between the given fingerprint and the userId. In
|
||||
* other words, determine, how much evidence can be gathered, that the certificate with the
|
||||
* given fingerprint really belongs to the user with the given userId.
|
||||
*
|
||||
* @param fingerprint fingerprint of the certificate
|
||||
* @param userId userId
|
||||
* @param email if true, the userId will be treated as an email address and all user-IDs containing
|
||||
* the email address will be matched.
|
||||
* @param email if true, the userId will be treated as an email address and all user-IDs
|
||||
* containing the email address will be matched.
|
||||
* @param referenceTime reference time at which the binding shall be evaluated
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||
* authenticated, 60 = partially authenticated...)
|
||||
* @return information about the authenticity of the binding
|
||||
*/
|
||||
fun authenticateBinding(fingerprint: OpenPgpFingerprint,
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): CertificateAuthenticity;
|
||||
fun authenticateBinding(
|
||||
fingerprint: OpenPgpFingerprint,
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int
|
||||
): CertificateAuthenticity
|
||||
|
||||
/**
|
||||
* Lookup certificates, which carry a trustworthy binding to the given userId.
|
||||
*
|
||||
* @param userId userId
|
||||
* @param email if true, the user-ID will be treated as an email address and all user-IDs containing
|
||||
* the email address will be matched.
|
||||
* @param email if true, the user-ID will be treated as an email address and all user-IDs
|
||||
* containing the email address will be matched.
|
||||
* @param referenceTime reference time at which the binding shall be evaluated
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||
* authenticated, 60 = partially authenticated...)
|
||||
* @return list of identified bindings
|
||||
*/
|
||||
fun lookupByUserId(userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): List<CertificateAuthenticity>
|
||||
fun lookupByUserId(
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int
|
||||
): List<CertificateAuthenticity>
|
||||
|
||||
/**
|
||||
* Identify trustworthy bindings for a certificate.
|
||||
* The result is a list of authenticatable userIds on the certificate.
|
||||
* Identify trustworthy bindings for a certificate. The result is a list of authenticatable
|
||||
* userIds on the certificate.
|
||||
*
|
||||
* @param fingerprint fingerprint of the certificate
|
||||
* @param referenceTime reference time for trust calculations
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @param targetAmount target trust amount (120 = fully authenticated, 240 = doubly
|
||||
* authenticated, 60 = partially authenticated...)
|
||||
* @return list of identified bindings
|
||||
*/
|
||||
fun identifyByFingerprint(fingerprint: OpenPgpFingerprint,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int): List<CertificateAuthenticity>
|
||||
fun identifyByFingerprint(
|
||||
fingerprint: OpenPgpFingerprint,
|
||||
referenceTime: Date,
|
||||
targetAmount: Int
|
||||
): List<CertificateAuthenticity>
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import org.bouncycastle.extensions.getPublicKeyFor
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||
|
@ -14,13 +17,8 @@ import org.pgpainless.key.protection.SecretKeyRingProtector
|
|||
import org.pgpainless.signature.SignatureUtils
|
||||
import org.pgpainless.util.Passphrase
|
||||
import org.pgpainless.util.SessionKey
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Options for decryption and signature verification.
|
||||
*/
|
||||
/** Options for decryption and signature verification. */
|
||||
class ConsumerOptions {
|
||||
|
||||
private var ignoreMDCErrors = false
|
||||
|
@ -34,15 +32,16 @@ class ConsumerOptions {
|
|||
private var missingCertificateCallback: MissingPublicKeyCallback? = null
|
||||
|
||||
private var sessionKey: SessionKey? = null
|
||||
private val customDecryptorFactories = mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
|
||||
private val customDecryptorFactories =
|
||||
mutableMapOf<SubkeyIdentifier, PublicKeyDataDecryptorFactory>()
|
||||
private val decryptionKeys = mutableMapOf<PGPSecretKeyRing, SecretKeyRingProtector>()
|
||||
private val decryptionPassphrases = mutableSetOf<Passphrase>()
|
||||
private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE
|
||||
private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy()
|
||||
|
||||
/**
|
||||
* Consider signatures on the message made before the given timestamp invalid.
|
||||
* Null means no limitation.
|
||||
* Consider signatures on the message made before the given timestamp invalid. Null means no
|
||||
* limitation.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return options
|
||||
|
@ -54,8 +53,8 @@ class ConsumerOptions {
|
|||
fun getVerifyNotBefore() = verifyNotBefore
|
||||
|
||||
/**
|
||||
* Consider signatures on the message made after the given timestamp invalid.
|
||||
* Null means no limitation.
|
||||
* Consider signatures on the message made after the given timestamp invalid. Null means no
|
||||
* limitation.
|
||||
*
|
||||
* @param timestamp timestamp
|
||||
* @return options
|
||||
|
@ -82,26 +81,27 @@ class ConsumerOptions {
|
|||
* @param verificationCerts certificates for signature verification
|
||||
* @return options
|
||||
*/
|
||||
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions = apply {
|
||||
for (cert in verificationCerts) {
|
||||
addVerificationCert(cert)
|
||||
fun addVerificationCerts(verificationCerts: PGPPublicKeyRingCollection): ConsumerOptions =
|
||||
apply {
|
||||
for (cert in verificationCerts) {
|
||||
addVerificationCert(cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some detached signatures from the given [InputStream] for verification.
|
||||
*
|
||||
* @param signatureInputStream input stream of detached signatures
|
||||
* @return options
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws PGPException in case of an OpenPGP error
|
||||
*/
|
||||
@Throws(IOException::class, PGPException::class)
|
||||
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions = apply {
|
||||
val signatures = SignatureUtils.readSignatures(signatureInputStream)
|
||||
addVerificationOfDetachedSignatures(signatures)
|
||||
}
|
||||
fun addVerificationOfDetachedSignatures(signatureInputStream: InputStream): ConsumerOptions =
|
||||
apply {
|
||||
val signatures = SignatureUtils.readSignatures(signatureInputStream)
|
||||
addVerificationOfDetachedSignatures(signatures)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add some detached signatures for verification.
|
||||
|
@ -109,7 +109,9 @@ class ConsumerOptions {
|
|||
* @param detachedSignatures detached signatures
|
||||
* @return options
|
||||
*/
|
||||
fun addVerificationOfDetachedSignatures(detachedSignatures: List<PGPSignature>): ConsumerOptions = apply {
|
||||
fun addVerificationOfDetachedSignatures(
|
||||
detachedSignatures: List<PGPSignature>
|
||||
): ConsumerOptions = apply {
|
||||
for (signature in detachedSignatures) {
|
||||
addVerificationOfDetachedSignature(signature)
|
||||
}
|
||||
|
@ -121,14 +123,16 @@ class ConsumerOptions {
|
|||
* @param detachedSignature detached signature
|
||||
* @return options
|
||||
*/
|
||||
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions = apply {
|
||||
detachedSignatures.add(detachedSignature)
|
||||
}
|
||||
fun addVerificationOfDetachedSignature(detachedSignature: PGPSignature): ConsumerOptions =
|
||||
apply {
|
||||
detachedSignatures.add(detachedSignature)
|
||||
}
|
||||
|
||||
fun getDetachedSignatures() = detachedSignatures.toList()
|
||||
|
||||
/**
|
||||
* Set a callback that's used when a certificate (public key) is missing for signature verification.
|
||||
* Set a callback that's used when a certificate (public key) is missing for signature
|
||||
* verification.
|
||||
*
|
||||
* @param callback callback
|
||||
* @return options
|
||||
|
@ -152,18 +156,18 @@ class ConsumerOptions {
|
|||
fun getSessionKey() = sessionKey
|
||||
|
||||
/**
|
||||
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector]
|
||||
* is used to decrypt it when needed.
|
||||
* Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is
|
||||
* used to decrypt it when needed.
|
||||
*
|
||||
* @param key key
|
||||
* @param keyRingProtector protector for the secret key
|
||||
* @return options
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun addDecryptionKey(key: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
||||
decryptionKeys[key] = protector
|
||||
}
|
||||
fun addDecryptionKey(
|
||||
key: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||
) = apply { decryptionKeys[key] = protector }
|
||||
|
||||
/**
|
||||
* Add the keys in the provided key collection for message decryption.
|
||||
|
@ -173,18 +177,21 @@ class ConsumerOptions {
|
|||
* @return options
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun addDecryptionKeys(keys: PGPSecretKeyRingCollection,
|
||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()) = apply {
|
||||
fun addDecryptionKeys(
|
||||
keys: PGPSecretKeyRingCollection,
|
||||
protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys()
|
||||
) = apply {
|
||||
for (key in keys) {
|
||||
addDecryptionKey(key, protector)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a passphrase for message decryption.
|
||||
* This passphrase will be used to try to decrypt messages which were symmetrically encrypted for a passphrase.
|
||||
* Add a passphrase for message decryption. This passphrase will be used to try to decrypt
|
||||
* messages which were symmetrically encrypted for a passphrase.
|
||||
*
|
||||
* See [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
||||
* See
|
||||
* [Symmetrically Encrypted Data Packet](https://datatracker.ietf.org/doc/html/rfc4880#section-5.7)
|
||||
*
|
||||
* @param passphrase passphrase
|
||||
* @return options
|
||||
|
@ -195,8 +202,8 @@ class ConsumerOptions {
|
|||
|
||||
/**
|
||||
* Add a custom [PublicKeyDataDecryptorFactory] which enable decryption of messages, e.g. using
|
||||
* hardware-backed secret keys.
|
||||
* (See e.g. [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
|
||||
* hardware-backed secret keys. (See e.g.
|
||||
* [org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory]).
|
||||
*
|
||||
* @param factory decryptor factory
|
||||
* @return options
|
||||
|
@ -206,9 +213,8 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the custom [PublicKeyDataDecryptorFactory] that were
|
||||
* set by the user.
|
||||
* These factories can be used to decrypt session keys using a custom logic.
|
||||
* Return the custom [PublicKeyDataDecryptorFactory] that were set by the user. These factories
|
||||
* can be used to decrypt session keys using a custom logic.
|
||||
*
|
||||
* @return custom decryptor factories
|
||||
*/
|
||||
|
@ -236,8 +242,8 @@ class ConsumerOptions {
|
|||
fun getCertificateSource() = certificates
|
||||
|
||||
/**
|
||||
* Return the callback that gets called when a certificate for signature verification is missing.
|
||||
* This method might return `null` if the users hasn't set a callback.
|
||||
* Return the callback that gets called when a certificate for signature verification is
|
||||
* missing. This method might return `null` if the users hasn't set a callback.
|
||||
*
|
||||
* @return missing public key callback
|
||||
*/
|
||||
|
@ -255,45 +261,46 @@ class ConsumerOptions {
|
|||
|
||||
/**
|
||||
* By default, PGPainless will require encrypted messages to make use of SEIP data packets.
|
||||
* Those are Symmetrically Encrypted Integrity Protected Data packets.
|
||||
* Symmetrically Encrypted Data Packets without integrity protection are rejected by default.
|
||||
* Furthermore, PGPainless will throw an exception if verification of the MDC error detection
|
||||
* code of the SEIP packet fails.
|
||||
* Those are Symmetrically Encrypted Integrity Protected Data packets. Symmetrically Encrypted
|
||||
* Data Packets without integrity protection are rejected by default. Furthermore, PGPainless
|
||||
* will throw an exception if verification of the MDC error detection code of the SEIP packet
|
||||
* fails.
|
||||
*
|
||||
* Failure of MDC verification indicates a tampered ciphertext, which might be the cause of an
|
||||
* attack or data corruption.
|
||||
*
|
||||
* This method can be used to ignore MDC errors and allow PGPainless to consume encrypted data
|
||||
* without integrity protection.
|
||||
* If the flag <pre>ignoreMDCErrors</pre> is set to true, PGPainless will
|
||||
* without integrity protection. If the flag <pre>ignoreMDCErrors</pre> is set to true,
|
||||
* PGPainless will
|
||||
* * not throw exceptions for SEIP packets with tampered ciphertext
|
||||
* * not throw exceptions for SEIP packets with tampered MDC
|
||||
* * not throw exceptions for MDCs with bad CTB
|
||||
* * not throw exceptions for MDCs with bad length
|
||||
*
|
||||
* * not throw exceptions for SEIP packets with tampered ciphertext
|
||||
* * not throw exceptions for SEIP packets with tampered MDC
|
||||
* * not throw exceptions for MDCs with bad CTB
|
||||
* * not throw exceptions for MDCs with bad length
|
||||
* It will however still throw an exception if it encounters a SEIP packet with missing or
|
||||
* truncated MDC
|
||||
*
|
||||
*
|
||||
* It will however still throw an exception if it encounters a SEIP packet with missing or truncated MDC
|
||||
*
|
||||
* See [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
|
||||
* See
|
||||
* [Sym. Encrypted Integrity Protected Data Packet](https://datatracker.ietf.org/doc/html/rfc4880.section-5.13)
|
||||
*
|
||||
* @param ignoreMDCErrors true if MDC errors or missing MDCs shall be ignored, false otherwise.
|
||||
* @return options
|
||||
*/
|
||||
@Deprecated("Ignoring non-integrity-protected packets is discouraged.")
|
||||
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply { this.ignoreMDCErrors = ignoreMDCErrors }
|
||||
fun setIgnoreMDCErrors(ignoreMDCErrors: Boolean): ConsumerOptions = apply {
|
||||
this.ignoreMDCErrors = ignoreMDCErrors
|
||||
}
|
||||
|
||||
fun isIgnoreMDCErrors() = ignoreMDCErrors
|
||||
|
||||
/**
|
||||
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data.
|
||||
* This workaround might come in handy if PGPainless accidentally mistakes the data for binary OpenPGP data.
|
||||
* Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This
|
||||
* workaround might come in handy if PGPainless accidentally mistakes the data for binary
|
||||
* OpenPGP data.
|
||||
*
|
||||
* @return options
|
||||
*/
|
||||
fun forceNonOpenPgpData(): ConsumerOptions = apply {
|
||||
this.forceNonOpenPgpData = true
|
||||
}
|
||||
fun forceNonOpenPgpData(): ConsumerOptions = apply { this.forceNonOpenPgpData = true }
|
||||
|
||||
/**
|
||||
* Return true, if the ciphertext should be handled as binary non-OpenPGP data.
|
||||
|
@ -303,15 +310,15 @@ class ConsumerOptions {
|
|||
fun isForceNonOpenPgpData() = forceNonOpenPgpData
|
||||
|
||||
/**
|
||||
* Specify the [MissingKeyPassphraseStrategy].
|
||||
* This strategy defines, how missing passphrases for unlocking secret keys are handled.
|
||||
* In interactive mode ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
|
||||
* Specify the [MissingKeyPassphraseStrategy]. This strategy defines, how missing passphrases
|
||||
* for unlocking secret keys are handled. In interactive mode
|
||||
* ([MissingKeyPassphraseStrategy.INTERACTIVE]) PGPainless will try to obtain missing
|
||||
* passphrases for secret keys via the [SecretKeyRingProtector]
|
||||
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider] callback.
|
||||
*
|
||||
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will instead
|
||||
* throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of all keys for which
|
||||
* there are missing passphrases.
|
||||
* In non-interactice mode ([MissingKeyPassphraseStrategy.THROW_EXCEPTION]), PGPainless will
|
||||
* instead throw a [org.pgpainless.exception.MissingPassphraseException] containing the ids of
|
||||
* all keys for which there are missing passphrases.
|
||||
*
|
||||
* @param strategy strategy
|
||||
* @return options
|
||||
|
@ -331,8 +338,8 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set a custom multi-pass strategy for processing cleartext-signed messages.
|
||||
* Uses [InMemoryMultiPassStrategy] by default.
|
||||
* Set a custom multi-pass strategy for processing cleartext-signed messages. Uses
|
||||
* [InMemoryMultiPassStrategy] by default.
|
||||
*
|
||||
* @param multiPassStrategy multi-pass caching strategy
|
||||
* @return builder
|
||||
|
@ -343,8 +350,7 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the currently configured [MultiPassStrategy].
|
||||
* Defaults to [InMemoryMultiPassStrategy].
|
||||
* Return the currently configured [MultiPassStrategy]. Defaults to [InMemoryMultiPassStrategy].
|
||||
*
|
||||
* @return multi-pass strategy
|
||||
*/
|
||||
|
@ -353,8 +359,8 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Source for OpenPGP certificates.
|
||||
* When verifying signatures on a message, this object holds available signer certificates.
|
||||
* Source for OpenPGP certificates. When verifying signatures on a message, this object holds
|
||||
* available signer certificates.
|
||||
*/
|
||||
class CertificateSource {
|
||||
private val explicitCertificates: MutableSet<PGPPublicKeyRing> = mutableSetOf()
|
||||
|
@ -370,6 +376,7 @@ class ConsumerOptions {
|
|||
|
||||
/**
|
||||
* Return the set of explicitly set verification certificates.
|
||||
*
|
||||
* @return explicitly set verification certs
|
||||
*/
|
||||
fun getExplicitCertificates(): Set<PGPPublicKeyRing> {
|
||||
|
@ -377,9 +384,9 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a certificate which contains a subkey with the given keyId.
|
||||
* This method first checks all explicitly set verification certs and if no cert is found it consults
|
||||
* the certificate stores.
|
||||
* Return a certificate which contains a subkey with the given keyId. This method first
|
||||
* checks all explicitly set verification certs and if no cert is found it consults the
|
||||
* certificate stores.
|
||||
*
|
||||
* @param keyId key id
|
||||
* @return certificate
|
||||
|
@ -389,13 +396,10 @@ class ConsumerOptions {
|
|||
}
|
||||
|
||||
fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? =
|
||||
explicitCertificates.firstOrNull {
|
||||
it.getPublicKeyFor(signature) != null
|
||||
}
|
||||
explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun get() = ConsumerOptions()
|
||||
@JvmStatic fun get() = ConsumerOptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,19 @@ import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
|||
import org.pgpainless.key.SubkeyIdentifier
|
||||
|
||||
/**
|
||||
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message decryption
|
||||
* using public keys.
|
||||
* This class can for example be used to implement message encryption using hardware tokens like smartcards or
|
||||
* TPMs.
|
||||
* Custom [PublicKeyDataDecryptorFactory] which can enable customized implementations of message
|
||||
* decryption using public keys. This class can for example be used to implement message encryption
|
||||
* using hardware tokens like smartcards or TPMs.
|
||||
*
|
||||
* @see [ConsumerOptions.addCustomDecryptorFactory]
|
||||
*/
|
||||
interface CustomPublicKeyDataDecryptorFactory : PublicKeyDataDecryptorFactory {
|
||||
|
||||
/**
|
||||
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory]
|
||||
* is intended.
|
||||
* Identifier for the subkey for which this particular [CustomPublicKeyDataDecryptorFactory] is
|
||||
* intended.
|
||||
*
|
||||
* @return subkey identifier
|
||||
*/
|
||||
val subkeyIdentifier: SubkeyIdentifier
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,20 @@ package org.pgpainless.decryption_verification
|
|||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Builder class that takes an [InputStream] of ciphertext (or plaintext signed data)
|
||||
* and combines it with a configured [ConsumerOptions] object to form a [DecryptionStream] which
|
||||
* can be used to decrypt an OpenPGP message or verify signatures.
|
||||
* Builder class that takes an [InputStream] of ciphertext (or plaintext signed data) and combines
|
||||
* it with a configured [ConsumerOptions] object to form a [DecryptionStream] which can be used to
|
||||
* decrypt an OpenPGP message or verify signatures.
|
||||
*/
|
||||
class DecryptionBuilder: DecryptionBuilderInterface {
|
||||
class DecryptionBuilder : DecryptionBuilderInterface {
|
||||
|
||||
override fun onInputStream(inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
|
||||
return DecryptWithImpl(inputStream)
|
||||
}
|
||||
|
||||
class DecryptWithImpl(val inputStream: InputStream): DecryptionBuilderInterface.DecryptWith {
|
||||
class DecryptWithImpl(val inputStream: InputStream) : DecryptionBuilderInterface.DecryptWith {
|
||||
|
||||
override fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream {
|
||||
return OpenPgpMessageInputStream.create(inputStream, consumerOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
|
||||
interface DecryptionBuilderInterface {
|
||||
|
||||
/**
|
||||
* Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed data.
|
||||
* Create a [DecryptionStream] on an [InputStream] which contains the encrypted and/or signed
|
||||
* data.
|
||||
*
|
||||
* @param inputStream encrypted and/or signed data.
|
||||
* @return api handle
|
||||
|
@ -31,4 +32,4 @@ interface DecryptionBuilderInterface {
|
|||
@Throws(PGPException::class, IOException::class)
|
||||
fun withOptions(consumerOptions: ConsumerOptions): DecryptionStream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ import java.io.InputStream
|
|||
/**
|
||||
* Abstract definition of an [InputStream] which can be used to decrypt / verify OpenPGP messages.
|
||||
*/
|
||||
abstract class DecryptionStream: InputStream() {
|
||||
abstract class DecryptionStream : InputStream() {
|
||||
|
||||
/**
|
||||
* Return [MessageMetadata] about the decrypted / verified message.
|
||||
* The [DecryptionStream] MUST be closed via [close] before the metadata object can be accessed.
|
||||
* Return [MessageMetadata] about the decrypted / verified message. The [DecryptionStream] MUST
|
||||
* be closed via [close] before the metadata object can be accessed.
|
||||
*
|
||||
* @return message metadata
|
||||
*/
|
||||
abstract val metadata: MessageMetadata
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import kotlin.jvm.Throws
|
||||
import org.bouncycastle.bcpg.AEADEncDataPacket
|
||||
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
|
@ -12,25 +13,22 @@ import org.bouncycastle.openpgp.operator.PGPDataDecryptor
|
|||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory
|
||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Enable integration of hardware-backed OpenPGP keys.
|
||||
*/
|
||||
/** Enable integration of hardware-backed OpenPGP keys. */
|
||||
class HardwareSecurity {
|
||||
|
||||
interface DecryptionCallback {
|
||||
|
||||
/**
|
||||
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for dealing with
|
||||
* hardware security modules such as smartcards or TPMs.
|
||||
* Delegate decryption of a Public-Key-Encrypted-Session-Key (PKESK) to an external API for
|
||||
* dealing with hardware security modules such as smartcards or TPMs.
|
||||
*
|
||||
* If decryption fails for some reason, a subclass of the [HardwareSecurityException] is thrown.
|
||||
* If decryption fails for some reason, a subclass of the [HardwareSecurityException] is
|
||||
* thrown.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @param keyAlgorithm algorithm
|
||||
* @param sessionKeyData encrypted session key
|
||||
*
|
||||
* @return decrypted session key
|
||||
* @throws HardwareSecurityException exception
|
||||
*/
|
||||
|
@ -39,38 +37,51 @@ class HardwareSecurity {
|
|||
}
|
||||
|
||||
/**
|
||||
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted session keys
|
||||
* to a [DecryptionCallback].
|
||||
* Users can provide such a callback to delegate decryption of messages to hardware security SDKs.
|
||||
* Implementation of [PublicKeyDataDecryptorFactory] which delegates decryption of encrypted
|
||||
* session keys to a [DecryptionCallback]. Users can provide such a callback to delegate
|
||||
* decryption of messages to hardware security SDKs.
|
||||
*/
|
||||
class HardwareDataDecryptorFactory(
|
||||
override val subkeyIdentifier: SubkeyIdentifier,
|
||||
private val callback: DecryptionCallback,
|
||||
override val subkeyIdentifier: SubkeyIdentifier,
|
||||
private val callback: DecryptionCallback,
|
||||
) : CustomPublicKeyDataDecryptorFactory {
|
||||
|
||||
// luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument.
|
||||
private val factory: PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(null)
|
||||
|
||||
override fun createDataDecryptor(withIntegrityPacket: Boolean, encAlgorithm: Int, key: ByteArray?): PGPDataDecryptor {
|
||||
override fun createDataDecryptor(
|
||||
withIntegrityPacket: Boolean,
|
||||
encAlgorithm: Int,
|
||||
key: ByteArray?
|
||||
): PGPDataDecryptor {
|
||||
return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key)
|
||||
}
|
||||
|
||||
override fun createDataDecryptor(aeadEncDataPacket: AEADEncDataPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor {
|
||||
override fun createDataDecryptor(
|
||||
aeadEncDataPacket: AEADEncDataPacket?,
|
||||
sessionKey: PGPSessionKey?
|
||||
): PGPDataDecryptor {
|
||||
return factory.createDataDecryptor(aeadEncDataPacket, sessionKey)
|
||||
}
|
||||
|
||||
override fun createDataDecryptor(seipd: SymmetricEncIntegrityPacket?, sessionKey: PGPSessionKey?): PGPDataDecryptor {
|
||||
override fun createDataDecryptor(
|
||||
seipd: SymmetricEncIntegrityPacket?,
|
||||
sessionKey: PGPSessionKey?
|
||||
): PGPDataDecryptor {
|
||||
return factory.createDataDecryptor(seipd, sessionKey)
|
||||
}
|
||||
|
||||
override fun recoverSessionData(keyAlgorithm: Int, secKeyData: Array<out ByteArray>): ByteArray {
|
||||
override fun recoverSessionData(
|
||||
keyAlgorithm: Int,
|
||||
secKeyData: Array<out ByteArray>
|
||||
): ByteArray {
|
||||
return try {
|
||||
callback.decryptSessionKey(subkeyIdentifier.subkeyId, keyAlgorithm, secKeyData[0])
|
||||
} catch (e : HardwareSecurityException) {
|
||||
} catch (e: HardwareSecurityException) {
|
||||
throw PGPException("Hardware-backed decryption failed.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HardwareSecurityException : Exception()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,25 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import org.bouncycastle.openpgp.PGPEncryptedData
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.pgpainless.exception.ModificationDetectionException
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class IntegrityProtectedInputStream(
|
||||
private val inputStream: InputStream,
|
||||
private val encryptedData: PGPEncryptedData,
|
||||
private val options: ConsumerOptions
|
||||
private val inputStream: InputStream,
|
||||
private val encryptedData: PGPEncryptedData,
|
||||
private val options: ConsumerOptions
|
||||
) : InputStream() {
|
||||
private var closed: Boolean = false
|
||||
|
||||
override fun read() = inputStream.read()
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int) = inputStream.read(b, off, len)
|
||||
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
|
||||
|
@ -29,7 +31,7 @@ class IntegrityProtectedInputStream(
|
|||
try {
|
||||
if (!encryptedData.verify()) throw ModificationDetectionException()
|
||||
LOGGER.debug("Integrity Protection check passed.")
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
throw IOException("Data appears to not be integrity protected.", e)
|
||||
}
|
||||
}
|
||||
|
@ -39,4 +41,4 @@ class IntegrityProtectedInputStream(
|
|||
@JvmStatic
|
||||
val LOGGER: Logger = LoggerFactory.getLogger(IntegrityProtectedInputStream::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.pgpainless.implementation.ImplementationFactory
|
||||
import org.pgpainless.util.ArmorUtils
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase protected.
|
||||
* Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase
|
||||
* protected.
|
||||
*/
|
||||
class MessageInspector {
|
||||
|
||||
|
@ -23,9 +24,10 @@ class MessageInspector {
|
|||
* @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures
|
||||
*/
|
||||
data class EncryptionInfo(
|
||||
val keyIds: List<Long>,
|
||||
val isPassphraseEncrypted: Boolean,
|
||||
val isSignedOnly: Boolean) {
|
||||
val keyIds: List<Long>,
|
||||
val isPassphraseEncrypted: Boolean,
|
||||
val isSignedOnly: Boolean
|
||||
) {
|
||||
|
||||
val isEncrypted: Boolean
|
||||
get() = isPassphraseEncrypted || keyIds.isNotEmpty()
|
||||
|
@ -34,25 +36,26 @@ class MessageInspector {
|
|||
companion object {
|
||||
|
||||
/**
|
||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
|
||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used
|
||||
* to encrypt it.
|
||||
*
|
||||
* @param message OpenPGP message
|
||||
* @return encryption info
|
||||
*
|
||||
* @throws PGPException in case the message is broken
|
||||
* @throws IOException in case of an IO error
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(PGPException::class, IOException::class)
|
||||
fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = determineEncryptionInfoForMessage(message.byteInputStream())
|
||||
fun determineEncryptionInfoForMessage(message: String): EncryptionInfo =
|
||||
determineEncryptionInfoForMessage(message.byteInputStream())
|
||||
|
||||
/**
|
||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used to encrypt it.
|
||||
* Note: This method does not rewind the passed in Stream, so you might need to take care of that yourselves.
|
||||
* Parses parts of the provided OpenPGP message in order to determine which keys were used
|
||||
* to encrypt it. Note: This method does not rewind the passed in Stream, so you might need
|
||||
* to take care of that yourselves.
|
||||
*
|
||||
* @param inputStream openpgp message
|
||||
* @return encryption information
|
||||
*
|
||||
* @throws IOException in case of an IO error
|
||||
* @throws PGPException if the message is broken
|
||||
*/
|
||||
|
@ -70,13 +73,12 @@ class MessageInspector {
|
|||
var n: Any?
|
||||
while (objectFactory.nextObject().also { n = it } != null) {
|
||||
when (val next = n!!) {
|
||||
|
||||
is PGPOnePassSignatureList -> {
|
||||
if (!next.isEmpty) {
|
||||
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = true)
|
||||
return EncryptionInfo(
|
||||
listOf(), isPassphraseEncrypted = false, isSignedOnly = true)
|
||||
}
|
||||
}
|
||||
|
||||
is PGPEncryptedDataList -> {
|
||||
var isPassphraseEncrypted = false
|
||||
val keyIds = mutableListOf<Long>()
|
||||
|
@ -90,13 +92,12 @@ class MessageInspector {
|
|||
// Data is encrypted, we cannot go deeper
|
||||
return EncryptionInfo(keyIds, isPassphraseEncrypted, false)
|
||||
}
|
||||
|
||||
is PGPCompressedData -> {
|
||||
objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(
|
||||
PGPUtil.getDecoderStream(next.dataStream))
|
||||
objectFactory =
|
||||
ImplementationFactory.getInstance()
|
||||
.getPGPObjectFactory(PGPUtil.getDecoderStream(next.dataStream))
|
||||
continue
|
||||
}
|
||||
|
||||
is PGPLiteralData -> {
|
||||
break
|
||||
}
|
||||
|
@ -105,4 +106,4 @@ class MessageInspector {
|
|||
return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.util.*
|
||||
import javax.annotation.Nonnull
|
||||
import org.bouncycastle.extensions.matches
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPLiteralData
|
||||
|
@ -15,116 +17,118 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
|||
import org.pgpainless.key.OpenPgpFingerprint
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.util.SessionKey
|
||||
import java.util.*
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
/**
|
||||
* View for extracting metadata about a [Message].
|
||||
*/
|
||||
class MessageMetadata(
|
||||
val message: Message
|
||||
) {
|
||||
/** View for extracting metadata about a [Message]. */
|
||||
class MessageMetadata(val message: Message) {
|
||||
|
||||
// ################################################################################################################
|
||||
// ### Encryption ###
|
||||
// ### Encryption
|
||||
// ###
|
||||
// ################################################################################################################
|
||||
|
||||
/**
|
||||
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is unencrypted.
|
||||
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
|
||||
* unencrypted.
|
||||
*/
|
||||
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
||||
get() = encryptionAlgorithms.let {
|
||||
if (it.hasNext()) it.next() else null
|
||||
}
|
||||
get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
|
||||
|
||||
/**
|
||||
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message.
|
||||
* The first item returned by the iterator is the algorithm of the outermost encrypted data packet, the next item
|
||||
* that of the next nested encrypted data packet and so on.
|
||||
* The iterator might also be empty, in case of an unencrypted message.
|
||||
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
|
||||
* returned by the iterator is the algorithm of the outermost encrypted data packet, the next
|
||||
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
||||
* empty, in case of an unencrypted message.
|
||||
*/
|
||||
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
||||
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
||||
|
||||
val isEncrypted: Boolean
|
||||
get() = if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
||||
get() =
|
||||
if (encryptionAlgorithm == null) false
|
||||
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
||||
|
||||
fun isEncryptedFor(keys: PGPKeyRing): Boolean {
|
||||
return encryptionLayers.asSequence().any {
|
||||
it.recipients.any { keyId ->
|
||||
keys.getPublicKey(keyId) != null
|
||||
}
|
||||
it.recipients.any { keyId -> keys.getPublicKey(keyId) != null }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [SessionKey] of the outermost encrypted data packet.
|
||||
* If the message was unencrypted, this method returns `null`.
|
||||
* [SessionKey] of the outermost encrypted data packet. If the message was unencrypted, this
|
||||
* method returns `null`.
|
||||
*/
|
||||
val sessionKey: SessionKey?
|
||||
get() = sessionKeys.asSequence().firstOrNull()
|
||||
|
||||
/**
|
||||
* [Iterator] of each [SessionKey] for all encrypted data packets in the message.
|
||||
* The first item returned by the iterator is the session key of the outermost encrypted data packet,
|
||||
* the next item that of the next nested encrypted data packet and so on.
|
||||
* The iterator might also be empty, in case of an unencrypted message.
|
||||
* [Iterator] of each [SessionKey] for all encrypted data packets in the message. The first item
|
||||
* returned by the iterator is the session key of the outermost encrypted data packet, the next
|
||||
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
||||
* empty, in case of an unencrypted message.
|
||||
*/
|
||||
val sessionKeys: Iterator<SessionKey>
|
||||
get() = encryptionLayers.asSequence().mapNotNull { it.sessionKey }.iterator()
|
||||
|
||||
/**
|
||||
* [SubkeyIdentifier] of the decryption key that was used to decrypt the outermost encryption
|
||||
* layer.
|
||||
* If the message was unencrypted or was decrypted using a passphrase, this field might be `null`.
|
||||
* layer. If the message was unencrypted or was decrypted using a passphrase, this field might
|
||||
* be `null`.
|
||||
*/
|
||||
val decryptionKey: SubkeyIdentifier?
|
||||
get() = encryptionLayers.asSequence()
|
||||
.mapNotNull { it.decryptionKey }
|
||||
.firstOrNull()
|
||||
get() = encryptionLayers.asSequence().mapNotNull { it.decryptionKey }.firstOrNull()
|
||||
|
||||
/**
|
||||
* List containing all recipient keyIDs.
|
||||
*/
|
||||
/** List containing all recipient keyIDs. */
|
||||
val recipientKeyIds: List<Long>
|
||||
get() = encryptionLayers.asSequence()
|
||||
get() =
|
||||
encryptionLayers
|
||||
.asSequence()
|
||||
.map { it.recipients.toMutableList() }
|
||||
.reduce { all, keyIds -> all.addAll(keyIds); all }
|
||||
.reduce { all, keyIds ->
|
||||
all.addAll(keyIds)
|
||||
all
|
||||
}
|
||||
.toList()
|
||||
|
||||
val encryptionLayers: Iterator<EncryptedData>
|
||||
get() = object : LayerIterator<EncryptedData>(message) {
|
||||
override fun matches(layer: Packet) = layer is EncryptedData
|
||||
override fun getProperty(last: Layer) = last as EncryptedData
|
||||
}
|
||||
get() =
|
||||
object : LayerIterator<EncryptedData>(message) {
|
||||
override fun matches(layer: Packet) = layer is EncryptedData
|
||||
|
||||
override fun getProperty(last: Layer) = last as EncryptedData
|
||||
}
|
||||
|
||||
// ################################################################################################################
|
||||
// ### Compression ###
|
||||
// ### Compression
|
||||
// ###
|
||||
// ################################################################################################################
|
||||
|
||||
/**
|
||||
* [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message
|
||||
* does not contain any compressed data packets.
|
||||
* [CompressionAlgorithm] of the outermost compressed data packet, or null, if the message does
|
||||
* not contain any compressed data packets.
|
||||
*/
|
||||
val compressionAlgorithm: CompressionAlgorithm? = compressionAlgorithms.asSequence().firstOrNull()
|
||||
val compressionAlgorithm: CompressionAlgorithm? =
|
||||
compressionAlgorithms.asSequence().firstOrNull()
|
||||
|
||||
/**
|
||||
* [Iterator] of each [CompressionAlgorithm] encountered in the message.
|
||||
* The first item returned by the iterator is the algorithm of the outermost compressed data packet, the next
|
||||
* item that of the next nested compressed data packet and so on.
|
||||
* The iterator might also be empty, in case of a message without any compressed data packets.
|
||||
* [Iterator] of each [CompressionAlgorithm] encountered in the message. The first item returned
|
||||
* by the iterator is the algorithm of the outermost compressed data packet, the next item that
|
||||
* of the next nested compressed data packet and so on. The iterator might also be empty, in
|
||||
* case of a message without any compressed data packets.
|
||||
*/
|
||||
val compressionAlgorithms: Iterator<CompressionAlgorithm>
|
||||
get() = compressionLayers.asSequence().map { it.algorithm }.iterator()
|
||||
|
||||
val compressionLayers: Iterator<CompressedData>
|
||||
get() = object : LayerIterator<CompressedData>(message) {
|
||||
override fun matches(layer: Packet) = layer is CompressedData
|
||||
override fun getProperty(last: Layer) = last as CompressedData
|
||||
}
|
||||
get() =
|
||||
object : LayerIterator<CompressedData>(message) {
|
||||
override fun matches(layer: Packet) = layer is CompressedData
|
||||
|
||||
override fun getProperty(last: Layer) = last as CompressedData
|
||||
}
|
||||
|
||||
// ################################################################################################################
|
||||
// ### Signatures ###
|
||||
// ### Signatures
|
||||
// ###
|
||||
// ################################################################################################################
|
||||
|
||||
val isUsingCleartextSignatureFramework: Boolean
|
||||
|
@ -133,81 +137,87 @@ class MessageMetadata(
|
|||
val verifiedSignatures: List<SignatureVerification>
|
||||
get() = verifiedInlineSignatures.plus(verifiedDetachedSignatures)
|
||||
|
||||
/**
|
||||
* List of all rejected signatures.
|
||||
*/
|
||||
/** List of all rejected signatures. */
|
||||
val rejectedSignatures: List<SignatureVerification.Failure>
|
||||
get() = mutableListOf<SignatureVerification.Failure>()
|
||||
get() =
|
||||
mutableListOf<SignatureVerification.Failure>()
|
||||
.plus(rejectedInlineSignatures)
|
||||
.plus(rejectedDetachedSignatures)
|
||||
.toList()
|
||||
|
||||
/**
|
||||
* List of all verified inline-signatures.
|
||||
* This list contains all acceptable, correct signatures that were part of the message itself.
|
||||
* List of all verified inline-signatures. This list contains all acceptable, correct signatures
|
||||
* that were part of the message itself.
|
||||
*/
|
||||
val verifiedInlineSignatures: List<SignatureVerification> = verifiedInlineSignaturesByLayer
|
||||
val verifiedInlineSignatures: List<SignatureVerification> =
|
||||
verifiedInlineSignaturesByLayer
|
||||
.asSequence()
|
||||
.map { it.toMutableList() }
|
||||
.reduce { acc, signatureVerifications -> acc.addAll(signatureVerifications); acc }
|
||||
.reduce { acc, signatureVerifications ->
|
||||
acc.addAll(signatureVerifications)
|
||||
acc
|
||||
}
|
||||
.toList()
|
||||
|
||||
/**
|
||||
* [Iterator] of each [List] of verified inline-signatures of the message, separated by layer.
|
||||
* Since signatures might occur in different layers within a message, this method can be used to gain more detailed
|
||||
* insights into what signatures were encountered at what layers of the message structure.
|
||||
* Each item of the [Iterator] represents a layer of the message and contains only signatures from
|
||||
* this layer.
|
||||
* An empty list means no (or no acceptable) signatures were encountered in that layer.
|
||||
* Since signatures might occur in different layers within a message, this method can be used to
|
||||
* gain more detailed insights into what signatures were encountered at what layers of the
|
||||
* message structure. Each item of the [Iterator] represents a layer of the message and contains
|
||||
* only signatures from this layer. An empty list means no (or no acceptable) signatures were
|
||||
* encountered in that layer.
|
||||
*/
|
||||
val verifiedInlineSignaturesByLayer: Iterator<List<SignatureVerification>>
|
||||
get() = object : LayerIterator<List<SignatureVerification>>(message) {
|
||||
override fun matches(layer: Packet) = layer is Layer
|
||||
get() =
|
||||
object : LayerIterator<List<SignatureVerification>>(message) {
|
||||
override fun matches(layer: Packet) = layer is Layer
|
||||
|
||||
override fun getProperty(last: Layer): List<SignatureVerification> {
|
||||
return listOf<SignatureVerification>()
|
||||
override fun getProperty(last: Layer): List<SignatureVerification> {
|
||||
return listOf<SignatureVerification>()
|
||||
.plus(last.verifiedOnePassSignatures)
|
||||
.plus(last.verifiedPrependedSignatures)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all rejected inline-signatures of the message.
|
||||
*/
|
||||
val rejectedInlineSignatures: List<SignatureVerification.Failure> = rejectedInlineSignaturesByLayer
|
||||
/** List of all rejected inline-signatures of the message. */
|
||||
val rejectedInlineSignatures: List<SignatureVerification.Failure> =
|
||||
rejectedInlineSignaturesByLayer
|
||||
.asSequence()
|
||||
.map { it.toMutableList() }
|
||||
.reduce { acc, failures -> acc.addAll(failures); acc}
|
||||
.reduce { acc, failures ->
|
||||
acc.addAll(failures)
|
||||
acc
|
||||
}
|
||||
.toList()
|
||||
|
||||
/**
|
||||
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected inline-signatures
|
||||
* of the message, but organized by layer.
|
||||
* Similar to [verifiedInlineSignaturesByLayer], this field contains all rejected
|
||||
* inline-signatures of the message, but organized by layer.
|
||||
*/
|
||||
val rejectedInlineSignaturesByLayer: Iterator<List<SignatureVerification.Failure>>
|
||||
get() = object : LayerIterator<List<SignatureVerification.Failure>>(message) {
|
||||
override fun matches(layer: Packet) = layer is Layer
|
||||
get() =
|
||||
object : LayerIterator<List<SignatureVerification.Failure>>(message) {
|
||||
override fun matches(layer: Packet) = layer is Layer
|
||||
|
||||
override fun getProperty(last: Layer): List<SignatureVerification.Failure> =
|
||||
override fun getProperty(last: Layer): List<SignatureVerification.Failure> =
|
||||
mutableListOf<SignatureVerification.Failure>()
|
||||
.plus(last.rejectedOnePassSignatures)
|
||||
.plus(last.rejectedPrependedSignatures)
|
||||
}
|
||||
.plus(last.rejectedOnePassSignatures)
|
||||
.plus(last.rejectedPrependedSignatures)
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all verified detached signatures.
|
||||
* This list contains all acceptable, correct detached signatures.
|
||||
* List of all verified detached signatures. This list contains all acceptable, correct detached
|
||||
* signatures.
|
||||
*/
|
||||
val verifiedDetachedSignatures: List<SignatureVerification> = message.verifiedDetachedSignatures
|
||||
|
||||
/**
|
||||
* List of all rejected detached signatures.
|
||||
*/
|
||||
val rejectedDetachedSignatures: List<SignatureVerification.Failure> = message.rejectedDetachedSignatures
|
||||
/** List of all rejected detached signatures. */
|
||||
val rejectedDetachedSignatures: List<SignatureVerification.Failure> =
|
||||
message.rejectedDetachedSignatures
|
||||
|
||||
/**
|
||||
* True, if the message contains any (verified or rejected) signature, false if no signatures are present.
|
||||
* True, if the message contains any (verified or rejected) signature, false if no signatures
|
||||
* are present.
|
||||
*/
|
||||
val hasSignature: Boolean
|
||||
get() = isVerifiedSigned() || hasRejectedSignatures()
|
||||
|
@ -217,110 +227,131 @@ class MessageMetadata(
|
|||
fun hasRejectedSignatures(): Boolean = rejectedSignatures.isNotEmpty()
|
||||
|
||||
/**
|
||||
* Return true, if the message was signed by a certificate for which we can authenticate a binding to the given userId.
|
||||
* Return true, if the message was signed by a certificate for which we can authenticate a
|
||||
* binding to the given userId.
|
||||
*
|
||||
* @param userId userId
|
||||
* @param email if true, treat the user-id as an email address and match all userIDs containing this address
|
||||
* @param email if true, treat the user-id as an email address and match all userIDs containing
|
||||
* this address
|
||||
* @param certificateAuthority certificate authority
|
||||
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify as authenticated.
|
||||
* defaults to 120.
|
||||
* @param targetAmount targeted trust amount that needs to be reached by the binding to qualify
|
||||
* as authenticated. defaults to 120.
|
||||
* @return true, if we can authenticate a binding for a signing key with sufficient evidence
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun isAuthenticatablySignedBy(userId: String, email: Boolean, certificateAuthority: CertificateAuthority, targetAmount: Int = 120): Boolean {
|
||||
return verifiedSignatures.any { certificateAuthority
|
||||
.authenticateBinding(it.signingKey.fingerprint, userId, email, it.signature.creationTime, targetAmount)
|
||||
fun isAuthenticatablySignedBy(
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
certificateAuthority: CertificateAuthority,
|
||||
targetAmount: Int = 120
|
||||
): Boolean {
|
||||
return verifiedSignatures.any {
|
||||
certificateAuthority
|
||||
.authenticateBinding(
|
||||
it.signingKey.fingerprint,
|
||||
userId,
|
||||
email,
|
||||
it.signature.creationTime,
|
||||
targetAmount)
|
||||
.authenticated
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return rue, if the message was verifiable signed by a certificate that either has the given fingerprint
|
||||
* as primary key, or as the signing subkey.
|
||||
* Return rue, if the message was verifiable signed by a certificate that either has the given
|
||||
* fingerprint as primary key, or as the signing subkey.
|
||||
*
|
||||
* @param fingerprint fingerprint
|
||||
* @return true if message was signed by a cert identified by the given fingerprint
|
||||
*/
|
||||
fun isVerifiedSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||
verifiedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
verifiedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
|
||||
fun isVerifiedSignedBy(keys: PGPKeyRing) =
|
||||
verifiedSignatures.any { keys.matches(it.signingKey) }
|
||||
verifiedSignatures.any { keys.matches(it.signingKey) }
|
||||
|
||||
fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||
verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
|
||||
fun isVerifiedDetachedSignedBy(keys: PGPKeyRing) =
|
||||
verifiedDetachedSignatures.any { keys.matches(it.signingKey) }
|
||||
verifiedDetachedSignatures.any { keys.matches(it.signingKey) }
|
||||
|
||||
fun isVerifiedInlineSignedBy(fingerprint: OpenPgpFingerprint) =
|
||||
verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
verifiedInlineSignatures.any { it.signingKey.matches(fingerprint) }
|
||||
|
||||
fun isVerifiedInlineSignedBy(keys: PGPKeyRing) =
|
||||
verifiedInlineSignatures.any { keys.matches(it.signingKey) }
|
||||
verifiedInlineSignatures.any { keys.matches(it.signingKey) }
|
||||
|
||||
// ################################################################################################################
|
||||
// ### Literal Data ###
|
||||
// ### Literal Data
|
||||
// ###
|
||||
// ################################################################################################################
|
||||
|
||||
/**
|
||||
* Value of the literal data packet's filename field.
|
||||
* This value can be used to store a decrypted file under its original filename,
|
||||
* but since this field is not necessarily part of the signed data of a message, usage of this field is
|
||||
* discouraged.
|
||||
* Value of the literal data packet's filename field. This value can be used to store a
|
||||
* decrypted file under its original filename, but since this field is not necessarily part of
|
||||
* the signed data of a message, usage of this field is discouraged.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||
* Packet</a>
|
||||
*/
|
||||
val filename: String? = findLiteralData()?.fileName
|
||||
|
||||
/**
|
||||
* True, if the sender signals an increased degree of confidentiality by setting the filename of the literal
|
||||
* data packet to a special value that indicates that the data is intended for your eyes only.
|
||||
* True, if the sender signals an increased degree of confidentiality by setting the filename of
|
||||
* the literal data packet to a special value that indicates that the data is intended for your
|
||||
* eyes only.
|
||||
*/
|
||||
@Deprecated("Reliance on this signaling mechanism is discouraged.")
|
||||
val isForYourEyesOnly: Boolean = PGPLiteralData.CONSOLE == filename
|
||||
|
||||
/**
|
||||
* Value of the literal data packets modification date field.
|
||||
* This value can be used to restore the modification date of a decrypted file,
|
||||
* but since this field is not necessarily part of the signed data, its use is discouraged.
|
||||
* Value of the literal data packets modification date field. This value can be used to restore
|
||||
* the modification date of a decrypted file, but since this field is not necessarily part of
|
||||
* the signed data, its use is discouraged.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||
* Packet</a>
|
||||
*/
|
||||
val modificationDate: Date? = findLiteralData()?.modificationDate
|
||||
|
||||
/**
|
||||
* Value of the format field of the literal data packet.
|
||||
* This value indicates what format (text, binary data, ...) the data has.
|
||||
* Since this field is not necessarily part of the signed data of a message, its usage is discouraged.
|
||||
* Value of the format field of the literal data packet. This value indicates what format (text,
|
||||
* binary data, ...) the data has. Since this field is not necessarily part of the signed data
|
||||
* of a message, its usage is discouraged.
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-5.9">RFC4880 §5.9. Literal Data
|
||||
* Packet</a>
|
||||
*/
|
||||
val literalDataEncoding: StreamEncoding? = findLiteralData()?.format
|
||||
|
||||
/**
|
||||
* Find the [LiteralData] layer of an OpenPGP message.
|
||||
* This method might return null, for example for a cleartext signed message without OpenPGP packets.
|
||||
* Find the [LiteralData] layer of an OpenPGP message. This method might return null, for
|
||||
* example for a cleartext signed message without OpenPGP packets.
|
||||
*
|
||||
* @return literal data
|
||||
*/
|
||||
private fun findLiteralData(): LiteralData? {
|
||||
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed message,
|
||||
// If the message is a non-OpenPGP message with a detached signature, or a Cleartext Signed
|
||||
// message,
|
||||
// we might not have a Literal Data packet.
|
||||
var nested = message.child ?: return null
|
||||
|
||||
while (nested.hasNestedChild()) {
|
||||
val layer = nested as Layer
|
||||
nested = checkNotNull(layer.child) {
|
||||
// Otherwise, we MUST find a Literal Data packet, or else the message is malformed
|
||||
"Malformed OpenPGP message. Cannot find Literal Data Packet"
|
||||
}
|
||||
nested =
|
||||
checkNotNull(layer.child) {
|
||||
// Otherwise, we MUST find a Literal Data packet, or else the message is
|
||||
// malformed
|
||||
"Malformed OpenPGP message. Cannot find Literal Data Packet"
|
||||
}
|
||||
}
|
||||
return nested as LiteralData
|
||||
}
|
||||
|
||||
// ################################################################################################################
|
||||
// ### Message Structure ###
|
||||
// ### Message Structure
|
||||
// ###
|
||||
// ################################################################################################################
|
||||
|
||||
interface Packet
|
||||
|
@ -329,13 +360,12 @@ class MessageMetadata(
|
|||
fun hasNestedChild(): Boolean
|
||||
}
|
||||
|
||||
abstract class Layer(
|
||||
val depth: Int
|
||||
) : Packet {
|
||||
abstract class Layer(val depth: Int) : Packet {
|
||||
|
||||
init {
|
||||
if (depth > MAX_LAYER_DEPTH) {
|
||||
throw MalformedOpenPgpMessageException("Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
|
||||
throw MalformedOpenPgpMessageException(
|
||||
"Maximum packet nesting depth ($MAX_LAYER_DEPTH) exceeded.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,9 +377,8 @@ class MessageMetadata(
|
|||
val rejectedPrependedSignatures: List<SignatureVerification.Failure> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Nested child element of this layer.
|
||||
* Might be `null`, if this layer does not have a child element
|
||||
* (e.g. if this is a [LiteralData] packet).
|
||||
* Nested child element of this layer. Might be `null`, if this layer does not have a child
|
||||
* element (e.g. if this is a [LiteralData] packet).
|
||||
*/
|
||||
var child: Nested? = null
|
||||
|
||||
|
@ -386,8 +415,8 @@ class MessageMetadata(
|
|||
* Outermost OpenPGP Message structure.
|
||||
*
|
||||
* @param cleartextSigned whether the message is using the Cleartext Signature Framework
|
||||
*
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext Signature Framework</a>
|
||||
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-7">RFC4880 §7. Cleartext
|
||||
* Signature Framework</a>
|
||||
*/
|
||||
class Message(var cleartextSigned: Boolean = false) : Layer(0) {
|
||||
fun setCleartextSigned() = apply { cleartextSigned = true }
|
||||
|
@ -397,14 +426,14 @@ class MessageMetadata(
|
|||
* Literal Data Packet.
|
||||
*
|
||||
* @param fileName value of the filename field. An empty String represents no filename.
|
||||
* @param modificationDate value of the modification date field. The special value `Date(0)` indicates no
|
||||
* modification date.
|
||||
* @param modificationDate value of the modification date field. The special value `Date(0)`
|
||||
* indicates no modification date.
|
||||
* @param format value of the format field.
|
||||
*/
|
||||
class LiteralData(
|
||||
val fileName: String = "",
|
||||
val modificationDate: Date = Date(0L),
|
||||
val format: StreamEncoding = StreamEncoding.BINARY
|
||||
val fileName: String = "",
|
||||
val modificationDate: Date = Date(0L),
|
||||
val format: StreamEncoding = StreamEncoding.BINARY
|
||||
) : Nested {
|
||||
|
||||
// A literal data packet MUST NOT have a child element, as its content is the plaintext
|
||||
|
@ -417,9 +446,7 @@ class MessageMetadata(
|
|||
* @param algorithm [CompressionAlgorithm] used to compress the packet.
|
||||
* @param depth nesting depth at which this packet was encountered.
|
||||
*/
|
||||
class CompressedData(
|
||||
val algorithm: CompressionAlgorithm,
|
||||
depth: Int) : Layer(depth), Nested {
|
||||
class CompressedData(val algorithm: CompressionAlgorithm, depth: Int) : Layer(depth), Nested {
|
||||
|
||||
// A compressed data packet MUST have a child element
|
||||
override fun hasNestedChild() = true
|
||||
|
@ -431,38 +458,30 @@ class MessageMetadata(
|
|||
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
||||
* @param depth nesting depth at which this packet was encountered.
|
||||
*/
|
||||
class EncryptedData(
|
||||
val algorithm: SymmetricKeyAlgorithm,
|
||||
depth: Int
|
||||
) : Layer(depth), Nested {
|
||||
class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested {
|
||||
|
||||
/**
|
||||
* [SessionKey] used to decrypt the packet.
|
||||
*/
|
||||
/** [SessionKey] used to decrypt the packet. */
|
||||
var sessionKey: SessionKey? = null
|
||||
|
||||
/**
|
||||
* List of all recipient key ids to which the packet was encrypted for.
|
||||
*/
|
||||
/** List of all recipient key ids to which the packet was encrypted for. */
|
||||
val recipients: List<Long> = mutableListOf()
|
||||
|
||||
fun addRecipients(keyIds: List<Long>) = apply {
|
||||
(recipients as MutableList).addAll(keyIds)
|
||||
}
|
||||
fun addRecipients(keyIds: List<Long>) = apply { (recipients as MutableList).addAll(keyIds) }
|
||||
|
||||
/**
|
||||
* Identifier of the subkey that was used to decrypt the packet (in case of a public key encrypted packet).
|
||||
* Identifier of the subkey that was used to decrypt the packet (in case of a public key
|
||||
* encrypted packet).
|
||||
*/
|
||||
var decryptionKey: SubkeyIdentifier? = null
|
||||
|
||||
// An encrypted data packet MUST have a child element
|
||||
override fun hasNestedChild() = true
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the results of
|
||||
* a transformation ([getProperty]) on those packets that match ([matches]) a given criterion.
|
||||
* Iterator that iterates the packet structure from outermost to innermost packet, emitting the
|
||||
* results of a transformation ([getProperty]) on those packets that match ([matches]) a given
|
||||
* criterion.
|
||||
*
|
||||
* @param message outermost structure object
|
||||
*/
|
||||
|
@ -519,6 +538,7 @@ class MessageMetadata(
|
|||
}
|
||||
|
||||
abstract fun matches(layer: Packet): Boolean
|
||||
|
||||
abstract fun getProperty(last: Layer): O
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,18 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
/**
|
||||
* Strategy defining how missing secret key passphrases are handled.
|
||||
*/
|
||||
/** Strategy defining how missing secret key passphrases are handled. */
|
||||
enum class MissingKeyPassphraseStrategy {
|
||||
/**
|
||||
* Try to interactively obtain key passphrases one-by-one via callbacks,
|
||||
* eg [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
|
||||
* Try to interactively obtain key passphrases one-by-one via callbacks, eg
|
||||
* [org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider].
|
||||
*/
|
||||
INTERACTIVE,
|
||||
|
||||
/**
|
||||
* Do not try to obtain passphrases interactively and instead throw a
|
||||
* [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing passphrases.
|
||||
* [org.pgpainless.exception.MissingPassphraseException] listing all keys with missing
|
||||
* passphrases.
|
||||
*/
|
||||
THROW_EXCEPTION
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,20 +9,19 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing
|
|||
fun interface MissingPublicKeyCallback {
|
||||
|
||||
/**
|
||||
* This method gets called if we encounter a signature made by a key which was not provided for signature verification.
|
||||
* If you cannot provide the requested key, it is safe to return null here.
|
||||
* PGPainless will then continue verification with the next signature.
|
||||
* This method gets called if we encounter a signature made by a key which was not provided for
|
||||
* signature verification. If you cannot provide the requested key, it is safe to return null
|
||||
* here. PGPainless will then continue verification with the next signature.
|
||||
*
|
||||
* Note: The key-id might belong to a subkey, so be aware that when looking up the [PGPPublicKeyRing],
|
||||
* you may not only search for the key-id on the key rings primary key!
|
||||
* Note: The key-id might belong to a subkey, so be aware that when looking up the
|
||||
* [PGPPublicKeyRing], you may not only search for the key-id on the key rings primary key!
|
||||
*
|
||||
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately one-pass-signatures
|
||||
* only contain the key id.
|
||||
* It would be super cool to provide the OpenPgp fingerprint here, but unfortunately
|
||||
* one-pass-signatures only contain the key id.
|
||||
*
|
||||
* @param keyId ID of the missing signing (sub)key
|
||||
* @return keyring containing the key or null
|
||||
*
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.4">RFC</a>
|
||||
*/
|
||||
fun onMissingPublicKeyEncountered(keyId: Long): PGPPublicKeyRing?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import openpgp.openPgpKeyId
|
||||
import org.bouncycastle.bcpg.BCPGInputStream
|
||||
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
|
||||
|
@ -34,16 +37,14 @@ import org.pgpainless.signature.consumer.SignatureValidator
|
|||
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||
import org.pgpainless.util.SessionKey
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
class OpenPgpMessageInputStream(
|
||||
type: Type,
|
||||
inputStream: InputStream,
|
||||
private val options: ConsumerOptions,
|
||||
private val layerMetadata: Layer,
|
||||
private val policy: Policy) : DecryptionStream() {
|
||||
type: Type,
|
||||
inputStream: InputStream,
|
||||
private val options: ConsumerOptions,
|
||||
private val layerMetadata: Layer,
|
||||
private val policy: Policy
|
||||
) : DecryptionStream() {
|
||||
|
||||
private val signatures: Signatures = Signatures(options)
|
||||
private var packetInputStream: TeeBCPGInputStream? = null
|
||||
|
@ -58,19 +59,20 @@ class OpenPgpMessageInputStream(
|
|||
signatures.addDetachedSignatures(options.getDetachedSignatures())
|
||||
}
|
||||
|
||||
when(type) {
|
||||
when (type) {
|
||||
Type.standard -> {
|
||||
|
||||
// tee out packet bytes for signature verification
|
||||
packetInputStream = TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures)
|
||||
packetInputStream =
|
||||
TeeBCPGInputStream(BCPGInputStream.wrap(inputStream), signatures)
|
||||
|
||||
// *omnomnom*
|
||||
consumePackets()
|
||||
}
|
||||
|
||||
Type.cleartext_signed -> {
|
||||
val multiPassStrategy = options.getMultiPassStrategy()
|
||||
val detachedSignatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
||||
val detachedSignatures =
|
||||
ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(
|
||||
inputStream, multiPassStrategy.messageOutputStream)
|
||||
|
||||
for (signature in detachedSignatures) {
|
||||
|
@ -78,9 +80,9 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
options.isForceNonOpenPgpData()
|
||||
nestedInputStream = TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
||||
nestedInputStream =
|
||||
TeeInputStream(multiPassStrategy.messageInputStream, this.signatures)
|
||||
}
|
||||
|
||||
Type.non_openpgp -> {
|
||||
packetInputStream = null
|
||||
nestedInputStream = TeeInputStream(inputStream, this.signatures)
|
||||
|
@ -89,11 +91,17 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
enum class Type {
|
||||
standard, cleartext_signed, non_openpgp
|
||||
standard,
|
||||
cleartext_signed,
|
||||
non_openpgp
|
||||
}
|
||||
|
||||
constructor(inputStream: InputStream, options: ConsumerOptions, metadata: Layer, policy: Policy):
|
||||
this(Type.standard, inputStream, options, metadata, policy)
|
||||
constructor(
|
||||
inputStream: InputStream,
|
||||
options: ConsumerOptions,
|
||||
metadata: Layer,
|
||||
policy: Policy
|
||||
) : this(Type.standard, inputStream, options, metadata, policy)
|
||||
|
||||
private fun consumePackets() {
|
||||
val pIn = packetInputStream ?: return
|
||||
|
@ -102,50 +110,53 @@ class OpenPgpMessageInputStream(
|
|||
|
||||
// Comsume packets, potentially stepping into nested layers
|
||||
layer@ while (run {
|
||||
packet = pIn.nextPacketTag()
|
||||
packet
|
||||
} != null) {
|
||||
packet = pIn.nextPacketTag()
|
||||
packet
|
||||
} != null) {
|
||||
|
||||
signatures.nextPacket(packet!!)
|
||||
// Consume packets in a layer
|
||||
when(packet) {
|
||||
|
||||
when (packet) {
|
||||
OpenPgpPacket.LIT -> {
|
||||
processLiteralData()
|
||||
break@layer // nest down
|
||||
}
|
||||
|
||||
OpenPgpPacket.COMP -> {
|
||||
processCompressedData()
|
||||
break@layer // nest down
|
||||
}
|
||||
|
||||
OpenPgpPacket.OPS -> {
|
||||
processOnePassSignature() // OPS is on the same layer, no nest down
|
||||
}
|
||||
|
||||
OpenPgpPacket.SIG -> {
|
||||
processSignature() // SIG is on the same layer, no nest down
|
||||
}
|
||||
|
||||
OpenPgpPacket.PKESK, OpenPgpPacket.SKESK, OpenPgpPacket.SED, OpenPgpPacket.SEIPD -> {
|
||||
OpenPgpPacket.PKESK,
|
||||
OpenPgpPacket.SKESK,
|
||||
OpenPgpPacket.SED,
|
||||
OpenPgpPacket.SEIPD -> {
|
||||
if (processEncryptedData()) {
|
||||
break@layer
|
||||
}
|
||||
throw MissingDecryptionMethodException("No working decryption method found.")
|
||||
}
|
||||
|
||||
OpenPgpPacket.MARKER -> {
|
||||
LOGGER.debug("Skipping Marker Packet")
|
||||
pIn.readMarker()
|
||||
}
|
||||
|
||||
OpenPgpPacket.SK, OpenPgpPacket.PK, OpenPgpPacket.SSK, OpenPgpPacket.PSK, OpenPgpPacket.TRUST, OpenPgpPacket.UID, OpenPgpPacket.UATTR ->
|
||||
OpenPgpPacket.SK,
|
||||
OpenPgpPacket.PK,
|
||||
OpenPgpPacket.SSK,
|
||||
OpenPgpPacket.PSK,
|
||||
OpenPgpPacket.TRUST,
|
||||
OpenPgpPacket.UID,
|
||||
OpenPgpPacket.UATTR ->
|
||||
throw MalformedOpenPgpMessageException("Illegal Packet in Stream: $packet")
|
||||
|
||||
OpenPgpPacket.EXP_1, OpenPgpPacket.EXP_2, OpenPgpPacket.EXP_3, OpenPgpPacket.EXP_4 ->
|
||||
OpenPgpPacket.EXP_1,
|
||||
OpenPgpPacket.EXP_2,
|
||||
OpenPgpPacket.EXP_3,
|
||||
OpenPgpPacket.EXP_4 ->
|
||||
throw MalformedOpenPgpMessageException("Unsupported Packet in Stream: $packet")
|
||||
|
||||
else ->
|
||||
throw MalformedOpenPgpMessageException("Unexpected Packet in Stream: $packet")
|
||||
}
|
||||
|
@ -158,8 +169,10 @@ class OpenPgpMessageInputStream(
|
|||
val literalData = packetInputStream!!.readLiteralData()
|
||||
|
||||
// Extract Metadata
|
||||
layerMetadata.child = LiteralData(
|
||||
literalData.fileName, literalData.modificationTime,
|
||||
layerMetadata.child =
|
||||
LiteralData(
|
||||
literalData.fileName,
|
||||
literalData.modificationTime,
|
||||
StreamEncoding.requireFromCode(literalData.format))
|
||||
|
||||
nestedInputStream = literalData.inputStream
|
||||
|
@ -171,18 +184,22 @@ class OpenPgpMessageInputStream(
|
|||
val compressedData = packetInputStream!!.readCompressedData()
|
||||
|
||||
// Extract Metadata
|
||||
val compressionLayer = CompressedData(
|
||||
val compressionLayer =
|
||||
CompressedData(
|
||||
CompressionAlgorithm.requireFromId(compressedData.algorithm),
|
||||
layerMetadata.depth + 1)
|
||||
|
||||
LOGGER.debug("Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.")
|
||||
nestedInputStream = OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy)
|
||||
LOGGER.debug(
|
||||
"Compressed Data Packet (${compressionLayer.algorithm}) at depth ${layerMetadata.depth} encountered.")
|
||||
nestedInputStream =
|
||||
OpenPgpMessageInputStream(compressedData.dataStream, options, compressionLayer, policy)
|
||||
}
|
||||
|
||||
private fun processOnePassSignature() {
|
||||
syntaxVerifier.next(InputSymbol.ONE_PASS_SIGNATURE)
|
||||
val ops = packetInputStream!!.readOnePassSignature()
|
||||
LOGGER.debug("One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
LOGGER.debug(
|
||||
"One-Pass-Signature Packet by key ${ops.keyID.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
signatures.addOnePassSignature(ops)
|
||||
}
|
||||
|
||||
|
@ -190,26 +207,33 @@ class OpenPgpMessageInputStream(
|
|||
// true if signature corresponds to OPS
|
||||
val isSigForOps = syntaxVerifier.peekStack() == StackSymbol.OPS
|
||||
syntaxVerifier.next(InputSymbol.SIGNATURE)
|
||||
val signature = try {
|
||||
packetInputStream!!.readSignature()
|
||||
} catch (e : UnsupportedPacketVersionException) {
|
||||
LOGGER.debug("Unsupported Signature at depth ${layerMetadata.depth} encountered.", e)
|
||||
return
|
||||
}
|
||||
val signature =
|
||||
try {
|
||||
packetInputStream!!.readSignature()
|
||||
} catch (e: UnsupportedPacketVersionException) {
|
||||
LOGGER.debug(
|
||||
"Unsupported Signature at depth ${layerMetadata.depth} encountered.", e)
|
||||
return
|
||||
}
|
||||
|
||||
val keyId = signature.issuerKeyId
|
||||
if (isSigForOps) {
|
||||
LOGGER.debug("Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
signatures.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are dealt with
|
||||
LOGGER.debug(
|
||||
"Signature Packet corresponding to One-Pass-Signature by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
signatures
|
||||
.leaveNesting() // TODO: Only leave nesting if all OPSs of the nesting layer are
|
||||
// dealt with
|
||||
signatures.addCorrespondingOnePassSignature(signature, layerMetadata, policy)
|
||||
} else {
|
||||
LOGGER.debug("Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
LOGGER.debug(
|
||||
"Prepended Signature Packet by key ${keyId.openPgpKeyId()} at depth ${layerMetadata.depth} encountered.")
|
||||
signatures.addPrependedSignature(signature)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processEncryptedData(): Boolean {
|
||||
LOGGER.debug("Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
|
||||
LOGGER.debug(
|
||||
"Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
|
||||
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
|
||||
val encDataList = packetInputStream!!.readEncryptedDataList()
|
||||
if (!encDataList.isIntegrityProtected) {
|
||||
|
@ -220,22 +244,25 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
val esks = SortedESKs(encDataList)
|
||||
LOGGER.debug("Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
|
||||
LOGGER.debug(
|
||||
"Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
|
||||
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
|
||||
" have an anonymous recipient.")
|
||||
|
||||
// try custom decryptor factories
|
||||
for ((key, decryptorFactory) in options.getCustomDecryptorFactories()) {
|
||||
LOGGER.debug("Attempt decryption with custom decryptor factory with key $key.")
|
||||
esks.pkesks.filter {
|
||||
// find matching PKESK
|
||||
it.keyID == key.subkeyId
|
||||
}.forEach {
|
||||
// attempt decryption
|
||||
if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) {
|
||||
return true
|
||||
esks.pkesks
|
||||
.filter {
|
||||
// find matching PKESK
|
||||
it.keyID == key.subkeyId
|
||||
}
|
||||
.forEach {
|
||||
// attempt decryption
|
||||
if (decryptPKESKAndStream(esks, key, decryptorFactory, it)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try provided session key
|
||||
|
@ -244,20 +271,24 @@ class OpenPgpMessageInputStream(
|
|||
LOGGER.debug("Attempt decryption with provided session key.")
|
||||
throwIfUnacceptable(sk.algorithm)
|
||||
|
||||
val decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getSessionKeyDataDecryptorFactory(sk)
|
||||
val decryptorFactory =
|
||||
ImplementationFactory.getInstance().getSessionKeyDataDecryptorFactory(sk)
|
||||
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1)
|
||||
val skEncData = encDataList.extractSessionKeyEncryptedData()
|
||||
try {
|
||||
val decrypted = skEncData.getDataStream(decryptorFactory)
|
||||
layer.sessionKey = sk
|
||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, skEncData, options)
|
||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, layer, policy)
|
||||
val integrityProtected =
|
||||
IntegrityProtectedInputStream(decrypted, skEncData, options)
|
||||
nestedInputStream =
|
||||
OpenPgpMessageInputStream(integrityProtected, options, layer, policy)
|
||||
LOGGER.debug("Successfully decrypted data using provided session key")
|
||||
return true
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
// Session key mismatch?
|
||||
LOGGER.debug("Decryption using provided session key failed. Mismatched session key and message?", e)
|
||||
LOGGER.debug(
|
||||
"Decryption using provided session key failed. Mismatched session key and message?",
|
||||
e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,19 +298,21 @@ class OpenPgpMessageInputStream(
|
|||
LOGGER.debug("Attempt decryption with provided passphrase")
|
||||
val algorithm = SymmetricKeyAlgorithm.requireFromId(skesk.algorithm)
|
||||
if (!isAcceptable(algorithm)) {
|
||||
LOGGER.debug("Skipping SKESK with unacceptable encapsulation algorithm $algorithm")
|
||||
LOGGER.debug(
|
||||
"Skipping SKESK with unacceptable encapsulation algorithm $algorithm")
|
||||
continue
|
||||
}
|
||||
|
||||
val decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getPBEDataDecryptorFactory(passphrase)
|
||||
val decryptorFactory =
|
||||
ImplementationFactory.getInstance().getPBEDataDecryptorFactory(passphrase)
|
||||
if (decryptSKESKAndStream(esks, skesk, decryptorFactory)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val postponedDueToMissingPassphrase = mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
|
||||
val postponedDueToMissingPassphrase =
|
||||
mutableListOf<Pair<PGPSecretKey, PGPPublicKeyEncryptedData>>()
|
||||
|
||||
// try (known) secret keys
|
||||
esks.pkesks.forEach { pkesk ->
|
||||
|
@ -295,7 +328,8 @@ class OpenPgpMessageInputStream(
|
|||
LOGGER.debug("Attempt decryption using secret key $decryptionKeyId")
|
||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||
LOGGER.debug(
|
||||
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
||||
continue
|
||||
}
|
||||
|
@ -319,7 +353,8 @@ class OpenPgpMessageInputStream(
|
|||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||
|
||||
if (!protector.hasPassphraseFor(secretKey.keyID)) {
|
||||
LOGGER.debug("Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||
LOGGER.debug(
|
||||
"Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.")
|
||||
postponedDueToMissingPassphrase.add(secretKey to pkesk)
|
||||
continue
|
||||
}
|
||||
|
@ -331,15 +366,14 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
}
|
||||
|
||||
if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||
if (options.getMissingKeyPassphraseStrategy() ==
|
||||
MissingKeyPassphraseStrategy.THROW_EXCEPTION) {
|
||||
// Non-interactive mode: Throw an exception with all locked decryption keys
|
||||
postponedDueToMissingPassphrase.map {
|
||||
SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID)
|
||||
}.also {
|
||||
if (it.isNotEmpty())
|
||||
throw MissingPassphraseException(it.toSet())
|
||||
}
|
||||
} else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||
postponedDueToMissingPassphrase
|
||||
.map { SubkeyIdentifier(getDecryptionKey(it.first.keyID)!!, it.first.keyID) }
|
||||
.also { if (it.isNotEmpty()) throw MissingPassphraseException(it.toSet()) }
|
||||
} else if (options.getMissingKeyPassphraseStrategy() ==
|
||||
MissingKeyPassphraseStrategy.INTERACTIVE) {
|
||||
for ((secretKey, pkesk) in postponedDueToMissingPassphrase) {
|
||||
val keyId = secretKey.keyID
|
||||
val decryptionKeys = getDecryptionKey(pkesk)!!
|
||||
|
@ -348,7 +382,8 @@ class OpenPgpMessageInputStream(
|
|||
continue
|
||||
}
|
||||
|
||||
LOGGER.debug("Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.")
|
||||
LOGGER.debug(
|
||||
"Attempt decryption with key $decryptionKeyId while interactively requesting its passphrase.")
|
||||
val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue
|
||||
val privateKey = secretKey.unlock(protector)
|
||||
if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) {
|
||||
|
@ -364,29 +399,37 @@ class OpenPgpMessageInputStream(
|
|||
return false
|
||||
}
|
||||
|
||||
private fun decryptWithPrivateKey(esks: SortedESKs,
|
||||
privateKey: PGPPrivateKey,
|
||||
decryptionKeyId: SubkeyIdentifier,
|
||||
pkesk: PGPPublicKeyEncryptedData): Boolean {
|
||||
val decryptorFactory = ImplementationFactory.getInstance()
|
||||
.getPublicKeyDataDecryptorFactory(privateKey)
|
||||
private fun decryptWithPrivateKey(
|
||||
esks: SortedESKs,
|
||||
privateKey: PGPPrivateKey,
|
||||
decryptionKeyId: SubkeyIdentifier,
|
||||
pkesk: PGPPublicKeyEncryptedData
|
||||
): Boolean {
|
||||
val decryptorFactory =
|
||||
ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey)
|
||||
return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk)
|
||||
}
|
||||
|
||||
private fun hasUnsupportedS2KSpecifier(secretKey: PGPSecretKey, decryptionKeyId: SubkeyIdentifier): Boolean {
|
||||
private fun hasUnsupportedS2KSpecifier(
|
||||
secretKey: PGPSecretKey,
|
||||
decryptionKeyId: SubkeyIdentifier
|
||||
): Boolean {
|
||||
val s2k = secretKey.s2K
|
||||
if (s2k != null) {
|
||||
if (s2k.type in 100..110) {
|
||||
LOGGER.debug("Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
|
||||
LOGGER.debug(
|
||||
"Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun decryptSKESKAndStream(esks: SortedESKs,
|
||||
skesk: PGPPBEEncryptedData,
|
||||
decryptorFactory: PBEDataDecryptorFactory): Boolean {
|
||||
private fun decryptSKESKAndStream(
|
||||
esks: SortedESKs,
|
||||
skesk: PGPPBEEncryptedData,
|
||||
decryptorFactory: PBEDataDecryptorFactory
|
||||
): Boolean {
|
||||
try {
|
||||
val decrypted = skesk.getDataStream(decryptorFactory)
|
||||
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
|
||||
|
@ -396,38 +439,45 @@ class OpenPgpMessageInputStream(
|
|||
encryptedData.addRecipients(esks.pkesks.map { it.keyID })
|
||||
LOGGER.debug("Successfully decrypted data with passphrase")
|
||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, skesk, options)
|
||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||
nestedInputStream =
|
||||
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||
return true
|
||||
} catch (e : UnacceptableAlgorithmException) {
|
||||
} catch (e: UnacceptableAlgorithmException) {
|
||||
throw e
|
||||
} catch (e : PGPException) {
|
||||
LOGGER.debug("Decryption of encrypted data packet using password failed. Password mismatch?", e)
|
||||
} catch (e: PGPException) {
|
||||
LOGGER.debug(
|
||||
"Decryption of encrypted data packet using password failed. Password mismatch?", e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun decryptPKESKAndStream(esks: SortedESKs,
|
||||
decryptionKeyId: SubkeyIdentifier,
|
||||
decryptorFactory: PublicKeyDataDecryptorFactory,
|
||||
pkesk: PGPPublicKeyEncryptedData): Boolean {
|
||||
private fun decryptPKESKAndStream(
|
||||
esks: SortedESKs,
|
||||
decryptionKeyId: SubkeyIdentifier,
|
||||
decryptorFactory: PublicKeyDataDecryptorFactory,
|
||||
pkesk: PGPPublicKeyEncryptedData
|
||||
): Boolean {
|
||||
try {
|
||||
val decrypted = pkesk.getDataStream(decryptorFactory)
|
||||
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
|
||||
throwIfUnacceptable(sessionKey.algorithm)
|
||||
|
||||
val encryptedData = EncryptedData(
|
||||
SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
||||
val encryptedData =
|
||||
EncryptedData(
|
||||
SymmetricKeyAlgorithm.requireFromId(
|
||||
pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
||||
layerMetadata.depth + 1)
|
||||
encryptedData.decryptionKey = decryptionKeyId
|
||||
encryptedData.sessionKey = sessionKey
|
||||
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyID })
|
||||
LOGGER.debug("Successfully decrypted data with key $decryptionKeyId")
|
||||
val integrityProtected = IntegrityProtectedInputStream(decrypted, pkesk, options)
|
||||
nestedInputStream = OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||
nestedInputStream =
|
||||
OpenPgpMessageInputStream(integrityProtected, options, encryptedData, policy)
|
||||
return true
|
||||
} catch (e : UnacceptableAlgorithmException) {
|
||||
} catch (e: UnacceptableAlgorithmException) {
|
||||
throw e
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
LOGGER.debug("Decryption of encrypted data packet using secret key failed.", e)
|
||||
}
|
||||
return false
|
||||
|
@ -441,11 +491,12 @@ class OpenPgpMessageInputStream(
|
|||
return -1
|
||||
}
|
||||
|
||||
val r: Int = try {
|
||||
nestedInputStream!!.read()
|
||||
} catch (e: IOException) {
|
||||
-1
|
||||
}
|
||||
val r: Int =
|
||||
try {
|
||||
nestedInputStream!!.read()
|
||||
} catch (e: IOException) {
|
||||
-1
|
||||
}
|
||||
if (r != -1) {
|
||||
signatures.updateLiteral(r.toByte())
|
||||
} else {
|
||||
|
@ -532,34 +583,37 @@ class OpenPgpMessageInputStream(
|
|||
return MessageMetadata((layerMetadata as Message))
|
||||
}
|
||||
|
||||
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull {
|
||||
it.any {
|
||||
k -> k.keyID == keyId
|
||||
}.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any {
|
||||
k -> k.keyID == keyId
|
||||
})
|
||||
}
|
||||
|
||||
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = options.getDecryptionKeys().firstOrNull {
|
||||
it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
||||
when (pkesk.version) {
|
||||
3 -> pkesk.keyID == subkey.keyID
|
||||
else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
|
||||
}
|
||||
private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? =
|
||||
options.getDecryptionKeys().firstOrNull {
|
||||
it.any { k -> k.keyID == keyId }
|
||||
.and(PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k -> k.keyID == keyId })
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> =
|
||||
options.getDecryptionKeys().filter {
|
||||
it.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
||||
private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? =
|
||||
options.getDecryptionKeys().firstOrNull {
|
||||
it.getSecretKeyFor(pkesk) != null &&
|
||||
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
||||
when (pkesk.version) {
|
||||
3 -> pkesk.keyID == subkey.keyID
|
||||
else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPotentialDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
|
||||
private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List<PGPSecretKeyRing> =
|
||||
options.getDecryptionKeys().filter {
|
||||
it.getSecretKeyFor(pkesk) != null &&
|
||||
PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey ->
|
||||
when (pkesk.version) {
|
||||
3 -> pkesk.keyID == subkey.keyID
|
||||
else -> throw NotImplementedError("Version 6 PKESK not yet supported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findPotentialDecryptionKeys(
|
||||
pkesk: PGPPublicKeyEncryptedData
|
||||
): List<Pair<PGPSecretKeyRing, PGPSecretKey>> {
|
||||
val algorithm = pkesk.algorithm
|
||||
val candidates = mutableListOf<Pair<PGPSecretKeyRing, PGPSecretKey>>()
|
||||
options.getDecryptionKeys().forEach {
|
||||
|
@ -574,11 +628,12 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
private fun isAcceptable(algorithm: SymmetricKeyAlgorithm): Boolean =
|
||||
policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm)
|
||||
policy.symmetricKeyDecryptionAlgorithmPolicy.isAcceptable(algorithm)
|
||||
|
||||
private fun throwIfUnacceptable(algorithm: SymmetricKeyAlgorithm) {
|
||||
if (!isAcceptable(algorithm)) {
|
||||
throw UnacceptableAlgorithmException("Symmetric-Key algorithm $algorithm is not acceptable for message decryption.")
|
||||
throw UnacceptableAlgorithmException(
|
||||
"Symmetric-Key algorithm $algorithm is not acceptable for message decryption.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -610,9 +665,7 @@ class OpenPgpMessageInputStream(
|
|||
get() = skesks.plus(pkesks).plus(anonPkesks)
|
||||
}
|
||||
|
||||
private class Signatures(
|
||||
val options: ConsumerOptions
|
||||
) : OutputStream() {
|
||||
private class Signatures(val options: ConsumerOptions) : OutputStream() {
|
||||
val detachedSignatures = mutableListOf<SignatureCheck>()
|
||||
val prependedSignatures = mutableListOf<SignatureCheck>()
|
||||
val onePassSignatures = mutableListOf<OnePassSignatureCheck>()
|
||||
|
@ -636,8 +689,10 @@ class OpenPgpMessageInputStream(
|
|||
if (check != null) {
|
||||
detachedSignatures.add(check)
|
||||
} else {
|
||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
detachedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||
LOGGER.debug(
|
||||
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
detachedSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key.")))
|
||||
}
|
||||
}
|
||||
|
@ -648,10 +703,11 @@ class OpenPgpMessageInputStream(
|
|||
if (check != null) {
|
||||
prependedSignatures.add(check)
|
||||
} else {
|
||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
prependedSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key")
|
||||
))
|
||||
LOGGER.debug(
|
||||
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
prependedSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -680,7 +736,11 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
}
|
||||
|
||||
fun addCorrespondingOnePassSignature(signature: PGPSignature, layer: Layer, policy: Policy) {
|
||||
fun addCorrespondingOnePassSignature(
|
||||
signature: PGPSignature,
|
||||
layer: Layer,
|
||||
policy: Policy
|
||||
) {
|
||||
var found = false
|
||||
val keyId = signature.issuerKeyId
|
||||
for ((i, check) in onePassSignatures.withIndex().reversed()) {
|
||||
|
@ -694,27 +754,32 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
check.signature = signature
|
||||
val verification = SignatureVerification(signature,
|
||||
val verification =
|
||||
SignatureVerification(
|
||||
signature,
|
||||
SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID))
|
||||
|
||||
try {
|
||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(signature)
|
||||
SignatureValidator.signatureWasCreatedInBounds(
|
||||
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(signature)
|
||||
CertificateValidator.validateCertificateAndVerifyOnePassSignature(check, policy)
|
||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||
layer.addVerifiedOnePassSignature(verification)
|
||||
} catch (e: SignatureValidationException) {
|
||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||
layer.addRejectedOnePassSignature(SignatureVerification.Failure(verification, e))
|
||||
layer.addRejectedOnePassSignature(
|
||||
SignatureVerification.Failure(verification, e))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOGGER.debug("No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
inbandSignaturesWithMissingCert.add(SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key.")
|
||||
))
|
||||
LOGGER.debug(
|
||||
"No suitable certificate for verification of signature by key ${keyId.openPgpKeyId()} found.")
|
||||
inbandSignaturesWithMissingCert.add(
|
||||
SignatureVerification.Failure(
|
||||
signature, null, SignatureValidationException("Missing verification key.")))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,7 +802,9 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
if (options.getMissingCertificateCallback() != null) {
|
||||
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID)
|
||||
return options
|
||||
.getMissingCertificateCallback()!!
|
||||
.onMissingPublicKeyEncountered(signature.keyID)
|
||||
}
|
||||
return null // TODO: Missing cert for sig
|
||||
}
|
||||
|
@ -749,7 +816,9 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
|
||||
if (options.getMissingCertificateCallback() != null) {
|
||||
return options.getMissingCertificateCallback()!!.onMissingPublicKeyEncountered(signature.keyID)
|
||||
return options
|
||||
.getMissingCertificateCallback()!!
|
||||
.onMissingPublicKeyEncountered(signature.keyID)
|
||||
}
|
||||
return null // TODO: Missing cert for sig
|
||||
}
|
||||
|
@ -800,32 +869,42 @@ class OpenPgpMessageInputStream(
|
|||
|
||||
fun finish(layer: Layer, policy: Policy) {
|
||||
for (detached in detachedSignatures) {
|
||||
val verification = SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
||||
val verification =
|
||||
SignatureVerification(detached.signature, detached.signingKeyIdentifier)
|
||||
try {
|
||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(detached.signature)
|
||||
SignatureValidator.signatureWasCreatedInBounds(
|
||||
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(detached.signature)
|
||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||
detached.signature, KeyRingUtils.publicKeys(detached.signingKeyRing), policy)
|
||||
detached.signature,
|
||||
KeyRingUtils.publicKeys(detached.signingKeyRing),
|
||||
policy)
|
||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||
layer.addVerifiedDetachedSignature(verification)
|
||||
} catch (e : SignatureValidationException) {
|
||||
} catch (e: SignatureValidationException) {
|
||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||
layer.addRejectedDetachedSignature(SignatureVerification.Failure(verification, e))
|
||||
layer.addRejectedDetachedSignature(
|
||||
SignatureVerification.Failure(verification, e))
|
||||
}
|
||||
}
|
||||
|
||||
for (prepended in prependedSignatures) {
|
||||
val verification = SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
||||
val verification =
|
||||
SignatureVerification(prepended.signature, prepended.signingKeyIdentifier)
|
||||
try {
|
||||
SignatureValidator.signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(prepended.signature)
|
||||
SignatureValidator.signatureWasCreatedInBounds(
|
||||
options.getVerifyNotBefore(), options.getVerifyNotAfter())
|
||||
.verify(prepended.signature)
|
||||
CertificateValidator.validateCertificateAndVerifyInitializedSignature(
|
||||
prepended.signature, KeyRingUtils.publicKeys(prepended.signingKeyRing), policy)
|
||||
prepended.signature,
|
||||
KeyRingUtils.publicKeys(prepended.signingKeyRing),
|
||||
policy)
|
||||
LOGGER.debug("Acceptable signature by key ${verification.signingKey}")
|
||||
layer.addVerifiedPrependedSignature(verification)
|
||||
} catch (e : SignatureValidationException) {
|
||||
} catch (e: SignatureValidationException) {
|
||||
LOGGER.debug("Rejected signature by key ${verification.signingKey}", e)
|
||||
layer.addRejectedPrependedSignature(SignatureVerification.Failure(verification, e))
|
||||
layer.addRejectedPrependedSignature(
|
||||
SignatureVerification.Failure(verification, e))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -864,22 +943,22 @@ class OpenPgpMessageInputStream(
|
|||
companion object {
|
||||
@JvmStatic
|
||||
private fun initialize(signature: PGPSignature, publicKey: PGPPublicKey) {
|
||||
val verifierProvider = ImplementationFactory.getInstance()
|
||||
.pgpContentVerifierBuilderProvider
|
||||
val verifierProvider =
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
|
||||
try {
|
||||
signature.init(verifierProvider, publicKey)
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun initialize(ops: PGPOnePassSignature, publicKey: PGPPublicKey) {
|
||||
val verifierProvider = ImplementationFactory.getInstance()
|
||||
.pgpContentVerifierBuilderProvider
|
||||
val verifierProvider =
|
||||
ImplementationFactory.getInstance().pgpContentVerifierBuilderProvider
|
||||
try {
|
||||
ops.init(verifierProvider, publicKey)
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
@ -891,36 +970,40 @@ class OpenPgpMessageInputStream(
|
|||
private val LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun create(inputStream: InputStream,
|
||||
options: ConsumerOptions) = create(inputStream, options, PGPainless.getPolicy())
|
||||
fun create(inputStream: InputStream, options: ConsumerOptions) =
|
||||
create(inputStream, options, PGPainless.getPolicy())
|
||||
|
||||
@JvmStatic
|
||||
fun create(inputStream: InputStream,
|
||||
options: ConsumerOptions,
|
||||
policy: Policy) = create(inputStream, options, Message(), policy)
|
||||
fun create(inputStream: InputStream, options: ConsumerOptions, policy: Policy) =
|
||||
create(inputStream, options, Message(), policy)
|
||||
|
||||
@JvmStatic
|
||||
internal fun create(inputStream: InputStream,
|
||||
options: ConsumerOptions,
|
||||
metadata: Layer,
|
||||
policy: Policy): OpenPgpMessageInputStream {
|
||||
internal fun create(
|
||||
inputStream: InputStream,
|
||||
options: ConsumerOptions,
|
||||
metadata: Layer,
|
||||
policy: Policy
|
||||
): OpenPgpMessageInputStream {
|
||||
val openPgpIn = OpenPgpInputStream(inputStream)
|
||||
openPgpIn.reset()
|
||||
|
||||
if (openPgpIn.isNonOpenPgp || options.isForceNonOpenPgpData()) {
|
||||
return OpenPgpMessageInputStream(Type.non_openpgp, openPgpIn, options, metadata, policy)
|
||||
return OpenPgpMessageInputStream(
|
||||
Type.non_openpgp, openPgpIn, options, metadata, policy)
|
||||
}
|
||||
|
||||
if (openPgpIn.isBinaryOpenPgp) {
|
||||
// Simply consume OpenPGP message
|
||||
return OpenPgpMessageInputStream(Type.standard, openPgpIn, options, metadata, policy)
|
||||
return OpenPgpMessageInputStream(
|
||||
Type.standard, openPgpIn, options, metadata, policy)
|
||||
}
|
||||
|
||||
return if (openPgpIn.isAsciiArmored) {
|
||||
val armorIn = ArmoredInputStreamFactory.get(openPgpIn)
|
||||
if (armorIn.isClearText) {
|
||||
(metadata as Message).setCleartextSigned()
|
||||
OpenPgpMessageInputStream(Type.cleartext_signed, armorIn, options, metadata, policy)
|
||||
OpenPgpMessageInputStream(
|
||||
Type.cleartext_signed, armorIn, options, metadata, policy)
|
||||
} else {
|
||||
// Simply consume dearmored OpenPGP message
|
||||
OpenPgpMessageInputStream(Type.standard, armorIn, options, metadata, policy)
|
||||
|
@ -930,4 +1013,4 @@ class OpenPgpMessageInputStream(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,43 +11,43 @@ import org.pgpainless.key.SubkeyIdentifier
|
|||
import org.pgpainless.signature.SignatureUtils
|
||||
|
||||
/**
|
||||
* Tuple of a signature and an identifier of its corresponding verification key.
|
||||
* Semantic meaning of the signature verification (success, failure) is merely given by context.
|
||||
* E.g. [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications,
|
||||
* while the class [Failure] contains failed verifications.
|
||||
* Tuple of a signature and an identifier of its corresponding verification key. Semantic meaning of
|
||||
* the signature verification (success, failure) is merely given by context. E.g.
|
||||
* [MessageMetadata.getVerifiedInlineSignatures] contains verified verifications, while the class
|
||||
* [Failure] contains failed verifications.
|
||||
*
|
||||
* @param signature PGPSignature object
|
||||
* @param signingKey [SubkeyIdentifier] of the (sub-) key that is used for signature verification.
|
||||
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
|
||||
* Note, that this might be null, e.g. in case of a [Failure] due to missing verification key.
|
||||
*/
|
||||
data class SignatureVerification(
|
||||
val signature: PGPSignature,
|
||||
val signingKey: SubkeyIdentifier
|
||||
) {
|
||||
data class SignatureVerification(val signature: PGPSignature, val signingKey: SubkeyIdentifier) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)};" +
|
||||
" Key: $signingKey;"
|
||||
" Key: $signingKey;"
|
||||
}
|
||||
|
||||
/**
|
||||
* Tuple object of a [SignatureVerification] and the corresponding [SignatureValidationException]
|
||||
* that caused the verification to fail.
|
||||
* Tuple object of a [SignatureVerification] and the corresponding
|
||||
* [SignatureValidationException] that caused the verification to fail.
|
||||
*
|
||||
* @param signatureVerification verification (tuple of [PGPSignature] and corresponding [SubkeyIdentifier])
|
||||
* @param signatureVerification verification (tuple of [PGPSignature] and corresponding
|
||||
* [SubkeyIdentifier])
|
||||
* @param validationException exception that caused the verification to fail
|
||||
*/
|
||||
data class Failure(
|
||||
val signature: PGPSignature,
|
||||
val signingKey: SubkeyIdentifier?,
|
||||
val validationException: SignatureValidationException
|
||||
val signature: PGPSignature,
|
||||
val signingKey: SubkeyIdentifier?,
|
||||
val validationException: SignatureValidationException
|
||||
) {
|
||||
|
||||
constructor(verification: SignatureVerification, validationException: SignatureValidationException):
|
||||
this(verification.signature, verification.signingKey, validationException)
|
||||
constructor(
|
||||
verification: SignatureVerification,
|
||||
validationException: SignatureValidationException
|
||||
) : this(verification.signature, verification.signingKey, validationException)
|
||||
|
||||
override fun toString(): String {
|
||||
return "Signature: ${SignatureUtils.getSignatureDigestPrefix(signature)}; Key: ${signingKey?.toString() ?: "null"}; Failure: ${validationException.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.decryption_verification
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import org.bouncycastle.bcpg.BCPGInputStream
|
||||
import org.bouncycastle.bcpg.MarkerPacket
|
||||
import org.bouncycastle.bcpg.Packet
|
||||
|
@ -13,25 +16,21 @@ import org.bouncycastle.openpgp.PGPLiteralData
|
|||
import org.bouncycastle.openpgp.PGPOnePassSignature
|
||||
import org.bouncycastle.openpgp.PGPSignature
|
||||
import org.pgpainless.algorithm.OpenPgpPacket
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* Since we need to update signatures with data from the underlying stream, this class is used to tee out the data.
|
||||
* Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data out though, since
|
||||
* [BCPGInputStream.readPacket] inconsistently calls a mix of [BCPGInputStream.read] and
|
||||
* [InputStream.read] of the underlying stream. This would cause the second length byte to get swallowed up.
|
||||
* Since we need to update signatures with data from the underlying stream, this class is used to
|
||||
* tee out the data. Unfortunately we cannot simply override [BCPGInputStream.read] to tee the data
|
||||
* out though, since [BCPGInputStream.readPacket] inconsistently calls a mix of
|
||||
* [BCPGInputStream.read] and [InputStream.read] of the underlying stream. This would cause the
|
||||
* second length byte to get swallowed up.
|
||||
*
|
||||
* Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the underlying
|
||||
* stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the next packets tag,
|
||||
* we need to delay teeing out that byte to signature verifiers.
|
||||
* Hence, the reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using
|
||||
* Therefore, this class delegates the teeing to an [DelayedTeeInputStream] which wraps the
|
||||
* underlying stream. Since calling [BCPGInputStream.nextPacketTag] reads up to and including the
|
||||
* next packets tag, we need to delay teeing out that byte to signature verifiers. Hence, the
|
||||
* reading methods of the [TeeBCPGInputStream] handle pushing this byte to the output stream using
|
||||
* [DelayedTeeInputStream.squeeze].
|
||||
*/
|
||||
class TeeBCPGInputStream(
|
||||
inputStream: BCPGInputStream,
|
||||
outputStream: OutputStream) {
|
||||
class TeeBCPGInputStream(inputStream: BCPGInputStream, outputStream: OutputStream) {
|
||||
|
||||
private val delayedTee: DelayedTeeInputStream
|
||||
private val packetInputStream: BCPGInputStream
|
||||
|
@ -43,8 +42,7 @@ class TeeBCPGInputStream(
|
|||
|
||||
fun nextPacketTag(): OpenPgpPacket? {
|
||||
return packetInputStream.nextPacketTag().let {
|
||||
if (it == -1) null
|
||||
else OpenPgpPacket.requireFromTag(it)
|
||||
if (it == -1) null else OpenPgpPacket.requireFromTag(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,8 +80,8 @@ class TeeBCPGInputStream(
|
|||
}
|
||||
|
||||
class DelayedTeeInputStream(
|
||||
private val inputStream: InputStream,
|
||||
private val outputStream: OutputStream
|
||||
private val inputStream: InputStream,
|
||||
private val outputStream: OutputStream
|
||||
) : InputStream() {
|
||||
private var last: Int = -1
|
||||
|
||||
|
@ -94,7 +92,7 @@ class TeeBCPGInputStream(
|
|||
return try {
|
||||
last = inputStream.read()
|
||||
last
|
||||
} catch (e : IOException) {
|
||||
} catch (e: IOException) {
|
||||
if (e.message?.contains("crc check failed in armored message") == true) {
|
||||
throw e
|
||||
}
|
||||
|
@ -108,19 +106,18 @@ class TeeBCPGInputStream(
|
|||
}
|
||||
|
||||
inputStream.read(b, off, len).let { r ->
|
||||
last = if (r > 0) {
|
||||
outputStream.write(b, off, r - 1)
|
||||
b[off + r - 1].toInt()
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
last =
|
||||
if (r > 0) {
|
||||
outputStream.write(b, off, r - 1)
|
||||
b[off + r - 1].toInt()
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Squeeze the last byte out and update the output stream.
|
||||
*/
|
||||
/** Squeeze the last byte out and update the output stream. */
|
||||
fun squeeze() {
|
||||
if (last != -1) {
|
||||
outputStream.write(last)
|
||||
|
@ -133,4 +130,4 @@ class TeeBCPGInputStream(
|
|||
outputStream.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,47 +4,49 @@
|
|||
|
||||
package org.pgpainless.decryption_verification.cleartext_signatures
|
||||
|
||||
import java.io.*
|
||||
import kotlin.jvm.Throws
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||
import org.bouncycastle.openpgp.PGPSignatureList
|
||||
import org.bouncycastle.util.Strings
|
||||
import org.pgpainless.exception.WrongConsumingMethodException
|
||||
import org.pgpainless.implementation.ImplementationFactory
|
||||
import org.pgpainless.util.ArmoredInputStreamFactory
|
||||
import java.io.*
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Utility class to deal with cleartext-signed messages.
|
||||
* Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
|
||||
* Utility class to deal with cleartext-signed messages. Based on Bouncycastle's
|
||||
* [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor].
|
||||
*/
|
||||
class ClearsignedMessageUtil {
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided
|
||||
* messageOutputStream.
|
||||
* Dearmor a clearsigned message, detach the inband signatures and write the plaintext
|
||||
* message to the provided messageOutputStream.
|
||||
*
|
||||
* @param clearsignedInputStream input stream containing a clearsigned message
|
||||
* @param messageOutputStream output stream to which the dearmored message shall be written
|
||||
* @return signatures
|
||||
*
|
||||
* @throws IOException if the message is not clearsigned or some other IO error happens
|
||||
* @throws WrongConsumingMethodException in case the armored message is not cleartext signed
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(WrongConsumingMethodException::class, IOException::class)
|
||||
fun detachSignaturesFromInbandClearsignedMessage(
|
||||
clearsignedInputStream: InputStream,
|
||||
messageOutputStream: OutputStream): PGPSignatureList {
|
||||
val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) {
|
||||
clearsignedInputStream
|
||||
} else {
|
||||
ArmoredInputStreamFactory.get(clearsignedInputStream)
|
||||
}
|
||||
clearsignedInputStream: InputStream,
|
||||
messageOutputStream: OutputStream
|
||||
): PGPSignatureList {
|
||||
val input: ArmoredInputStream =
|
||||
if (clearsignedInputStream is ArmoredInputStream) {
|
||||
clearsignedInputStream
|
||||
} else {
|
||||
ArmoredInputStreamFactory.get(clearsignedInputStream)
|
||||
}
|
||||
|
||||
if (!input.isClearText) {
|
||||
throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.")
|
||||
throw WrongConsumingMethodException(
|
||||
"Message isn't using the Cleartext Signature Framework.")
|
||||
}
|
||||
|
||||
BufferedOutputStream(messageOutputStream).use { output ->
|
||||
|
@ -94,7 +96,11 @@ class ClearsignedMessageUtil {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int {
|
||||
private fun readInputLine(
|
||||
bOut: ByteArrayOutputStream,
|
||||
lookAhead: Int,
|
||||
fIn: InputStream
|
||||
): Int {
|
||||
var mLookAhead = lookAhead
|
||||
bOut.reset()
|
||||
var ch = mLookAhead
|
||||
|
@ -150,4 +156,4 @@ class ClearsignedMessageUtil {
|
|||
return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@ import java.io.ByteArrayInputStream
|
|||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* Implementation of the [MultiPassStrategy].
|
||||
* This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream].
|
||||
* Implementation of the [MultiPassStrategy]. This class keeps the read data in memory by caching
|
||||
* the data inside a [ByteArrayOutputStream].
|
||||
*
|
||||
* Note, that this class is suitable and efficient for processing small amounts of data.
|
||||
* For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to
|
||||
* prevent [OutOfMemoryError] and other issues.
|
||||
* Note, that this class is suitable and efficient for processing small amounts of data. For larger
|
||||
* data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to prevent
|
||||
* [OutOfMemoryError] and other issues.
|
||||
*/
|
||||
class InMemoryMultiPassStrategy : MultiPassStrategy {
|
||||
|
||||
|
@ -26,4 +26,4 @@ class InMemoryMultiPassStrategy : MultiPassStrategy {
|
|||
get() = ByteArrayInputStream(getBytes())
|
||||
|
||||
fun getBytes(): ByteArray = messageOutputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,16 @@ package org.pgpainless.decryption_verification.cleartext_signatures
|
|||
import java.io.*
|
||||
|
||||
/**
|
||||
* Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures,
|
||||
* a strategy for how to cache the read data is required.
|
||||
* Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues.
|
||||
* Since for verification of cleartext signed messages, we need to read the whole data twice in
|
||||
* order to verify signatures, a strategy for how to cache the read data is required. Otherwise,
|
||||
* large data kept in memory could cause an [OutOfMemoryError] or other issues.
|
||||
*
|
||||
* This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes
|
||||
* to do verification.
|
||||
* This is an Interface that describes a strategy to deal with the fact that detached signatures
|
||||
* require multiple passes to do verification.
|
||||
*
|
||||
* This interface can be used to write the signed data stream out via [messageOutputStream] and later
|
||||
* get access to the data again via [messageInputStream].
|
||||
* Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away.
|
||||
* This interface can be used to write the signed data stream out via [messageOutputStream] and
|
||||
* later get access to the data again via [messageInputStream]. Thereby the detail where the data is
|
||||
* being stored (memory, file, etc.) can be abstracted away.
|
||||
*/
|
||||
interface MultiPassStrategy {
|
||||
|
||||
|
@ -32,8 +32,8 @@ interface MultiPassStrategy {
|
|||
* Provide an [InputStream] which contains the data that was previously written away in
|
||||
* [messageOutputStream].
|
||||
*
|
||||
* As there may be multiple signatures that need to be processed, each call of this method MUST return
|
||||
* a new [InputStream].
|
||||
* As there may be multiple signatures that need to be processed, each call of this method MUST
|
||||
* return a new [InputStream].
|
||||
*
|
||||
* @return input stream
|
||||
* @throws IOException io error
|
||||
|
@ -43,9 +43,10 @@ interface MultiPassStrategy {
|
|||
companion object {
|
||||
|
||||
/**
|
||||
* Write the message content out to a file and re-read it to verify signatures.
|
||||
* This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory.
|
||||
* After the message has been processed completely, the messages content are available at the provided file.
|
||||
* Write the message content out to a file and re-read it to verify signatures. This
|
||||
* strategy is best suited for larger messages (e.g. plaintext signed files) which might not
|
||||
* fit into memory. After the message has been processed completely, the messages content
|
||||
* are available at the provided file.
|
||||
*
|
||||
* @param file target file
|
||||
* @return strategy
|
||||
|
@ -56,10 +57,10 @@ interface MultiPassStrategy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Read the message content into memory.
|
||||
* This strategy is best suited for small messages which fit into memory.
|
||||
* After the message has been processed completely, the message content can be accessed by calling
|
||||
* [ByteArrayOutputStream.toByteArray] on [messageOutputStream].
|
||||
* Read the message content into memory. This strategy is best suited for small messages
|
||||
* which fit into memory. After the message has been processed completely, the message
|
||||
* content can be accessed by calling [ByteArrayOutputStream.toByteArray] on
|
||||
* [messageOutputStream].
|
||||
*
|
||||
* @return strategy
|
||||
*/
|
||||
|
@ -68,4 +69,4 @@ interface MultiPassStrategy {
|
|||
return InMemoryMultiPassStrategy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,15 @@ package org.pgpainless.decryption_verification.cleartext_signatures
|
|||
import java.io.*
|
||||
|
||||
/**
|
||||
* Implementation of the [MultiPassStrategy].
|
||||
* When processing signed data the first time, the data is being written out into a file.
|
||||
* For the second pass, that file is being read again.
|
||||
* Implementation of the [MultiPassStrategy]. When processing signed data the first time, the data
|
||||
* is being written out into a file. For the second pass, that file is being read again.
|
||||
*
|
||||
* This strategy is recommended when larger amounts of data need to be processed.
|
||||
* For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency.
|
||||
* This strategy is recommended when larger amounts of data need to be processed. For smaller files,
|
||||
* [InMemoryMultiPassStrategy] yields higher efficiency.
|
||||
*
|
||||
* @param file file to write the data to and read from
|
||||
*/
|
||||
class WriteToFileMultiPassStrategy(
|
||||
private val file: File
|
||||
) : MultiPassStrategy {
|
||||
class WriteToFileMultiPassStrategy(private val file: File) : MultiPassStrategy {
|
||||
|
||||
override val messageOutputStream: OutputStream
|
||||
@Throws(IOException::class)
|
||||
|
@ -39,5 +36,4 @@ class WriteToFileMultiPassStrategy(
|
|||
}
|
||||
return FileInputStream(file)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,35 +5,26 @@
|
|||
package org.pgpainless.decryption_verification.syntax_check
|
||||
|
||||
enum class InputSymbol {
|
||||
/**
|
||||
* A [PGPLiteralData] packet.
|
||||
*/
|
||||
/** A [PGPLiteralData] packet. */
|
||||
LITERAL_DATA,
|
||||
/**
|
||||
* A [PGPSignatureList] object.
|
||||
*/
|
||||
/** A [PGPSignatureList] object. */
|
||||
SIGNATURE,
|
||||
/**
|
||||
* A [PGPOnePassSignatureList] object.
|
||||
*/
|
||||
/** A [PGPOnePassSignatureList] object. */
|
||||
ONE_PASS_SIGNATURE,
|
||||
/**
|
||||
* A [PGPCompressedData] packet.
|
||||
* The contents of this packet MUST form a valid OpenPGP message, so a nested PDA is opened to verify
|
||||
* its nested packet sequence.
|
||||
* A [PGPCompressedData] packet. The contents of this packet MUST form a valid OpenPGP message,
|
||||
* so a nested PDA is opened to verify its nested packet sequence.
|
||||
*/
|
||||
COMPRESSED_DATA,
|
||||
/**
|
||||
* A [PGPEncryptedDataList] object.
|
||||
* This object combines multiple ESKs and the corresponding Symmetrically Encrypted
|
||||
* (possibly Integrity Protected) Data packet.
|
||||
* A [PGPEncryptedDataList] object. This object combines multiple ESKs and the corresponding
|
||||
* Symmetrically Encrypted (possibly Integrity Protected) Data packet.
|
||||
*/
|
||||
ENCRYPTED_DATA,
|
||||
/**
|
||||
* Marks the end of a (sub-) sequence.
|
||||
* This input is given if the end of an OpenPGP message is reached.
|
||||
* This might be the case for the end of the whole ciphertext, or the end of a packet with nested contents
|
||||
* (e.g. the end of a Compressed Data packet).
|
||||
* Marks the end of a (sub-) sequence. This input is given if the end of an OpenPGP message is
|
||||
* reached. This might be the case for the end of the whole ciphertext, or the end of a packet
|
||||
* with nested contents (e.g. the end of a Compressed Data packet).
|
||||
*/
|
||||
END_OF_SEQUENCE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
|||
/**
|
||||
* This class describes the syntax for OpenPGP messages as specified by rfc4880.
|
||||
*
|
||||
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
|
||||
* See [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/)
|
||||
* See [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/)
|
||||
* See [rfc4880 - §11.3. OpenPGP Messages](https://www.rfc-editor.org/rfc/rfc4880#section-11.3) See
|
||||
* [Blog post about theoretic background and translation of grammar to PDA syntax](https://blog.jabberhead.tk/2022/09/14/using-pushdown-automata-to-verify-packet-sequences/)
|
||||
* See
|
||||
* [Blog post about practically implementing the PDA for packet syntax validation](https://blog.jabberhead.tk/2022/10/26/implementing-packet-sequence-validation-using-pushdown-automata/)
|
||||
*/
|
||||
class OpenPgpMessageSyntax : Syntax {
|
||||
|
||||
|
@ -33,10 +34,12 @@ class OpenPgpMessageSyntax : Syntax {
|
|||
return when (input) {
|
||||
InputSymbol.LITERAL_DATA -> Transition(State.LITERAL_MESSAGE)
|
||||
InputSymbol.SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.MSG)
|
||||
InputSymbol.ONE_PASS_SIGNATURE -> Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG)
|
||||
InputSymbol.ONE_PASS_SIGNATURE ->
|
||||
Transition(State.OPENPGP_MESSAGE, StackSymbol.OPS, StackSymbol.MSG)
|
||||
InputSymbol.COMPRESSED_DATA -> Transition(State.COMPRESSED_MESSAGE)
|
||||
InputSymbol.ENCRYPTED_DATA -> Transition(State.ENCRYPTED_MESSAGE)
|
||||
InputSymbol.END_OF_SEQUENCE -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
||||
InputSymbol.END_OF_SEQUENCE ->
|
||||
throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
||||
else -> throw MalformedOpenPgpMessageException(State.OPENPGP_MESSAGE, input, stackItem)
|
||||
}
|
||||
}
|
||||
|
@ -85,4 +88,4 @@ class OpenPgpMessageSyntax : Syntax {
|
|||
}
|
||||
throw MalformedOpenPgpMessageException(State.VALID, input, stackItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,37 +8,41 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException
|
|||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Pushdown Automaton for validating context-free languages.
|
||||
* In PGPainless, this class is used to validate OpenPGP message packet sequences against the allowed syntax.
|
||||
* Pushdown Automaton for validating context-free languages. In PGPainless, this class is used to
|
||||
* validate OpenPGP message packet sequences against the allowed syntax.
|
||||
*
|
||||
* See [OpenPGP Message Syntax](https://www.rfc-editor.org/rfc/rfc4880#section-11.3)
|
||||
*/
|
||||
class PDA constructor(
|
||||
private val syntax: Syntax,
|
||||
private val stack: ArrayDeque<StackSymbol>,
|
||||
private val inputs: MutableList<InputSymbol>,
|
||||
private var state: State
|
||||
class PDA
|
||||
constructor(
|
||||
private val syntax: Syntax,
|
||||
private val stack: ArrayDeque<StackSymbol>,
|
||||
private val inputs: MutableList<InputSymbol>,
|
||||
private var state: State
|
||||
) {
|
||||
|
||||
/**
|
||||
* Construct a PDA with a custom [Syntax], initial [State] and initial [StackSymbols][StackSymbol].
|
||||
* Construct a PDA with a custom [Syntax], initial [State] and initial
|
||||
* [StackSymbols][StackSymbol].
|
||||
*
|
||||
* @param syntax syntax
|
||||
* @param initialState initial state
|
||||
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of appearance)
|
||||
* @param initialStack zero or more initial stack items (get pushed onto the stack in order of
|
||||
* appearance)
|
||||
*/
|
||||
constructor(syntax: Syntax, initialState: State, vararg initialStack: StackSymbol): this (
|
||||
syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
|
||||
constructor(
|
||||
syntax: Syntax,
|
||||
initialState: State,
|
||||
vararg initialStack: StackSymbol
|
||||
) : this(syntax, ArrayDeque(initialStack.toList().reversed()), mutableListOf(), initialState)
|
||||
|
||||
/** Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax]. */
|
||||
constructor() :
|
||||
this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
|
||||
|
||||
/**
|
||||
* Default constructor which initializes the PDA to work with the [OpenPgpMessageSyntax].
|
||||
*/
|
||||
constructor(): this(OpenPgpMessageSyntax(), State.OPENPGP_MESSAGE, StackSymbol.TERMINUS, StackSymbol.MSG)
|
||||
|
||||
/**
|
||||
* Process the next [InputSymbol].
|
||||
* This will either leave the PDA in the next state, or throw a [MalformedOpenPgpMessageException] if the
|
||||
* input symbol is rejected.
|
||||
* Process the next [InputSymbol]. This will either leave the PDA in the next state, or throw a
|
||||
* [MalformedOpenPgpMessageException] if the input symbol is rejected.
|
||||
*
|
||||
* @param input input symbol
|
||||
* @throws MalformedOpenPgpMessageException if the input symbol is rejected
|
||||
|
@ -53,14 +57,17 @@ class PDA constructor(
|
|||
}
|
||||
inputs.add(input)
|
||||
} catch (e: MalformedOpenPgpMessageException) {
|
||||
val stackFormat = if (stackSymbol != null) {
|
||||
"${stack.joinToString()}||$stackSymbol"
|
||||
} else {
|
||||
stack.joinToString()
|
||||
}
|
||||
val wrapped = MalformedOpenPgpMessageException(
|
||||
val stackFormat =
|
||||
if (stackSymbol != null) {
|
||||
"${stack.joinToString()}||$stackSymbol"
|
||||
} else {
|
||||
stack.joinToString()
|
||||
}
|
||||
val wrapped =
|
||||
MalformedOpenPgpMessageException(
|
||||
"Malformed message: After reading packet sequence ${inputs.joinToString()}, token '$input' is not allowed.\n" +
|
||||
"No transition from state '$state' with stack $stackFormat", e)
|
||||
"No transition from state '$state' with stack $stackFormat",
|
||||
e)
|
||||
LOGGER.debug("Invalid input '$input'", wrapped)
|
||||
throw wrapped
|
||||
}
|
||||
|
@ -87,7 +94,8 @@ class PDA constructor(
|
|||
*/
|
||||
fun assertValid() {
|
||||
if (!isValid()) {
|
||||
throw MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: ${toString()}")
|
||||
throw MalformedOpenPgpMessageException(
|
||||
"Pushdown Automaton is not in an acceptable state: ${toString()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +122,6 @@ class PDA constructor(
|
|||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
|
||||
@JvmStatic private val LOGGER = LoggerFactory.getLogger(PDA::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,10 @@
|
|||
package org.pgpainless.decryption_verification.syntax_check
|
||||
|
||||
enum class StackSymbol {
|
||||
/**
|
||||
* OpenPGP Message.
|
||||
*/
|
||||
/** OpenPGP Message. */
|
||||
MSG,
|
||||
/**
|
||||
* OnePassSignature (in case of BC this represents a OnePassSignatureList).
|
||||
*/
|
||||
/** OnePassSignature (in case of BC this represents a OnePassSignatureList). */
|
||||
OPS,
|
||||
/**
|
||||
* Special symbol representing the end of the message.
|
||||
*/
|
||||
/** Special symbol representing the end of the message. */
|
||||
TERMINUS
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
|
||||
package org.pgpainless.decryption_verification.syntax_check
|
||||
|
||||
/**
|
||||
* Set of states of the automaton.
|
||||
*/
|
||||
/** Set of states of the automaton. */
|
||||
enum class State {
|
||||
OPENPGP_MESSAGE,
|
||||
LITERAL_MESSAGE,
|
||||
COMPRESSED_MESSAGE,
|
||||
ENCRYPTED_MESSAGE,
|
||||
VALID
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,25 +6,24 @@ package org.pgpainless.decryption_verification.syntax_check
|
|||
|
||||
import org.pgpainless.exception.MalformedOpenPgpMessageException
|
||||
|
||||
/**
|
||||
* This interface can be used to define a custom syntax for the [PDA].
|
||||
*/
|
||||
/** This interface can be used to define a custom syntax for the [PDA]. */
|
||||
interface Syntax {
|
||||
|
||||
/**
|
||||
* Describe a transition rule from [State] <pre>from</pre> for [InputSymbol] <pre>input</pre>
|
||||
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack.
|
||||
* The resulting [Transition] contains the new [State], as well as a list of
|
||||
* [StackSymbols][StackSymbol] that get pushed onto the stack by the transition rule.
|
||||
* If there is no applicable rule, a [MalformedOpenPgpMessageException] is thrown, since in this case
|
||||
* the [InputSymbol] must be considered illegal.
|
||||
* with [StackSymbol] <pre>stackItem</pre> from the top of the [PDAs][PDA] stack. The resulting
|
||||
* [Transition] contains the new [State], as well as a list of [StackSymbols][StackSymbol] that
|
||||
* get pushed onto the stack by the transition rule. If there is no applicable rule, a
|
||||
* [MalformedOpenPgpMessageException] is thrown, since in this case the [InputSymbol] must be
|
||||
* considered illegal.
|
||||
*
|
||||
* @param from current state of the PDA
|
||||
* @param input input symbol
|
||||
* @param stackItem item that got popped from the top of the stack
|
||||
* @return applicable transition rule containing the new state and pushed stack symbols
|
||||
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input symbol is illegal)
|
||||
* @throws MalformedOpenPgpMessageException if there is no applicable transition rule (the input
|
||||
* symbol is illegal)
|
||||
*/
|
||||
@Throws(MalformedOpenPgpMessageException::class)
|
||||
fun transition(from: State, input: InputSymbol, stackItem: StackSymbol?): Transition
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,18 @@
|
|||
package org.pgpainless.decryption_verification.syntax_check
|
||||
|
||||
/**
|
||||
* Result of applying a transition rule.
|
||||
* Transition rules can be described by implementing the [Syntax] interface.
|
||||
* Result of applying a transition rule. Transition rules can be described by implementing the
|
||||
* [Syntax] interface.
|
||||
*
|
||||
* @param newState new [State] that is reached by applying the transition.
|
||||
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the transition.
|
||||
* The list contains items in the order in which they are pushed onto the stack.
|
||||
* The list may be empty.
|
||||
* @param pushedItems list of [StackSymbol] that are pushed onto the stack by applying the
|
||||
* transition. The list contains items in the order in which they are pushed onto the stack. The
|
||||
* list may be empty.
|
||||
*/
|
||||
class Transition private constructor(
|
||||
val pushedItems: List<StackSymbol>,
|
||||
val newState: State
|
||||
) {
|
||||
class Transition private constructor(val pushedItems: List<StackSymbol>, val newState: State) {
|
||||
|
||||
constructor(newState: State, vararg pushedItems: StackSymbol): this(pushedItems.toList(), newState)
|
||||
}
|
||||
constructor(
|
||||
newState: State,
|
||||
vararg pushedItems: StackSymbol
|
||||
) : this(pushedItems.toList(), newState)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.security.MessageDigest
|
||||
import org.bouncycastle.extensions.unlock
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey
|
||||
|
@ -13,20 +14,23 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator
|
|||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.algorithm.SignatureType
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector
|
||||
import java.security.MessageDigest
|
||||
|
||||
class BcHashContextSigner {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun signHashContext(hashContext: MessageDigest,
|
||||
signatureType: SignatureType,
|
||||
secretKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector): PGPSignature {
|
||||
fun signHashContext(
|
||||
hashContext: MessageDigest,
|
||||
signatureType: SignatureType,
|
||||
secretKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector
|
||||
): PGPSignature {
|
||||
val info = PGPainless.inspectKeyRing(secretKey)
|
||||
return info.signingSubkeys.mapNotNull { info.getSecretKey(it.keyID) }.firstOrNull()
|
||||
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
|
||||
?: throw PGPException("Key does not contain suitable signing subkey.")
|
||||
return info.signingSubkeys
|
||||
.mapNotNull { info.getSecretKey(it.keyID) }
|
||||
.firstOrNull()
|
||||
?.let { signHashContext(hashContext, signatureType, it.unlock(protector)) }
|
||||
?: throw PGPException("Key does not contain suitable signing subkey.")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,12 +42,14 @@ class BcHashContextSigner {
|
|||
* @throws PGPException in case of an OpenPGP error
|
||||
*/
|
||||
@JvmStatic
|
||||
internal fun signHashContext(hashContext: MessageDigest,
|
||||
signatureType: SignatureType,
|
||||
privateKey: PGPPrivateKey): PGPSignature {
|
||||
internal fun signHashContext(
|
||||
hashContext: MessageDigest,
|
||||
signatureType: SignatureType,
|
||||
privateKey: PGPPrivateKey
|
||||
): PGPSignature {
|
||||
return PGPSignatureGenerator(BcPGPHashContextContentSignerBuilder(hashContext))
|
||||
.apply { init(signatureType.code, privateKey) }
|
||||
.generate()
|
||||
.apply { init(signatureType.code, privateKey) }
|
||||
.generate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags
|
||||
import org.bouncycastle.crypto.CipherParameters
|
||||
import org.bouncycastle.crypto.CryptoException
|
||||
|
@ -23,17 +25,14 @@ import org.bouncycastle.openpgp.operator.PGPContentSigner
|
|||
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash contexts.
|
||||
* This can come in handy to sign data, which was already processed to calculate the hash context, without the
|
||||
* need to process it again to calculate the OpenPGP signature.
|
||||
* Implementation of [PGPContentSignerBuilder] using the BC API, which can be used to sign hash
|
||||
* contexts. This can come in handy to sign data, which was already processed to calculate the hash
|
||||
* context, without the need to process it again to calculate the OpenPGP signature.
|
||||
*/
|
||||
class BcPGPHashContextContentSignerBuilder(
|
||||
private val messageDigest: MessageDigest
|
||||
) : PGPHashContextContentSignerBuilder() {
|
||||
class BcPGPHashContextContentSignerBuilder(private val messageDigest: MessageDigest) :
|
||||
PGPHashContextContentSignerBuilder() {
|
||||
|
||||
private val keyConverter = BcPGPKeyConverter()
|
||||
private val _hashAlgorithm: HashAlgorithm
|
||||
|
@ -50,15 +49,22 @@ class BcPGPHashContextContentSignerBuilder(
|
|||
|
||||
return object : PGPContentSigner {
|
||||
override fun getOutputStream(): OutputStream = SignerOutputStream(signer)
|
||||
override fun getSignature(): ByteArray = try {
|
||||
signer.generateSignature()
|
||||
} catch (e : CryptoException) {
|
||||
throw IllegalStateException("unable to create signature.", e)
|
||||
}
|
||||
|
||||
override fun getSignature(): ByteArray =
|
||||
try {
|
||||
signer.generateSignature()
|
||||
} catch (e: CryptoException) {
|
||||
throw IllegalStateException("unable to create signature.", e)
|
||||
}
|
||||
|
||||
override fun getDigest(): ByteArray = messageDigest.digest()
|
||||
|
||||
override fun getType(): Int = signatureType
|
||||
|
||||
override fun getHashAlgorithm(): Int = _hashAlgorithm.algorithmId
|
||||
|
||||
override fun getKeyAlgorithm(): Int = keyAlgorithm.algorithmId
|
||||
|
||||
override fun getKeyID(): Long = privateKey.keyID
|
||||
}
|
||||
}
|
||||
|
@ -67,21 +73,25 @@ class BcPGPHashContextContentSignerBuilder(
|
|||
@JvmStatic
|
||||
private fun requireFromName(digestName: String): HashAlgorithm {
|
||||
val algorithm = HashAlgorithm.fromName(digestName)
|
||||
require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName"}
|
||||
require(algorithm != null) { "Cannot recognize OpenPGP Hash Algorithm: $digestName" }
|
||||
return algorithm
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun createSigner(keyAlgorithm: PublicKeyAlgorithm,
|
||||
messageDigest: MessageDigest,
|
||||
keyParam: CipherParameters): Signer {
|
||||
private fun createSigner(
|
||||
keyAlgorithm: PublicKeyAlgorithm,
|
||||
messageDigest: MessageDigest,
|
||||
keyParam: CipherParameters
|
||||
): Signer {
|
||||
val staticDigest = ExistingMessageDigest(messageDigest)
|
||||
return when (keyAlgorithm.algorithmId) {
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL, PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest)
|
||||
PublicKeyAlgorithmTags.RSA_GENERAL,
|
||||
PublicKeyAlgorithmTags.RSA_SIGN -> RSADigestSigner(staticDigest)
|
||||
PublicKeyAlgorithmTags.DSA -> DSADigestSigner(DSASigner(), staticDigest)
|
||||
PublicKeyAlgorithmTags.ECDSA -> DSADigestSigner(ECDSASigner(), staticDigest)
|
||||
PublicKeyAlgorithmTags.EDDSA_LEGACY -> {
|
||||
if (keyParam is Ed25519PrivateKeyParameters || keyParam is Ed25519PublicKeyParameters)
|
||||
if (keyParam is Ed25519PrivateKeyParameters ||
|
||||
keyParam is Ed25519PublicKeyParameters)
|
||||
EdDsaSigner(Ed25519Signer(), staticDigest)
|
||||
else EdDsaSigner(Ed448Signer(byteArrayOf()), staticDigest)
|
||||
}
|
||||
|
@ -91,10 +101,7 @@ class BcPGPHashContextContentSignerBuilder(
|
|||
}
|
||||
|
||||
// Copied from BCs BcImplProvider - required since BCs class is package visible only :/
|
||||
internal class EdDsaSigner(
|
||||
private val signer: Signer,
|
||||
private val digest: Digest
|
||||
) : Signer {
|
||||
internal class EdDsaSigner(private val signer: Signer, private val digest: Digest) : Signer {
|
||||
private val digBuf: ByteArray = ByteArray(digest.digestSize)
|
||||
|
||||
override fun init(forSigning: Boolean, param: CipherParameters) {
|
||||
|
@ -128,4 +135,4 @@ class BcPGPHashContextContentSignerBuilder(
|
|||
digest.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,15 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import org.pgpainless.algorithm.StreamEncoding
|
||||
import java.io.OutputStream
|
||||
import org.pgpainless.algorithm.StreamEncoding
|
||||
|
||||
/**
|
||||
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired [StreamEncoding].
|
||||
* This implementation originates from the Bouncy Castle library.
|
||||
* [OutputStream] which applies CR-LF encoding of its input data, based on the desired
|
||||
* [StreamEncoding]. This implementation originates from the Bouncy Castle library.
|
||||
*/
|
||||
class CRLFGeneratorStream(
|
||||
private val crlfOut: OutputStream,
|
||||
encoding: StreamEncoding
|
||||
) : OutputStream() {
|
||||
class CRLFGeneratorStream(private val crlfOut: OutputStream, encoding: StreamEncoding) :
|
||||
OutputStream() {
|
||||
|
||||
private val isBinary: Boolean
|
||||
private var lastB = 0
|
||||
|
@ -26,9 +24,9 @@ class CRLFGeneratorStream(
|
|||
|
||||
override fun write(b: Int) {
|
||||
if (!isBinary) {
|
||||
if (b == '\n'.code && lastB != '\r'.code) { // Unix
|
||||
if (b == '\n'.code && lastB != '\r'.code) { // Unix
|
||||
crlfOut.write('\r'.code)
|
||||
} else if (lastB == '\r'.code) { // MAC
|
||||
} else if (lastB == '\r'.code) { // MAC
|
||||
if (b != '\n'.code) {
|
||||
crlfOut.write('\n'.code)
|
||||
}
|
||||
|
@ -49,4 +47,4 @@ class CRLFGeneratorStream(
|
|||
super.flush()
|
||||
crlfOut.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,16 +4,18 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.io.OutputStream
|
||||
import org.pgpainless.PGPainless.Companion.getPolicy
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator.Companion.byPopularity
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.OutputStream
|
||||
|
||||
class EncryptionBuilder : EncryptionBuilderInterface {
|
||||
override fun onOutputStream(outputStream: OutputStream): EncryptionBuilderInterface.WithOptions {
|
||||
override fun onOutputStream(
|
||||
outputStream: OutputStream
|
||||
): EncryptionBuilderInterface.WithOptions {
|
||||
return WithOptionsImpl(outputStream)
|
||||
}
|
||||
|
||||
|
@ -26,8 +28,7 @@ class EncryptionBuilder : EncryptionBuilderInterface {
|
|||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
|
||||
@JvmStatic val LOGGER: Logger = LoggerFactory.getLogger(EncryptionBuilder::class.java)
|
||||
|
||||
/**
|
||||
* Negotiate the [SymmetricKeyAlgorithm] used for message encryption.
|
||||
|
@ -36,24 +37,32 @@ class EncryptionBuilder : EncryptionBuilderInterface {
|
|||
* @return negotiated symmetric key algorithm
|
||||
*/
|
||||
@JvmStatic
|
||||
fun negotiateSymmetricEncryptionAlgorithm(encryptionOptions: EncryptionOptions): SymmetricKeyAlgorithm {
|
||||
val preferences = encryptionOptions.keyViews.values
|
||||
fun negotiateSymmetricEncryptionAlgorithm(
|
||||
encryptionOptions: EncryptionOptions
|
||||
): SymmetricKeyAlgorithm {
|
||||
val preferences =
|
||||
encryptionOptions.keyViews.values
|
||||
.map { it.preferredSymmetricKeyAlgorithms }
|
||||
.toList()
|
||||
val algorithm = byPopularity().negotiate(
|
||||
getPolicy().symmetricKeyEncryptionAlgorithmPolicy,
|
||||
encryptionOptions.encryptionAlgorithmOverride,
|
||||
preferences)
|
||||
LOGGER.debug("Negotiation resulted in {} being the symmetric encryption algorithm of choice.", algorithm)
|
||||
val algorithm =
|
||||
byPopularity()
|
||||
.negotiate(
|
||||
getPolicy().symmetricKeyEncryptionAlgorithmPolicy,
|
||||
encryptionOptions.encryptionAlgorithmOverride,
|
||||
preferences)
|
||||
LOGGER.debug(
|
||||
"Negotiation resulted in {} being the symmetric encryption algorithm of choice.",
|
||||
algorithm)
|
||||
return algorithm
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun negotiateCompressionAlgorithm(producerOptions: ProducerOptions): CompressionAlgorithm {
|
||||
val compressionAlgorithmOverride = producerOptions.compressionAlgorithmOverride
|
||||
return compressionAlgorithmOverride ?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm()
|
||||
return compressionAlgorithmOverride
|
||||
?: getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm()
|
||||
|
||||
// TODO: Negotiation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
|
||||
fun interface EncryptionBuilderInterface {
|
||||
|
||||
|
@ -26,11 +26,11 @@ fun interface EncryptionBuilderInterface {
|
|||
*
|
||||
* @param options options
|
||||
* @return encryption stream
|
||||
*
|
||||
* @throws PGPException if something goes wrong during encryption stream preparation
|
||||
* @throws IOException if something goes wrong during encryption stream preparation (writing headers)
|
||||
* @throws IOException if something goes wrong during encryption stream preparation (writing
|
||||
* headers)
|
||||
*/
|
||||
@Throws(PGPException::class, IOException::class)
|
||||
fun withOptions(options: ProducerOptions): EncryptionStream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
|
||||
|
@ -19,12 +20,8 @@ import org.pgpainless.key.SubkeyIdentifier
|
|||
import org.pgpainless.key.info.KeyAccessor
|
||||
import org.pgpainless.key.info.KeyRingInfo
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.util.*
|
||||
|
||||
|
||||
class EncryptionOptions(
|
||||
private val purpose: EncryptionPurpose
|
||||
) {
|
||||
class EncryptionOptions(private val purpose: EncryptionPurpose) {
|
||||
private val _encryptionMethods: MutableSet<PGPKeyEncryptionMethodGenerator> = mutableSetOf()
|
||||
private val _encryptionKeyIdentifiers: MutableSet<SubkeyIdentifier> = mutableSetOf()
|
||||
private val _keyRingInfo: MutableMap<SubkeyIdentifier, KeyRingInfo> = mutableMapOf()
|
||||
|
@ -37,16 +34,20 @@ class EncryptionOptions(
|
|||
|
||||
val encryptionMethods
|
||||
get() = _encryptionMethods.toSet()
|
||||
|
||||
val encryptionKeyIdentifiers
|
||||
get() = _encryptionKeyIdentifiers.toSet()
|
||||
|
||||
val keyRingInfo
|
||||
get() = _keyRingInfo.toMap()
|
||||
|
||||
val keyViews
|
||||
get() = _keyViews.toMap()
|
||||
|
||||
val encryptionAlgorithmOverride
|
||||
get() = _encryptionAlgorithmOverride
|
||||
|
||||
constructor(): this(EncryptionPurpose.ANY)
|
||||
constructor() : this(EncryptionPurpose.ANY)
|
||||
|
||||
/**
|
||||
* Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
|
||||
|
@ -54,54 +55,56 @@ class EncryptionOptions(
|
|||
*
|
||||
* @return encryption options
|
||||
*/
|
||||
fun setEvaluationDate(evaluationDate: Date) = apply {
|
||||
this.evaluationDate = evaluationDate
|
||||
}
|
||||
fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate }
|
||||
|
||||
/**
|
||||
* Identify authenticatable certificates for the given user-ID by querying the {@link CertificateAuthority} for
|
||||
* identifiable bindings.
|
||||
* Add all acceptable bindings, whose trust amount is larger or equal to the target amount to the list of recipients.
|
||||
* Identify authenticatable certificates for the given user-ID by querying the {@link
|
||||
* CertificateAuthority} for identifiable bindings. Add all acceptable bindings, whose trust
|
||||
* amount is larger or equal to the target amount to the list of recipients.
|
||||
*
|
||||
* @param userId userId
|
||||
* @param email if true, treat the user-ID as an email address and match all user-IDs containing the mail address
|
||||
* @param email if true, treat the user-ID as an email address and match all user-IDs containing
|
||||
* the mail address
|
||||
* @param authority certificate authority
|
||||
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated,
|
||||
* 60 = partially authenticated...)
|
||||
* @param targetAmount target amount (120 = fully authenticated, 240 = doubly authenticated, 60
|
||||
* = partially authenticated...)
|
||||
* @return encryption options
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun addAuthenticatableRecipients(userId: String, email: Boolean, authority: CertificateAuthority, targetAmount: Int = 120) = apply {
|
||||
fun addAuthenticatableRecipients(
|
||||
userId: String,
|
||||
email: Boolean,
|
||||
authority: CertificateAuthority,
|
||||
targetAmount: Int = 120
|
||||
) = apply {
|
||||
var foundAcceptable = false
|
||||
authority.lookupByUserId(userId, email, evaluationDate, targetAmount)
|
||||
.filter { it.isAuthenticated() }
|
||||
.forEach { addRecipient(it.certificate)
|
||||
.also {
|
||||
foundAcceptable = true
|
||||
}
|
||||
}
|
||||
authority
|
||||
.lookupByUserId(userId, email, evaluationDate, targetAmount)
|
||||
.filter { it.isAuthenticated() }
|
||||
.forEach { addRecipient(it.certificate).also { foundAcceptable = true } }
|
||||
require(foundAcceptable) {
|
||||
"Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
|
||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
|
||||
* as recipients.
|
||||
*
|
||||
* @param keys keys
|
||||
* @return this
|
||||
*/
|
||||
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
|
||||
keys.toList().let {
|
||||
require(it.isNotEmpty()) {
|
||||
"Set of recipient keys cannot be empty."
|
||||
}
|
||||
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
|
||||
it.forEach { key -> addRecipient(key) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection}) as recipients.
|
||||
* Per key ring, the selector is applied to select one or more encryption subkeys.
|
||||
* Add all key rings in the provided {@link Iterable} (e.g. {@link PGPPublicKeyRingCollection})
|
||||
* as recipients. Per key ring, the selector is applied to select one or more encryption
|
||||
* subkeys.
|
||||
*
|
||||
* @param keys keys
|
||||
* @param selector encryption key selector
|
||||
|
@ -109,9 +112,7 @@ class EncryptionOptions(
|
|||
*/
|
||||
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
|
||||
keys.toList().let {
|
||||
require(it.isNotEmpty()) {
|
||||
"Set of recipient keys cannot be empty."
|
||||
}
|
||||
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
|
||||
it.forEach { key -> addRecipient(key, selector) }
|
||||
}
|
||||
}
|
||||
|
@ -125,19 +126,25 @@ class EncryptionOptions(
|
|||
fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector)
|
||||
|
||||
/**
|
||||
* Add a recipient by providing a key and recipient user-id.
|
||||
* The user-id is used to determine the recipients preferences (algorithms etc.).
|
||||
* Add a recipient by providing a key and recipient user-id. The user-id is used to determine
|
||||
* the recipients preferences (algorithms etc.).
|
||||
*
|
||||
* @param key key ring
|
||||
* @param userId user id
|
||||
* @return this
|
||||
*/
|
||||
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
|
||||
addRecipient(key, userId, encryptionKeySelector)
|
||||
addRecipient(key, userId, encryptionKeySelector)
|
||||
|
||||
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence, encryptionKeySelector: EncryptionKeySelector) = apply {
|
||||
fun addRecipient(
|
||||
key: PGPPublicKeyRing,
|
||||
userId: CharSequence,
|
||||
encryptionKeySelector: EncryptionKeySelector
|
||||
) = apply {
|
||||
val info = KeyRingInfo(key, evaluationDate)
|
||||
val subkeys = encryptionKeySelector.selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose))
|
||||
val subkeys =
|
||||
encryptionKeySelector.selectEncryptionSubkeys(
|
||||
info.getEncryptionSubkeys(userId, purpose))
|
||||
if (subkeys.isEmpty()) {
|
||||
throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
|
||||
}
|
||||
|
@ -155,17 +162,23 @@ class EncryptionOptions(
|
|||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun addHiddenRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector = encryptionKeySelector) = apply {
|
||||
addAsRecipient(key, selector, true)
|
||||
}
|
||||
fun addHiddenRecipient(
|
||||
key: PGPPublicKeyRing,
|
||||
selector: EncryptionKeySelector = encryptionKeySelector
|
||||
) = apply { addAsRecipient(key, selector, true) }
|
||||
|
||||
private fun addAsRecipient(key: PGPPublicKeyRing, selector: EncryptionKeySelector, wildcardKeyId: Boolean) = apply {
|
||||
private fun addAsRecipient(
|
||||
key: PGPPublicKeyRing,
|
||||
selector: EncryptionKeySelector,
|
||||
wildcardKeyId: Boolean
|
||||
) = apply {
|
||||
val info = KeyRingInfo(key, evaluationDate)
|
||||
val primaryKeyExpiration = try {
|
||||
info.primaryKeyExpirationDate
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
|
||||
}
|
||||
val primaryKeyExpiration =
|
||||
try {
|
||||
info.primaryKeyExpirationDate
|
||||
} catch (e: NoSuchElementException) {
|
||||
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
|
||||
}
|
||||
|
||||
if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) {
|
||||
throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration)
|
||||
|
@ -174,10 +187,12 @@ class EncryptionOptions(
|
|||
var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose))
|
||||
|
||||
// There are some legacy keys around without key flags.
|
||||
// If we allow encryption for those keys, we add valid keys without any key flags, if they are
|
||||
// If we allow encryption for those keys, we add valid keys without any key flags, if they
|
||||
// are
|
||||
// capable of encryption by means of their algorithm
|
||||
if (encryptionSubkeys.isEmpty() && allowEncryptionWithMissingKeyFlags) {
|
||||
encryptionSubkeys = info.validSubkeys
|
||||
encryptionSubkeys =
|
||||
info.validSubkeys
|
||||
.filter { it.isEncryptionKey }
|
||||
.filter { info.getKeyFlagsOf(it.keyID).isEmpty() }
|
||||
}
|
||||
|
@ -194,13 +209,16 @@ class EncryptionOptions(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addRecipientKey(certificate: PGPPublicKeyRing,
|
||||
key: PGPPublicKey,
|
||||
wildcardKeyId: Boolean) {
|
||||
private fun addRecipientKey(
|
||||
certificate: PGPPublicKeyRing,
|
||||
key: PGPPublicKey,
|
||||
wildcardKeyId: Boolean
|
||||
) {
|
||||
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
|
||||
addEncryptionMethod(ImplementationFactory.getInstance()
|
||||
.getPublicKeyKeyEncryptionMethodGenerator(key)
|
||||
.also { it.setUseWildcardKeyID(wildcardKeyId) })
|
||||
addEncryptionMethod(
|
||||
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also {
|
||||
it.setUseWildcardKeyID(wildcardKeyId)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -210,19 +228,19 @@ class EncryptionOptions(
|
|||
* @return this
|
||||
*/
|
||||
fun addPassphrase(passphrase: Passphrase) = apply {
|
||||
require(!passphrase.isEmpty) {
|
||||
"Passphrase MUST NOT be empty."
|
||||
}
|
||||
addEncryptionMethod(ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
|
||||
require(!passphrase.isEmpty) { "Passphrase MUST NOT be empty." }
|
||||
addEncryptionMethod(
|
||||
ImplementationFactory.getInstance().getPBEKeyEncryptionMethodGenerator(passphrase))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an {@link PGPKeyEncryptionMethodGenerator} which will be used to encrypt the message.
|
||||
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase)
|
||||
* or {@link PGPKeyEncryptionMethodGenerator} (public key).
|
||||
* Method generators are either {@link PBEKeyEncryptionMethodGenerator} (passphrase) or {@link
|
||||
* PGPKeyEncryptionMethodGenerator} (public key).
|
||||
*
|
||||
* This method is intended for advanced users to allow encryption for specific subkeys.
|
||||
* This can come in handy for example if data needs to be encrypted to a subkey that's ignored by PGPainless.
|
||||
* This method is intended for advanced users to allow encryption for specific subkeys. This can
|
||||
* come in handy for example if data needs to be encrypted to a subkey that's ignored by
|
||||
* PGPainless.
|
||||
*
|
||||
* @param encryptionMethod encryption method
|
||||
* @return this
|
||||
|
@ -232,10 +250,9 @@ class EncryptionOptions(
|
|||
}
|
||||
|
||||
/**
|
||||
* Override the used symmetric encryption algorithm.
|
||||
* The symmetric encryption algorithm is used to encrypt the message itself,
|
||||
* while the used symmetric key will be encrypted to all recipients using public key
|
||||
* cryptography.
|
||||
* Override the used symmetric encryption algorithm. The symmetric encryption algorithm is used
|
||||
* to encrypt the message itself, while the used symmetric key will be encrypted to all
|
||||
* recipients using public key cryptography.
|
||||
*
|
||||
* If the algorithm is not overridden, a suitable algorithm will be negotiated.
|
||||
*
|
||||
|
@ -250,10 +267,10 @@ class EncryptionOptions(
|
|||
}
|
||||
|
||||
/**
|
||||
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will allow encryption
|
||||
* for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag} subpacket.
|
||||
* This is a workaround for dealing with legacy keys that have no key flags subpacket but rely on the key algorithm
|
||||
* type to convey the subkeys use.
|
||||
* If this method is called, subsequent calls to {@link #addRecipient(PGPPublicKeyRing)} will
|
||||
* allow encryption for subkeys that do not carry any {@link org.pgpainless.algorithm.KeyFlag}
|
||||
* subpacket. This is a workaround for dealing with legacy keys that have no key flags subpacket
|
||||
* but rely on the key algorithm type to convey the subkeys use.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
|
@ -263,20 +280,16 @@ class EncryptionOptions(
|
|||
|
||||
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
|
||||
|
||||
|
||||
fun interface EncryptionKeySelector {
|
||||
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey>
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun get() = EncryptionOptions()
|
||||
@JvmStatic fun get() = EncryptionOptions()
|
||||
|
||||
@JvmStatic
|
||||
fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
|
||||
@JvmStatic fun encryptCommunications() = EncryptionOptions(EncryptionPurpose.COMMUNICATIONS)
|
||||
|
||||
@JvmStatic
|
||||
fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
|
||||
@JvmStatic fun encryptDataAtRest() = EncryptionOptions(EncryptionPurpose.STORAGE)
|
||||
|
||||
/**
|
||||
* Only encrypt to the first valid encryption capable subkey we stumble upon.
|
||||
|
@ -285,7 +298,8 @@ class EncryptionOptions(
|
|||
*/
|
||||
@JvmStatic
|
||||
fun encryptToFirstSubkey() = EncryptionKeySelector { encryptionCapableKeys ->
|
||||
encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf() }
|
||||
encryptionCapableKeys.firstOrNull()?.let { listOf(it) } ?: listOf()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt to any valid, encryption capable subkey on the key ring.
|
||||
|
@ -293,6 +307,8 @@ class EncryptionOptions(
|
|||
* @return encryption key selector
|
||||
*/
|
||||
@JvmStatic
|
||||
fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys -> encryptionCapableKeys }
|
||||
fun encryptToAllCapableSubkeys() = EncryptionKeySelector { encryptionCapableKeys ->
|
||||
encryptionCapableKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.extensions.matches
|
||||
import org.bouncycastle.openpgp.PGPLiteralData
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
|
@ -13,21 +14,20 @@ import org.pgpainless.algorithm.StreamEncoding
|
|||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.util.MultiMap
|
||||
import java.util.*
|
||||
|
||||
data class EncryptionResult(
|
||||
val encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
val compressionAlgorithm: CompressionAlgorithm,
|
||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>,
|
||||
val recipients: Set<SubkeyIdentifier>,
|
||||
val fileName: String,
|
||||
val modificationDate: Date,
|
||||
val fileEncoding: StreamEncoding
|
||||
val encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
val compressionAlgorithm: CompressionAlgorithm,
|
||||
val detachedSignatures: MultiMap<SubkeyIdentifier, PGPSignature>,
|
||||
val recipients: Set<SubkeyIdentifier>,
|
||||
val fileName: String,
|
||||
val modificationDate: Date,
|
||||
val fileEncoding: StreamEncoding
|
||||
) {
|
||||
|
||||
/**
|
||||
* Return true, if the message is marked as for-your-eyes-only.
|
||||
* This is typically done by setting the filename "_CONSOLE".
|
||||
* Return true, if the message is marked as for-your-eyes-only. This is typically done by
|
||||
* setting the filename "_CONSOLE".
|
||||
*
|
||||
* @return is message for your eyes only?
|
||||
*/
|
||||
|
@ -48,8 +48,7 @@ data class EncryptionResult(
|
|||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun builder() = Builder()
|
||||
@JvmStatic fun builder() = Builder()
|
||||
}
|
||||
|
||||
class Builder {
|
||||
|
@ -70,32 +69,35 @@ data class EncryptionResult(
|
|||
_compressionAlgorithm = compressionAlgorithm
|
||||
}
|
||||
|
||||
fun setFileName(fileName: String) = apply {
|
||||
_fileName = fileName
|
||||
}
|
||||
fun setFileName(fileName: String) = apply { _fileName = fileName }
|
||||
|
||||
fun setModificationDate(modificationDate: Date) = apply {
|
||||
_modificationDate = modificationDate
|
||||
}
|
||||
|
||||
fun setFileEncoding(encoding: StreamEncoding) = apply {
|
||||
_encoding = encoding
|
||||
}
|
||||
fun setFileEncoding(encoding: StreamEncoding) = apply { _encoding = encoding }
|
||||
|
||||
fun addRecipient(recipient: SubkeyIdentifier) = apply {
|
||||
(recipients as MutableSet).add(recipient)
|
||||
}
|
||||
|
||||
fun addDetachedSignature(signingSubkeyIdentifier: SubkeyIdentifier, detachedSignature: PGPSignature) = apply {
|
||||
detachedSignatures.put(signingSubkeyIdentifier, detachedSignature)
|
||||
}
|
||||
fun addDetachedSignature(
|
||||
signingSubkeyIdentifier: SubkeyIdentifier,
|
||||
detachedSignature: PGPSignature
|
||||
) = apply { detachedSignatures.put(signingSubkeyIdentifier, detachedSignature) }
|
||||
|
||||
fun build(): EncryptionResult {
|
||||
checkNotNull(_encryptionAlgorithm) { "Encryption algorithm not set." }
|
||||
checkNotNull(_compressionAlgorithm) { "Compression algorithm not set." }
|
||||
|
||||
return EncryptionResult(_encryptionAlgorithm!!, _compressionAlgorithm!!, detachedSignatures, recipients,
|
||||
_fileName, _modificationDate, _encoding)
|
||||
return EncryptionResult(
|
||||
_encryptionAlgorithm!!,
|
||||
_compressionAlgorithm!!,
|
||||
detachedSignatures,
|
||||
recipients,
|
||||
_fileName,
|
||||
_modificationDate,
|
||||
_encoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream
|
||||
import org.bouncycastle.bcpg.BCPGOutputStream
|
||||
import org.bouncycastle.openpgp.PGPCompressedDataGenerator
|
||||
|
@ -16,9 +19,6 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
|||
import org.pgpainless.implementation.ImplementationFactory
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
// 1 << 8 causes wrong partial body length encoding
|
||||
// 1 << 9 fixes this.
|
||||
|
@ -30,11 +30,13 @@ const val BUFFER_SIZE = 1 shl 9
|
|||
* depending on its configuration.
|
||||
*
|
||||
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
|
||||
* @see <a href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
|
||||
*
|
||||
* @see <a
|
||||
* href="https://github.com/neuhalje/bouncy-gpg/blob/master/src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java">Source</a>
|
||||
*/
|
||||
class EncryptionStream(
|
||||
private var outermostStream: OutputStream,
|
||||
private val options: ProducerOptions,
|
||||
private var outermostStream: OutputStream,
|
||||
private val options: ProducerOptions,
|
||||
) : OutputStream() {
|
||||
|
||||
private val resultBuilder: EncryptionResult.Builder = EncryptionResult.builder()
|
||||
|
@ -66,8 +68,8 @@ class EncryptionStream(
|
|||
|
||||
outermostStream = BufferedOutputStream(outermostStream)
|
||||
LOGGER.debug("Wrap encryption output in ASCII armor.")
|
||||
armorOutputStream = ArmoredOutputStreamFactory.get(outermostStream, options)
|
||||
.also { outermostStream = it }
|
||||
armorOutputStream =
|
||||
ArmoredOutputStreamFactory.get(outermostStream, options).also { outermostStream = it }
|
||||
}
|
||||
|
||||
@Throws(IOException::class, PGPException::class)
|
||||
|
@ -84,9 +86,11 @@ class EncryptionStream(
|
|||
EncryptionBuilder.negotiateSymmetricEncryptionAlgorithm(options.encryptionOptions).let {
|
||||
resultBuilder.setEncryptionAlgorithm(it)
|
||||
LOGGER.debug("Encrypt message using symmetric algorithm $it.")
|
||||
val encryptedDataGenerator = PGPEncryptedDataGenerator(
|
||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it)
|
||||
.apply { setWithIntegrityPacket(true) })
|
||||
val encryptedDataGenerator =
|
||||
PGPEncryptedDataGenerator(
|
||||
ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(it).apply {
|
||||
setWithIntegrityPacket(true)
|
||||
})
|
||||
options.encryptionOptions.encryptionMethods.forEach { m ->
|
||||
encryptedDataGenerator.addMethod(m)
|
||||
}
|
||||
|
@ -94,8 +98,11 @@ class EncryptionStream(
|
|||
resultBuilder.addRecipient(r)
|
||||
}
|
||||
|
||||
publicKeyEncryptedStream = encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE))
|
||||
.also { stream -> outermostStream = stream }
|
||||
publicKeyEncryptedStream =
|
||||
encryptedDataGenerator.open(outermostStream, ByteArray(BUFFER_SIZE)).also { stream
|
||||
->
|
||||
outermostStream = stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,8 +114,10 @@ class EncryptionStream(
|
|||
if (it == CompressionAlgorithm.UNCOMPRESSED) return
|
||||
|
||||
LOGGER.debug("Compress using $it.")
|
||||
basicCompressionStream = BCPGOutputStream(compressedDataGenerator!!.open(outermostStream))
|
||||
.also { stream -> outermostStream = stream }
|
||||
basicCompressionStream =
|
||||
BCPGOutputStream(compressedDataGenerator!!.open(outermostStream)).also { stream ->
|
||||
outermostStream = stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,12 +147,17 @@ class EncryptionStream(
|
|||
return
|
||||
}
|
||||
|
||||
literalDataGenerator = PGPLiteralDataGenerator().also { gen ->
|
||||
literalDataStream = gen.open(outermostStream, options.encoding.code, options.fileName,
|
||||
options.modificationDate, ByteArray(BUFFER_SIZE)).also { stream ->
|
||||
outermostStream = stream
|
||||
literalDataGenerator =
|
||||
PGPLiteralDataGenerator().also { gen ->
|
||||
literalDataStream =
|
||||
gen.open(
|
||||
outermostStream,
|
||||
options.encoding.code,
|
||||
options.fileName,
|
||||
options.modificationDate,
|
||||
ByteArray(BUFFER_SIZE))
|
||||
.also { stream -> outermostStream = stream }
|
||||
}
|
||||
}
|
||||
resultBuilder.apply {
|
||||
setFileName(options.fileName)
|
||||
setModificationDate(options.modificationDate)
|
||||
|
@ -156,39 +170,47 @@ class EncryptionStream(
|
|||
}
|
||||
|
||||
private fun prepareInputEncoding() {
|
||||
outermostStream = CRLFGeneratorStream(
|
||||
outermostStream =
|
||||
CRLFGeneratorStream(
|
||||
// By buffering here, we drastically improve performance
|
||||
// Reason is that CRLFGeneratorStream only implements write(int), so we need BufferedOutputStream to
|
||||
// Reason is that CRLFGeneratorStream only implements write(int), so we need
|
||||
// BufferedOutputStream to
|
||||
// "convert" to write(buf) calls again
|
||||
BufferedOutputStream(outermostStream),
|
||||
if (options.isApplyCRLFEncoding) StreamEncoding.UTF8 else StreamEncoding.BINARY)
|
||||
}
|
||||
|
||||
private fun collectHashAlgorithmsForCleartextSigning(): Array<Int> {
|
||||
return options.signingOptions?.signingMethods?.values
|
||||
?.map { it.hashAlgorithm }?.toSet()
|
||||
?.map { it.algorithmId }?.toTypedArray()
|
||||
?: arrayOf()
|
||||
return options.signingOptions
|
||||
?.signingMethods
|
||||
?.values
|
||||
?.map { it.hashAlgorithm }
|
||||
?.toSet()
|
||||
?.map { it.algorithmId }
|
||||
?.toTypedArray()
|
||||
?: arrayOf()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(data: Int) = outermostStream.write(data)
|
||||
@Throws(IOException::class) override fun write(data: Int) = outermostStream.write(data)
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(buffer: ByteArray) = write(buffer, 0, buffer.size)
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun write(buffer: ByteArray, off: Int, len: Int) = outermostStream.write(buffer, off, len)
|
||||
override fun write(buffer: ByteArray, off: Int, len: Int) =
|
||||
outermostStream.write(buffer, off, len)
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun flush() = outermostStream.flush()
|
||||
@Throws(IOException::class) override fun flush() = outermostStream.flush()
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
|
||||
outermostStream.close()
|
||||
literalDataStream?.apply { flush(); close() }
|
||||
literalDataStream?.apply {
|
||||
flush()
|
||||
close()
|
||||
}
|
||||
literalDataGenerator?.close()
|
||||
|
||||
if (options.isCleartextSigned) {
|
||||
|
@ -201,7 +223,7 @@ class EncryptionStream(
|
|||
|
||||
try {
|
||||
writeSignatures()
|
||||
} catch (e : PGPException) {
|
||||
} catch (e: PGPException) {
|
||||
throw IOException("Exception while writing signatures.", e)
|
||||
}
|
||||
|
||||
|
@ -238,14 +260,14 @@ class EncryptionStream(
|
|||
}
|
||||
|
||||
val result: EncryptionResult
|
||||
get() = check(closed) { "EncryptionStream must be closed before accessing the result." }
|
||||
get() =
|
||||
check(closed) { "EncryptionStream must be closed before accessing the result." }
|
||||
.let { resultBuilder.build() }
|
||||
|
||||
val isClosed
|
||||
get() = closed
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
|
||||
@JvmStatic private val LOGGER = LoggerFactory.getLogger(EncryptionStream::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,39 +4,43 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
import org.bouncycastle.crypto.Digest
|
||||
import org.bouncycastle.crypto.Signer
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder
|
||||
import java.io.OutputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
abstract class PGPHashContextContentSignerBuilder : PGPContentSignerBuilder {
|
||||
|
||||
// Copied from BC, required since BCs class is package visible only
|
||||
internal class SignerOutputStream(
|
||||
private val signer: Signer
|
||||
) : OutputStream() {
|
||||
internal class SignerOutputStream(private val signer: Signer) : OutputStream() {
|
||||
override fun write(p0: Int) = signer.update(p0.toByte())
|
||||
|
||||
override fun write(b: ByteArray) = signer.update(b, 0, b.size)
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) = signer.update(b, off, len)
|
||||
}
|
||||
|
||||
internal class ExistingMessageDigest(
|
||||
private val digest: MessageDigest
|
||||
) : Digest {
|
||||
internal class ExistingMessageDigest(private val digest: MessageDigest) : Digest {
|
||||
|
||||
override fun getAlgorithmName(): String = digest.algorithm
|
||||
|
||||
override fun getDigestSize(): Int = digest.digestLength
|
||||
|
||||
override fun update(b: Byte) = digest.update(b)
|
||||
|
||||
override fun update(buf: ByteArray, inOff: Int, len: Int) = digest.update(buf)
|
||||
|
||||
override fun doFinal(out: ByteArray, outOff: Int): Int {
|
||||
digest.digest().copyInto(out, outOff)
|
||||
return digestSize
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
// Nope!
|
||||
// We cannot reset, since BCs signer classes are resetting in their init() methods, which would also reset
|
||||
// We cannot reset, since BCs signer classes are resetting in their init() methods,
|
||||
// which would also reset
|
||||
// the messageDigest, losing its state. This would shatter our intention.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,17 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPLiteralData
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.StreamEncoding
|
||||
import java.util.*
|
||||
|
||||
class ProducerOptions private constructor(
|
||||
val encryptionOptions: EncryptionOptions?,
|
||||
val signingOptions: SigningOptions?) {
|
||||
class ProducerOptions
|
||||
private constructor(
|
||||
val encryptionOptions: EncryptionOptions?,
|
||||
val signingOptions: SigningOptions?
|
||||
) {
|
||||
|
||||
private var _fileName: String = ""
|
||||
private var _modificationDate: Date = PGPLiteralData.NOW
|
||||
|
@ -22,14 +24,15 @@ class ProducerOptions private constructor(
|
|||
private var _hideArmorHeaders = false
|
||||
var isDisableAsciiArmorCRC = false
|
||||
|
||||
private var _compressionAlgorithmOverride: CompressionAlgorithm = PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm
|
||||
private var _compressionAlgorithmOverride: CompressionAlgorithm =
|
||||
PGPainless.getPolicy().compressionAlgorithmPolicy.defaultCompressionAlgorithm
|
||||
private var asciiArmor = true
|
||||
private var _comment: String? = null
|
||||
private var _version: String? = null
|
||||
|
||||
/**
|
||||
* Specify, whether the result of the encryption/signing operation shall be ascii armored.
|
||||
* The default value is true.
|
||||
* Specify, whether the result of the encryption/signing operation shall be ascii armored. The
|
||||
* default value is true.
|
||||
*
|
||||
* @param asciiArmor ascii armor
|
||||
* @return builder
|
||||
|
@ -50,19 +53,15 @@ class ProducerOptions private constructor(
|
|||
get() = asciiArmor
|
||||
|
||||
/**
|
||||
* Set the comment header in ASCII armored output.
|
||||
* The default value is null, which means no comment header is added.
|
||||
* Multiline comments are possible using '\\n'.
|
||||
* <br>
|
||||
* Note: If a default header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment],
|
||||
* then both comments will be written to the produced ASCII armor.
|
||||
* Set the comment header in ASCII armored output. The default value is null, which means no
|
||||
* comment header is added. Multiline comments are possible using '\\n'. <br> Note: If a default
|
||||
* header comment is set using [org.pgpainless.util.ArmoredOutputStreamFactory.setComment], then
|
||||
* both comments will be written to the produced ASCII armor.
|
||||
*
|
||||
* @param comment comment header text
|
||||
* @return builder
|
||||
*/
|
||||
fun setComment(comment: String?) = apply {
|
||||
_comment = comment
|
||||
}
|
||||
fun setComment(comment: String?) = apply { _comment = comment }
|
||||
|
||||
/**
|
||||
* Return comment set for header in ascii armored output.
|
||||
|
@ -80,18 +79,15 @@ class ProducerOptions private constructor(
|
|||
fun hasComment() = _comment != null
|
||||
|
||||
/**
|
||||
* Set the version header in ASCII armored output.
|
||||
* The default value is null, which means no version header is added.
|
||||
* <br>
|
||||
* Note: If the value is non-null, then this method overrides the default version header set using
|
||||
* Set the version header in ASCII armored output. The default value is null, which means no
|
||||
* version header is added. <br> Note: If the value is non-null, then this method overrides the
|
||||
* default version header set using
|
||||
* [org.pgpainless.util.ArmoredOutputStreamFactory.setVersionInfo].
|
||||
*
|
||||
* @param version version header, or null for no version info.
|
||||
* @return builder
|
||||
*/
|
||||
fun setVersion(version: String?) = apply {
|
||||
_version = version
|
||||
}
|
||||
fun setVersion(version: String?) = apply { _version = version }
|
||||
|
||||
/**
|
||||
* Return the version info header in ascii armored output.
|
||||
|
@ -128,15 +124,13 @@ class ProducerOptions private constructor(
|
|||
get() = cleartextSigned
|
||||
|
||||
/**
|
||||
* Set the name of the encrypted file.
|
||||
* Note: This option cannot be used simultaneously with [setForYourEyesOnly].
|
||||
* Set the name of the encrypted file. Note: This option cannot be used simultaneously with
|
||||
* [setForYourEyesOnly].
|
||||
*
|
||||
* @param fileName name of the encrypted file
|
||||
* @return this
|
||||
*/
|
||||
fun setFileName(fileName: String) = apply {
|
||||
_fileName = fileName
|
||||
}
|
||||
fun setFileName(fileName: String) = apply { _fileName = fileName }
|
||||
|
||||
/**
|
||||
* Return the encrypted files name.
|
||||
|
@ -147,17 +141,15 @@ class ProducerOptions private constructor(
|
|||
get() = _fileName
|
||||
|
||||
/**
|
||||
* Mark the encrypted message as for-your-eyes-only by setting a special file name.
|
||||
* Note: Therefore this method cannot be used simultaneously with [setFileName].
|
||||
* Mark the encrypted message as for-your-eyes-only by setting a special file name. Note:
|
||||
* Therefore this method cannot be used simultaneously with [setFileName].
|
||||
*
|
||||
* @return this
|
||||
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this special filename in
|
||||
* newly generated literal data packets
|
||||
* @deprecated deprecated since at least crypto-refresh-05. It is not recommended using this
|
||||
* special filename in newly generated literal data packets
|
||||
*/
|
||||
@Deprecated("Signaling using special file name is discouraged.")
|
||||
fun setForYourEyesOnly() = apply {
|
||||
_fileName = PGPLiteralData.CONSOLE
|
||||
}
|
||||
fun setForYourEyesOnly() = apply { _fileName = PGPLiteralData.CONSOLE }
|
||||
|
||||
/**
|
||||
* Set the modification date of the encrypted file.
|
||||
|
@ -165,9 +157,7 @@ class ProducerOptions private constructor(
|
|||
* @param modificationDate Modification date of the encrypted file.
|
||||
* @return this
|
||||
*/
|
||||
fun setModificationDate(modificationDate: Date) = apply {
|
||||
_modificationDate = modificationDate
|
||||
}
|
||||
fun setModificationDate(modificationDate: Date) = apply { _modificationDate = modificationDate }
|
||||
|
||||
/**
|
||||
* Return the modification date of the encrypted file.
|
||||
|
@ -178,40 +168,32 @@ class ProducerOptions private constructor(
|
|||
get() = _modificationDate
|
||||
|
||||
/**
|
||||
* Set format metadata field of the literal data packet.
|
||||
* Defaults to [StreamEncoding.BINARY].
|
||||
* <br>
|
||||
* This does not change the encoding of the wrapped data itself.
|
||||
* To apply CR/LF encoding to your input data before processing, use [applyCRLFEncoding] instead.
|
||||
*
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9. Literal Data Packet</a>
|
||||
* Set format metadata field of the literal data packet. Defaults to [StreamEncoding.BINARY].
|
||||
* <br> This does not change the encoding of the wrapped data itself. To apply CR/LF encoding to
|
||||
* your input data before processing, use [applyCRLFEncoding] instead.
|
||||
*
|
||||
* @param encoding encoding
|
||||
* @return this
|
||||
*
|
||||
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are discouraged.
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-5.9">RFC4880 §5.9.
|
||||
* Literal Data Packet</a>
|
||||
* @deprecated options other than the default value of {@link StreamEncoding#BINARY} are
|
||||
* discouraged.
|
||||
*/
|
||||
@Deprecated("Options other than BINARY are discouraged.")
|
||||
fun setEncoding(encoding: StreamEncoding) = apply {
|
||||
encodingField = encoding
|
||||
}
|
||||
fun setEncoding(encoding: StreamEncoding) = apply { encodingField = encoding }
|
||||
|
||||
val encoding: StreamEncoding
|
||||
get() = encodingField
|
||||
|
||||
/**
|
||||
* Apply special encoding of line endings to the input data.
|
||||
* By default, this is disabled, which means that the data is not altered.
|
||||
* <br>
|
||||
* Enabling it will change the line endings to CR/LF.
|
||||
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will result in
|
||||
* the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
|
||||
* Apply special encoding of line endings to the input data. By default, this is disabled, which
|
||||
* means that the data is not altered. <br> Enabling it will change the line endings to CR/LF.
|
||||
* Note: The encoding will not be reversed when decrypting, so applying CR/LF encoding will
|
||||
* result in the identity "decrypt(encrypt(data)) == data == verify(sign(data))".
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
fun applyCRLFEncoding() = apply {
|
||||
applyCRLFEncoding = true
|
||||
}
|
||||
fun applyCRLFEncoding() = apply { applyCRLFEncoding = true }
|
||||
|
||||
/**
|
||||
* Return the input encoding that will be applied before signing / encryption.
|
||||
|
@ -239,9 +221,8 @@ class ProducerOptions private constructor(
|
|||
|
||||
/**
|
||||
* If set to `true`, armor headers like version or comments will be omitted from armored output.
|
||||
* By default, armor headers are not hidden.
|
||||
* Note: If comments are added via [setComment], those are not omitted, even if
|
||||
* [hideArmorHeaders] is set to `true`.
|
||||
* By default, armor headers are not hidden. Note: If comments are added via [setComment], those
|
||||
* are not omitted, even if [hideArmorHeaders] is set to `true`.
|
||||
*
|
||||
* @param hideArmorHeaders true or false
|
||||
* @return this
|
||||
|
@ -260,7 +241,7 @@ class ProducerOptions private constructor(
|
|||
*/
|
||||
@JvmStatic
|
||||
fun signAndEncrypt(encryptionOptions: EncryptionOptions, signingOptions: SigningOptions) =
|
||||
ProducerOptions(encryptionOptions, signingOptions)
|
||||
ProducerOptions(encryptionOptions, signingOptions)
|
||||
|
||||
/**
|
||||
* Sign some data without encryption.
|
||||
|
@ -268,8 +249,7 @@ class ProducerOptions private constructor(
|
|||
* @param signingOptions signing options
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
|
||||
@JvmStatic fun sign(signingOptions: SigningOptions) = ProducerOptions(null, signingOptions)
|
||||
|
||||
/**
|
||||
* Encrypt some data without signing.
|
||||
|
@ -281,12 +261,10 @@ class ProducerOptions private constructor(
|
|||
fun encrypt(encryptionOptions: EncryptionOptions) = ProducerOptions(encryptionOptions, null)
|
||||
|
||||
/**
|
||||
* Only wrap the data in an OpenPGP packet.
|
||||
* No encryption or signing will be applied.
|
||||
* Only wrap the data in an OpenPGP packet. No encryption or signing will be applied.
|
||||
*
|
||||
* @return builder
|
||||
*/
|
||||
@JvmStatic
|
||||
fun noEncryptionNoSigning() = ProducerOptions(null, null)
|
||||
@JvmStatic fun noEncryptionNoSigning() = ProducerOptions(null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,20 @@ package org.pgpainless.encryption_signing
|
|||
|
||||
import java.io.OutputStream
|
||||
|
||||
/**
|
||||
* OutputStream which has the task of updating signature generators for written data.
|
||||
*/
|
||||
/** OutputStream which has the task of updating signature generators for written data. */
|
||||
class SignatureGenerationStream(
|
||||
private val wrapped: OutputStream,
|
||||
private val options: SigningOptions?
|
||||
private val wrapped: OutputStream,
|
||||
private val options: SigningOptions?
|
||||
) : OutputStream() {
|
||||
|
||||
override fun close() = wrapped.close()
|
||||
|
||||
override fun flush() = wrapped.flush()
|
||||
|
||||
override fun write(b: Int) {
|
||||
wrapped.write(b)
|
||||
options?.run {
|
||||
signingMethods.values.forEach {
|
||||
it.signatureGenerator.update((b and 0xff).toByte())
|
||||
}
|
||||
signingMethods.values.forEach { it.signatureGenerator.update((b and 0xff).toByte()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,10 +27,6 @@ class SignatureGenerationStream(
|
|||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
wrapped.write(b, off, len)
|
||||
options?.run {
|
||||
signingMethods.values.forEach {
|
||||
it.signatureGenerator.update(b, off, len)
|
||||
}
|
||||
}
|
||||
options?.run { signingMethods.values.forEach { it.signatureGenerator.update(b, off, len) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.encryption_signing
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.extensions.unlock
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.pgpainless.PGPainless.Companion.getPolicy
|
||||
|
@ -22,7 +23,6 @@ import org.pgpainless.policy.Policy
|
|||
import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||
import java.util.*
|
||||
|
||||
class SigningOptions {
|
||||
|
||||
|
@ -34,10 +34,10 @@ class SigningOptions {
|
|||
get() = _hashAlgorithmOverride
|
||||
|
||||
/**
|
||||
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
|
||||
* If no override has been set, an acceptable algorithm will be negotiated instead.
|
||||
* Note: To override the hash algorithm for signing, call this method *before* calling
|
||||
* [addInlineSignature] or [addDetachedSignature].
|
||||
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used. If no
|
||||
* override has been set, an acceptable algorithm will be negotiated instead. Note: To override
|
||||
* the hash algorithm for signing, call this method *before* calling [addInlineSignature] or
|
||||
* [addDetachedSignature].
|
||||
*
|
||||
* @param hashAlgorithmOverride override hash algorithm
|
||||
* @return this
|
||||
|
@ -55,9 +55,7 @@ class SigningOptions {
|
|||
* @param evaluationDate new evaluation date
|
||||
* @return this
|
||||
*/
|
||||
fun setEvaluationDate(evaluationDate: Date) = apply {
|
||||
_evaluationDate = evaluationDate
|
||||
}
|
||||
fun setEvaluationDate(evaluationDate: Date) = apply { _evaluationDate = evaluationDate }
|
||||
|
||||
/**
|
||||
* Sign the message using an inline signature made by the provided signing key.
|
||||
|
@ -65,14 +63,15 @@ class SigningOptions {
|
|||
* @param signingKeyProtector protector to unlock the signing key
|
||||
* @param signingKey key ring containing the signing key
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with the key
|
||||
* @throws PGPException if the key cannot be unlocked or a signing method cannot be created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply {
|
||||
addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||
}
|
||||
fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) =
|
||||
apply {
|
||||
addInlineSignature(
|
||||
signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add inline signatures with all secret key rings in the provided secret key ring collection.
|
||||
|
@ -81,44 +80,41 @@ class SigningOptions {
|
|||
* @param signingKeys collection of signing keys
|
||||
* @param signatureType type of signature (binary, canonical text)
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with any of the keys
|
||||
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
|
||||
* @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be
|
||||
* created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||
signatureType: DocumentSignatureType) = apply {
|
||||
signingKeys.forEach {
|
||||
addInlineSignature(signingKeyProtector, it, null, signatureType)
|
||||
}
|
||||
fun addInlineSignatures(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||
signatureType: DocumentSignatureType
|
||||
) = apply {
|
||||
signingKeys.forEach { addInlineSignature(signingKeyProtector, it, null, signatureType) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an inline-signature.
|
||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||
* of one-pass-signature packets.
|
||||
* Add an inline-signature. Inline signatures are being embedded into the message itself and can
|
||||
* be processed in one pass, thanks to the use of one-pass-signature packets.
|
||||
*
|
||||
* @param signingKeyProtector decryptor to unlock the signing secret key
|
||||
* @param signingKey signing key
|
||||
* @param signatureType type of signature (binary, canonical text)
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with the key
|
||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
signatureType: DocumentSignatureType) = apply {
|
||||
addInlineSignature(signingKeyProtector, signingKey, null, signatureType)
|
||||
}
|
||||
fun addInlineSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
signatureType: DocumentSignatureType
|
||||
) = apply { addInlineSignature(signingKeyProtector, signingKey, null, signatureType) }
|
||||
|
||||
/**
|
||||
* Add an inline-signature.
|
||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||
* of one-pass-signature packets.
|
||||
* Add an inline-signature. Inline signatures are being embedded into the message itself and can
|
||||
* be processed in one pass, thanks to the use of one-pass-signature packets.
|
||||
*
|
||||
* <p>
|
||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||
*
|
||||
|
@ -126,27 +122,28 @@ class SigningOptions {
|
|||
* @param signingKey signing key
|
||||
* @param userId user-id of the signer
|
||||
* @param signatureType signature type (binary, canonical text)
|
||||
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
|
||||
* @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the
|
||||
* signature
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if the key is invalid
|
||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
@JvmOverloads
|
||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
userId: CharSequence? = null,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null) = apply {
|
||||
fun addInlineSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
userId: CharSequence? = null,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null
|
||||
) = apply {
|
||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||
throw UnboundUserIdException(
|
||||
of(signingKey),
|
||||
userId.toString(),
|
||||
keyRingInfo.getLatestUserIdCertification(userId),
|
||||
keyRingInfo.getUserIdRevocation(userId)
|
||||
)
|
||||
of(signingKey),
|
||||
userId.toString(),
|
||||
keyRingInfo.getLatestUserIdCertification(userId),
|
||||
keyRingInfo.getUserIdRevocation(userId))
|
||||
}
|
||||
|
||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||
|
@ -155,14 +152,16 @@ class SigningOptions {
|
|||
}
|
||||
|
||||
for (signingPubKey in signingPubKeys) {
|
||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||
val signingSecKey: PGPSecretKey =
|
||||
signingKey.getSecretKey(signingPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||
val hashAlgorithms =
|
||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||
addSigningMethod(
|
||||
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,17 +174,22 @@ class SigningOptions {
|
|||
* @param signatureType signature type
|
||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||
* @return builder
|
||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be
|
||||
* created.
|
||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
|
||||
* signing-capable subkeys
|
||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
|
||||
* identified secret key
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
@JvmOverloads
|
||||
fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
keyId: Long,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null) = apply {
|
||||
fun addInlineSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
keyId: Long,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null
|
||||
) = apply {
|
||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||
if (signingPubKeys.isEmpty()) {
|
||||
|
@ -197,12 +201,14 @@ class SigningOptions {
|
|||
continue
|
||||
}
|
||||
|
||||
val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||
val signingSecKey =
|
||||
signingKey.getSecretKey(signingPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||
val signingSubkey = signingSecKey.unlock(signingKeyProtector)
|
||||
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||
addSigningMethod(
|
||||
signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback)
|
||||
return this
|
||||
}
|
||||
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||
|
@ -215,45 +221,44 @@ class SigningOptions {
|
|||
* @param signingKeys collection of signing key rings
|
||||
* @param signatureType type of the signature (binary, canonical text)
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with any of the keys
|
||||
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
|
||||
* @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing
|
||||
* method cannot be created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||
signatureType: DocumentSignatureType) = apply {
|
||||
signingKeys.forEach {
|
||||
addDetachedSignature(signingKeyProtector, it, null, signatureType)
|
||||
}
|
||||
fun addDetachedSignatures(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKeys: Iterable<PGPSecretKeyRing>,
|
||||
signatureType: DocumentSignatureType
|
||||
) = apply {
|
||||
signingKeys.forEach { addDetachedSignature(signingKeyProtector, it, null, signatureType) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a detached signature.
|
||||
* Detached signatures are not being added into the PGP message itself.
|
||||
* Instead, they can be distributed separately to the message.
|
||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||
* Create a detached signature. Detached signatures are not being added into the PGP message
|
||||
* itself. Instead, they can be distributed separately to the message. Detached signatures are
|
||||
* useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||
*
|
||||
* @param signingKeyProtector decryptor to unlock the secret signing key
|
||||
* @param signingKey signing key
|
||||
* @param signatureType type of data that is signed (binary, canonical text)
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with the key
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method
|
||||
* can be created
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
signatureType: DocumentSignatureType) = apply {
|
||||
addDetachedSignature(signingKeyProtector, signingKey, null, signatureType)
|
||||
}
|
||||
fun addDetachedSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
signatureType: DocumentSignatureType
|
||||
) = apply { addDetachedSignature(signingKeyProtector, signingKey, null, signatureType) }
|
||||
|
||||
/**
|
||||
* Create a detached signature.
|
||||
* Detached signatures are not being added into the PGP message itself.
|
||||
* Instead, they can be distributed separately to the message.
|
||||
* Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||
* Create a detached signature. Detached signatures are not being added into the PGP message
|
||||
* itself. Instead, they can be distributed separately to the message. Detached signatures are
|
||||
* useful if the data that is being signed shall not be modified (e.g. when signing a file).
|
||||
*
|
||||
* <p>
|
||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||
*
|
||||
|
@ -263,25 +268,26 @@ class SigningOptions {
|
|||
* @param signatureType type of data that is signed (binary, canonical text)
|
||||
* @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
|
||||
* @return this
|
||||
*
|
||||
* @throws KeyException if something is wrong with the key
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method
|
||||
* can be created
|
||||
*/
|
||||
@JvmOverloads
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
userId: String? = null,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketCallback: Callback? = null) = apply {
|
||||
fun addDetachedSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
userId: String? = null,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketCallback: Callback? = null
|
||||
) = apply {
|
||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||
if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
|
||||
throw UnboundUserIdException(
|
||||
of(signingKey),
|
||||
userId.toString(),
|
||||
keyRingInfo.getLatestUserIdCertification(userId),
|
||||
keyRingInfo.getUserIdRevocation(userId)
|
||||
)
|
||||
of(signingKey),
|
||||
userId.toString(),
|
||||
keyRingInfo.getLatestUserIdCertification(userId),
|
||||
keyRingInfo.getUserIdRevocation(userId))
|
||||
}
|
||||
|
||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||
|
@ -290,14 +296,16 @@ class SigningOptions {
|
|||
}
|
||||
|
||||
for (signingPubKey in signingPubKeys) {
|
||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||
val signingSecKey: PGPSecretKey =
|
||||
signingKey.getSecretKey(signingPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||
val hashAlgorithms =
|
||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId)
|
||||
else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
|
||||
addSigningMethod(
|
||||
signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,17 +318,22 @@ class SigningOptions {
|
|||
* @param signatureType signature type
|
||||
* @param subpacketsCallback callback to modify the signatures subpackets
|
||||
* @return builder
|
||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be created.
|
||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys
|
||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key
|
||||
* @throws PGPException if the secret key cannot be unlocked or if no signing method can be
|
||||
* created.
|
||||
* @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any
|
||||
* signing-capable subkeys
|
||||
* @throws KeyException.MissingSecretKeyException if the key ring does not contain the
|
||||
* identified secret key
|
||||
*/
|
||||
@Throws(KeyException::class, PGPException::class)
|
||||
@JvmOverloads
|
||||
fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
keyId: Long,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null) = apply {
|
||||
fun addDetachedSignature(
|
||||
signingKeyProtector: SecretKeyRingProtector,
|
||||
signingKey: PGPSecretKeyRing,
|
||||
keyId: Long,
|
||||
signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT,
|
||||
subpacketsCallback: Callback? = null
|
||||
) = apply {
|
||||
val keyRingInfo = inspectKeyRing(signingKey, evaluationDate)
|
||||
|
||||
val signingPubKeys = keyRingInfo.signingSubkeys
|
||||
|
@ -330,12 +343,20 @@ class SigningOptions {
|
|||
|
||||
for (signingPubKey in signingPubKeys) {
|
||||
if (signingPubKey.keyID == keyId) {
|
||||
val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID)
|
||||
val signingSecKey: PGPSecretKey =
|
||||
signingKey.getSecretKey(signingPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID)
|
||||
val signingSubkey: PGPPrivateKey = signingSecKey.unlock(signingKeyProtector)
|
||||
val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID)
|
||||
val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||
addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback)
|
||||
val hashAlgorithm: HashAlgorithm =
|
||||
negotiateHashAlgorithm(hashAlgorithms, getPolicy())
|
||||
addSigningMethod(
|
||||
signingKey,
|
||||
signingSubkey,
|
||||
hashAlgorithm,
|
||||
signatureType,
|
||||
true,
|
||||
subpacketsCallback)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@ -343,26 +364,30 @@ class SigningOptions {
|
|||
throw MissingSecretKeyException(of(signingKey), keyId)
|
||||
}
|
||||
|
||||
private fun addSigningMethod(signingKey: PGPSecretKeyRing,
|
||||
signingSubkey: PGPPrivateKey,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
signatureType: DocumentSignatureType,
|
||||
detached: Boolean,
|
||||
subpacketCallback: Callback? = null) {
|
||||
private fun addSigningMethod(
|
||||
signingKey: PGPSecretKeyRing,
|
||||
signingSubkey: PGPPrivateKey,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
signatureType: DocumentSignatureType,
|
||||
detached: Boolean,
|
||||
subpacketCallback: Callback? = null
|
||||
) {
|
||||
val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID)
|
||||
val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID)
|
||||
val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm)
|
||||
val bitStrength = signingSecretKey.publicKey.bitStrength
|
||||
if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) {
|
||||
throw UnacceptableSigningKeyException(
|
||||
PublicKeyAlgorithmPolicyException(
|
||||
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
|
||||
PublicKeyAlgorithmPolicyException(
|
||||
of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength))
|
||||
}
|
||||
|
||||
val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
|
||||
val generator: PGPSignatureGenerator =
|
||||
createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType)
|
||||
|
||||
// Subpackets
|
||||
val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
|
||||
val hashedSubpackets =
|
||||
SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey)
|
||||
val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets()
|
||||
if (subpacketCallback != null) {
|
||||
subpacketCallback.modifyHashedSubpackets(hashedSubpackets)
|
||||
|
@ -372,80 +397,87 @@ class SigningOptions {
|
|||
generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets))
|
||||
|
||||
val signingMethod =
|
||||
if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm)
|
||||
else SigningMethod.inlineSignature(generator, hashAlgorithm)
|
||||
if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm)
|
||||
else SigningMethod.inlineSignature(generator, hashAlgorithm)
|
||||
(signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate, which hash algorithm to use.
|
||||
*
|
||||
*
|
||||
* This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm].
|
||||
* After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
|
||||
* Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is
|
||||
* used as a fallback.
|
||||
* This method gives the highest priority to the algorithm override, which can be set via
|
||||
* [.overrideHashAlgorithm]. After that, the signing keys hash algorithm preferences are
|
||||
* iterated to find the first acceptable algorithm. Lastly, should no acceptable algorithm be
|
||||
* found, the [Policies][Policy] default signature hash algorithm is used as a fallback.
|
||||
*
|
||||
* @param preferences preferences
|
||||
* @param policy policy
|
||||
* @return selected hash algorithm
|
||||
*/
|
||||
private fun negotiateHashAlgorithm(preferences: Set<HashAlgorithm>,
|
||||
policy: Policy): HashAlgorithm {
|
||||
return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
|
||||
private fun negotiateHashAlgorithm(
|
||||
preferences: Set<HashAlgorithm>,
|
||||
policy: Policy
|
||||
): HashAlgorithm {
|
||||
return _hashAlgorithmOverride
|
||||
?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences)
|
||||
}
|
||||
|
||||
@Throws(PGPException::class)
|
||||
private fun createSignatureGenerator(privateKey: PGPPrivateKey,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
signatureType: DocumentSignatureType): PGPSignatureGenerator {
|
||||
private fun createSignatureGenerator(
|
||||
privateKey: PGPPrivateKey,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
signatureType: DocumentSignatureType
|
||||
): PGPSignatureGenerator {
|
||||
return ImplementationFactory.getInstance()
|
||||
.getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
|
||||
.let { csb ->
|
||||
PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) }
|
||||
.getPGPContentSignerBuilder(
|
||||
privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId)
|
||||
.let { csb ->
|
||||
PGPSignatureGenerator(csb).also {
|
||||
it.init(signatureType.signatureType.code, privateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun get() = SigningOptions()
|
||||
@JvmStatic fun get() = SigningOptions()
|
||||
}
|
||||
|
||||
/**
|
||||
* A method of signing.
|
||||
*/
|
||||
class SigningMethod private constructor(
|
||||
val signatureGenerator: PGPSignatureGenerator,
|
||||
val isDetached: Boolean,
|
||||
val hashAlgorithm: HashAlgorithm
|
||||
/** A method of signing. */
|
||||
class SigningMethod
|
||||
private constructor(
|
||||
val signatureGenerator: PGPSignatureGenerator,
|
||||
val isDetached: Boolean,
|
||||
val hashAlgorithm: HashAlgorithm
|
||||
) {
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Inline-signature method.
|
||||
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
|
||||
* Inline-signature method. The resulting signature will be written into the message
|
||||
* itself, together with a one-pass-signature packet.
|
||||
*
|
||||
* @param signatureGenerator signature generator
|
||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||
* @return inline signing method
|
||||
*/
|
||||
@JvmStatic
|
||||
fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
||||
SigningMethod(signatureGenerator, false, hashAlgorithm)
|
||||
fun inlineSignature(
|
||||
signatureGenerator: PGPSignatureGenerator,
|
||||
hashAlgorithm: HashAlgorithm
|
||||
) = SigningMethod(signatureGenerator, false, hashAlgorithm)
|
||||
|
||||
/**
|
||||
* Detached signing method.
|
||||
* The resulting signature will not be added to the message, and instead can be distributed separately
|
||||
* to the signed message.
|
||||
* Detached signing method. The resulting signature will not be added to the message,
|
||||
* and instead can be distributed separately to the signed message.
|
||||
*
|
||||
* @param signatureGenerator signature generator
|
||||
* @param hashAlgorithm hash algorithm used to generate the signature
|
||||
* @return detached signing method
|
||||
*/
|
||||
@JvmStatic
|
||||
fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) =
|
||||
SigningMethod(signatureGenerator, true, hashAlgorithm)
|
||||
fun detachedSignature(
|
||||
signatureGenerator: PGPSignatureGenerator,
|
||||
hashAlgorithm: HashAlgorithm
|
||||
) = SigningMethod(signatureGenerator, true, hashAlgorithm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.implementation
|
||||
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
||||
|
@ -27,70 +30,85 @@ import org.pgpainless.algorithm.HashAlgorithm
|
|||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
|
||||
class BcImplementationFactory : ImplementationFactory() {
|
||||
override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider = BcPGPDigestCalculatorProvider()
|
||||
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider = BcPGPContentVerifierBuilderProvider()
|
||||
override val pgpDigestCalculatorProvider: BcPGPDigestCalculatorProvider =
|
||||
BcPGPDigestCalculatorProvider()
|
||||
override val pgpContentVerifierBuilderProvider: BcPGPContentVerifierBuilderProvider =
|
||||
BcPGPContentVerifierBuilderProvider()
|
||||
override val keyFingerprintCalculator: BcKeyFingerprintCalculator = BcKeyFingerprintCalculator()
|
||||
|
||||
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
||||
BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||
.build(passphrase.getChars())
|
||||
override fun getPBESecretKeyEncryptor(
|
||||
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor =
|
||||
BcPBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int,
|
||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
||||
BcPBESecretKeyEncryptorBuilder(
|
||||
encryptionAlgorithm.algorithmId,
|
||||
getPGPDigestCalculator(hashAlgorithm),
|
||||
s2kCount)
|
||||
.build(passphrase.getChars())
|
||||
override fun getPBESecretKeyEncryptor(
|
||||
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor =
|
||||
BcPBESecretKeyEncryptorBuilder(
|
||||
encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
||||
BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
||||
.build(passphrase.getChars())
|
||||
BcPBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider).build(passphrase.getChars())
|
||||
|
||||
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder =
|
||||
BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||
override fun getPGPContentSignerBuilder(
|
||||
keyAlgorithm: Int,
|
||||
hashAlgorithm: Int
|
||||
): PGPContentSignerBuilder = BcPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||
|
||||
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
||||
BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider)
|
||||
BcPBEDataDecryptorFactory(passphrase.getChars(), pgpDigestCalculatorProvider)
|
||||
|
||||
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory =
|
||||
BcPublicKeyDataDecryptorFactory(privateKey)
|
||||
override fun getPublicKeyDataDecryptorFactory(
|
||||
privateKey: PGPPrivateKey
|
||||
): PublicKeyDataDecryptorFactory = BcPublicKeyDataDecryptorFactory(privateKey)
|
||||
|
||||
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory =
|
||||
BcSessionKeyDataDecryptorFactory(sessionKey)
|
||||
override fun getSessionKeyDataDecryptorFactory(
|
||||
sessionKey: PGPSessionKey
|
||||
): SessionKeyDataDecryptorFactory = BcSessionKeyDataDecryptorFactory(sessionKey)
|
||||
|
||||
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator =
|
||||
BcPublicKeyKeyEncryptionMethodGenerator(key)
|
||||
override fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||
key: PGPPublicKey
|
||||
): PublicKeyKeyEncryptionMethodGenerator = BcPublicKeyKeyEncryptionMethodGenerator(key)
|
||||
|
||||
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator =
|
||||
BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||
override fun getPBEKeyEncryptionMethodGenerator(
|
||||
passphrase: Passphrase
|
||||
): PBEKeyEncryptionMethodGenerator = BcPBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||
|
||||
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
||||
BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
||||
BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
||||
|
||||
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair =
|
||||
BcPGPKeyPair(
|
||||
publicKeyAlgorithm.algorithmId,
|
||||
jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate),
|
||||
creationDate)
|
||||
override fun getPGPKeyPair(
|
||||
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||
keyPair: KeyPair,
|
||||
creationDate: Date
|
||||
): PGPKeyPair =
|
||||
BcPGPKeyPair(
|
||||
publicKeyAlgorithm.algorithmId,
|
||||
jceToBcKeyPair(publicKeyAlgorithm, keyPair, creationDate),
|
||||
creationDate)
|
||||
|
||||
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory = BcPGPObjectFactory(inputStream)
|
||||
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
|
||||
BcPGPObjectFactory(inputStream)
|
||||
|
||||
private fun jceToBcKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||
keyPair: KeyPair,
|
||||
creationDate: Date): AsymmetricCipherKeyPair =
|
||||
BcPGPKeyConverter().let { converter ->
|
||||
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair ->
|
||||
AsymmetricCipherKeyPair(converter.getPublicKey(pair.publicKey), converter.getPrivateKey(pair.privateKey))
|
||||
}
|
||||
private fun jceToBcKeyPair(
|
||||
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||
keyPair: KeyPair,
|
||||
creationDate: Date
|
||||
): AsymmetricCipherKeyPair =
|
||||
BcPGPKeyConverter().let { converter ->
|
||||
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate).let { pair ->
|
||||
AsymmetricCipherKeyPair(
|
||||
converter.getPublicKey(pair.publicKey),
|
||||
converter.getPrivateKey(pair.privateKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.implementation
|
||||
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.operator.*
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
|
@ -11,18 +14,13 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
|
|||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.util.Passphrase
|
||||
import org.pgpainless.util.SessionKey
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
|
||||
abstract class ImplementationFactory {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private var instance: ImplementationFactory = BcImplementationFactory()
|
||||
@JvmStatic private var instance: ImplementationFactory = BcImplementationFactory()
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance() = instance
|
||||
@JvmStatic fun getInstance() = instance
|
||||
|
||||
@JvmStatic
|
||||
fun setFactoryImplementation(implementation: ImplementationFactory) = apply {
|
||||
|
@ -38,56 +36,82 @@ abstract class ImplementationFactory {
|
|||
get() = getPGPDigestCalculator(HashAlgorithm.SHA1)
|
||||
|
||||
@Throws(PGPException::class)
|
||||
abstract fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase): PBESecretKeyEncryptor
|
||||
abstract fun getPBESecretKeyEncryptor(
|
||||
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor
|
||||
|
||||
@Throws(PGPException::class)
|
||||
abstract fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor
|
||||
|
||||
@Throws(PGPException::class)
|
||||
abstract fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm, hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int, passphrase: Passphrase): PBESecretKeyEncryptor
|
||||
abstract fun getPBESecretKeyEncryptor(
|
||||
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor
|
||||
|
||||
fun getPGPDigestCalculator(hashAlgorithm: HashAlgorithm): PGPDigestCalculator =
|
||||
getPGPDigestCalculator(hashAlgorithm.algorithmId)
|
||||
getPGPDigestCalculator(hashAlgorithm.algorithmId)
|
||||
|
||||
fun getPGPDigestCalculator(hashAlgorithm: Int): PGPDigestCalculator =
|
||||
pgpDigestCalculatorProvider.get(hashAlgorithm)
|
||||
pgpDigestCalculatorProvider.get(hashAlgorithm)
|
||||
|
||||
fun getPGPContentSignerBuilder(keyAlgorithm: PublicKeyAlgorithm, hashAlgorithm: HashAlgorithm): PGPContentSignerBuilder =
|
||||
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
|
||||
fun getPGPContentSignerBuilder(
|
||||
keyAlgorithm: PublicKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm
|
||||
): PGPContentSignerBuilder =
|
||||
getPGPContentSignerBuilder(keyAlgorithm.algorithmId, hashAlgorithm.algorithmId)
|
||||
|
||||
abstract fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder
|
||||
abstract fun getPGPContentSignerBuilder(
|
||||
keyAlgorithm: Int,
|
||||
hashAlgorithm: Int
|
||||
): PGPContentSignerBuilder
|
||||
|
||||
@Throws(PGPException::class)
|
||||
abstract fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory
|
||||
|
||||
abstract fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory
|
||||
abstract fun getPublicKeyDataDecryptorFactory(
|
||||
privateKey: PGPPrivateKey
|
||||
): PublicKeyDataDecryptorFactory
|
||||
|
||||
fun getSessionKeyDataDecryptorFactory(sessionKey: SessionKey): SessionKeyDataDecryptorFactory =
|
||||
getSessionKeyDataDecryptorFactory(PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key))
|
||||
getSessionKeyDataDecryptorFactory(
|
||||
PGPSessionKey(sessionKey.algorithm.algorithmId, sessionKey.key))
|
||||
|
||||
abstract fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory
|
||||
abstract fun getSessionKeyDataDecryptorFactory(
|
||||
sessionKey: PGPSessionKey
|
||||
): SessionKeyDataDecryptorFactory
|
||||
|
||||
abstract fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator
|
||||
abstract fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||
key: PGPPublicKey
|
||||
): PublicKeyKeyEncryptionMethodGenerator
|
||||
|
||||
abstract fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator
|
||||
abstract fun getPBEKeyEncryptionMethodGenerator(
|
||||
passphrase: Passphrase
|
||||
): PBEKeyEncryptionMethodGenerator
|
||||
|
||||
fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: SymmetricKeyAlgorithm): PGPDataEncryptorBuilder =
|
||||
getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
|
||||
fun getPGPDataEncryptorBuilder(
|
||||
symmetricKeyAlgorithm: SymmetricKeyAlgorithm
|
||||
): PGPDataEncryptorBuilder = getPGPDataEncryptorBuilder(symmetricKeyAlgorithm.algorithmId)
|
||||
|
||||
abstract fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder
|
||||
|
||||
@Throws(PGPException::class)
|
||||
abstract fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair
|
||||
abstract fun getPGPKeyPair(
|
||||
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||
keyPair: KeyPair,
|
||||
creationDate: Date
|
||||
): PGPKeyPair
|
||||
|
||||
fun getPGPObjectFactory(bytes: ByteArray): PGPObjectFactory =
|
||||
getPGPObjectFactory(bytes.inputStream())
|
||||
getPGPObjectFactory(bytes.inputStream())
|
||||
|
||||
abstract fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory
|
||||
|
||||
override fun toString(): String {
|
||||
return javaClass.simpleName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.implementation
|
||||
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.operator.*
|
||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator
|
||||
|
@ -24,79 +27,86 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
|
|||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import org.pgpainless.provider.ProviderFactory
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.io.InputStream
|
||||
import java.security.KeyPair
|
||||
import java.util.*
|
||||
|
||||
class JceImplementationFactory : ImplementationFactory() {
|
||||
override val pgpDigestCalculatorProvider: PGPDigestCalculatorProvider =
|
||||
JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build()
|
||||
JcaPGPDigestCalculatorProviderBuilder().setProvider(ProviderFactory.provider).build()
|
||||
override val pgpContentVerifierBuilderProvider: PGPContentVerifierBuilderProvider =
|
||||
JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
JcaPGPContentVerifierBuilderProvider().setProvider(ProviderFactory.provider)
|
||||
override val keyFingerprintCalculator: KeyFingerPrintCalculator =
|
||||
JcaKeyFingerprintCalculator()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
JcaKeyFingerprintCalculator().setProvider(ProviderFactory.provider)
|
||||
|
||||
override fun getPBESecretKeyEncryptor(symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
||||
JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
override fun getPBESecretKeyEncryptor(
|
||||
symmetricKeyAlgorithm: SymmetricKeyAlgorithm,
|
||||
digestCalculator: PGPDigestCalculator,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor =
|
||||
JcePBESecretKeyEncryptorBuilder(symmetricKeyAlgorithm.algorithmId, digestCalculator)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPBESecretKeyEncryptor(encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int,
|
||||
passphrase: Passphrase): PBESecretKeyEncryptor =
|
||||
JcePBESecretKeyEncryptorBuilder(
|
||||
encryptionAlgorithm.algorithmId,
|
||||
getPGPDigestCalculator(hashAlgorithm),
|
||||
s2kCount)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
override fun getPBESecretKeyEncryptor(
|
||||
encryptionAlgorithm: SymmetricKeyAlgorithm,
|
||||
hashAlgorithm: HashAlgorithm,
|
||||
s2kCount: Int,
|
||||
passphrase: Passphrase
|
||||
): PBESecretKeyEncryptor =
|
||||
JcePBESecretKeyEncryptorBuilder(
|
||||
encryptionAlgorithm.algorithmId, getPGPDigestCalculator(hashAlgorithm), s2kCount)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPBESecretKeyDecryptor(passphrase: Passphrase): PBESecretKeyDecryptor =
|
||||
JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
JcePBESecretKeyDecryptorBuilder(pgpDigestCalculatorProvider)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPGPContentSignerBuilder(keyAlgorithm: Int, hashAlgorithm: Int): PGPContentSignerBuilder =
|
||||
JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
override fun getPGPContentSignerBuilder(
|
||||
keyAlgorithm: Int,
|
||||
hashAlgorithm: Int
|
||||
): PGPContentSignerBuilder =
|
||||
JcaPGPContentSignerBuilder(keyAlgorithm, hashAlgorithm)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
|
||||
override fun getPBEDataDecryptorFactory(passphrase: Passphrase): PBEDataDecryptorFactory =
|
||||
JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
JcePBEDataDecryptorFactoryBuilder(pgpDigestCalculatorProvider)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(passphrase.getChars())
|
||||
|
||||
override fun getPublicKeyDataDecryptorFactory(privateKey: PGPPrivateKey): PublicKeyDataDecryptorFactory =
|
||||
JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(privateKey)
|
||||
override fun getPublicKeyDataDecryptorFactory(
|
||||
privateKey: PGPPrivateKey
|
||||
): PublicKeyDataDecryptorFactory =
|
||||
JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(privateKey)
|
||||
|
||||
override fun getSessionKeyDataDecryptorFactory(sessionKey: PGPSessionKey): SessionKeyDataDecryptorFactory =
|
||||
JceSessionKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(sessionKey)
|
||||
override fun getSessionKeyDataDecryptorFactory(
|
||||
sessionKey: PGPSessionKey
|
||||
): SessionKeyDataDecryptorFactory =
|
||||
JceSessionKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(ProviderFactory.provider)
|
||||
.build(sessionKey)
|
||||
|
||||
override fun getPublicKeyKeyEncryptionMethodGenerator(key: PGPPublicKey): PublicKeyKeyEncryptionMethodGenerator =
|
||||
JcePublicKeyKeyEncryptionMethodGenerator(key)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
override fun getPublicKeyKeyEncryptionMethodGenerator(
|
||||
key: PGPPublicKey
|
||||
): PublicKeyKeyEncryptionMethodGenerator =
|
||||
JcePublicKeyKeyEncryptionMethodGenerator(key).setProvider(ProviderFactory.provider)
|
||||
|
||||
override fun getPBEKeyEncryptionMethodGenerator(passphrase: Passphrase): PBEKeyEncryptionMethodGenerator =
|
||||
JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||
.setProvider(ProviderFactory.provider)
|
||||
override fun getPBEKeyEncryptionMethodGenerator(
|
||||
passphrase: Passphrase
|
||||
): PBEKeyEncryptionMethodGenerator =
|
||||
JcePBEKeyEncryptionMethodGenerator(passphrase.getChars())
|
||||
.setProvider(ProviderFactory.provider)
|
||||
|
||||
override fun getPGPDataEncryptorBuilder(symmetricKeyAlgorithm: Int): PGPDataEncryptorBuilder =
|
||||
JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm)
|
||||
.setProvider(ProviderFactory.provider)
|
||||
JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm).setProvider(ProviderFactory.provider)
|
||||
|
||||
override fun getPGPKeyPair(publicKeyAlgorithm: PublicKeyAlgorithm, keyPair: KeyPair, creationDate: Date): PGPKeyPair =
|
||||
JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate)
|
||||
override fun getPGPKeyPair(
|
||||
publicKeyAlgorithm: PublicKeyAlgorithm,
|
||||
keyPair: KeyPair,
|
||||
creationDate: Date
|
||||
): PGPKeyPair = JcaPGPKeyPair(publicKeyAlgorithm.algorithmId, keyPair, creationDate)
|
||||
|
||||
override fun getPGPObjectFactory(inputStream: InputStream): PGPObjectFactory =
|
||||
PGPObjectFactory(inputStream, keyFingerprintCalculator)
|
||||
}
|
||||
PGPObjectFactory(inputStream, keyFingerprintCalculator)
|
||||
}
|
||||
|
|
|
@ -4,16 +4,13 @@
|
|||
|
||||
package org.pgpainless.key
|
||||
|
||||
import java.nio.charset.Charset
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Abstract super class of different version OpenPGP fingerprints.
|
||||
*
|
||||
*/
|
||||
/** Abstract super class of different version OpenPGP fingerprints. */
|
||||
abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint> {
|
||||
val fingerprint: String
|
||||
val bytes: ByteArray
|
||||
|
@ -26,39 +23,41 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
|||
abstract fun getVersion(): Int
|
||||
|
||||
/**
|
||||
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to.
|
||||
* This method can be implemented for V4 and V5 fingerprints.
|
||||
* V3 key-IDs cannot be derived from the fingerprint, but we don't care, since V3 is deprecated.
|
||||
* Return the key id of the OpenPGP public key this {@link OpenPgpFingerprint} belongs to. This
|
||||
* method can be implemented for V4 and V5 fingerprints. V3 key-IDs cannot be derived from the
|
||||
* fingerprint, but we don't care, since V3 is deprecated.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
|
||||
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
|
||||
* @return key id
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2"> RFC-4880 §12.2: Key IDs and
|
||||
* Fingerprints</a>
|
||||
*/
|
||||
abstract val keyId: Long
|
||||
|
||||
constructor(fingerprint: String) {
|
||||
val prep = fingerprint.replace(" ", "").trim().uppercase()
|
||||
if (!isValid(prep)) {
|
||||
throw IllegalArgumentException("Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
||||
throw IllegalArgumentException(
|
||||
"Fingerprint '$fingerprint' does not appear to be a valid OpenPGP V${getVersion()} fingerprint.")
|
||||
}
|
||||
this.fingerprint = prep
|
||||
this.bytes = Hex.decode(prep)
|
||||
}
|
||||
|
||||
constructor(bytes: ByteArray): this(Hex.toHexString(bytes))
|
||||
constructor(bytes: ByteArray) : this(Hex.toHexString(bytes))
|
||||
|
||||
constructor(key: PGPPublicKey): this(key.fingerprint) {
|
||||
constructor(key: PGPPublicKey) : this(key.fingerprint) {
|
||||
if (key.version != getVersion()) {
|
||||
throw IllegalArgumentException("Key is not a v${getVersion()} OpenPgp key.")
|
||||
}
|
||||
}
|
||||
|
||||
constructor(key: PGPSecretKey): this(key.publicKey)
|
||||
constructor(key: PGPSecretKey) : this(key.publicKey)
|
||||
|
||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
||||
constructor(keys: PGPKeyRing) : this(keys.publicKey)
|
||||
|
||||
/**
|
||||
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
|
||||
*
|
||||
* @param fp fingerprint to check.
|
||||
* @return true if fingerprint is valid.
|
||||
*/
|
||||
|
@ -69,7 +68,9 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
|||
|
||||
override fun get(index: Int) = fingerprint.get(index)
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int) = fingerprint.subSequence(startIndex, endIndex)
|
||||
override fun subSequence(startIndex: Int, endIndex: Int) =
|
||||
fingerprint.subSequence(startIndex, endIndex)
|
||||
|
||||
override fun compareTo(other: OpenPgpFingerprint): Int {
|
||||
return fingerprint.compareTo(other.fingerprint)
|
||||
}
|
||||
|
@ -87,49 +88,49 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
|||
abstract fun prettyPrint(): String
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val utf8: Charset = Charset.forName("UTF-8")
|
||||
@JvmStatic val utf8: Charset = Charset.forName("UTF-8")
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the given key.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
* Return the fingerprint of the given key. This method automatically matches key versions
|
||||
* to fingerprint implementations.
|
||||
*
|
||||
* @param key key
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the given key. This method automatically matches key versions
|
||||
* to fingerprint implementations.
|
||||
*
|
||||
* @param key key
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of(key: PGPSecretKey): OpenPgpFingerprint = of(key.publicKey)
|
||||
fun of(key: PGPPublicKey): OpenPgpFingerprint =
|
||||
when (key.version) {
|
||||
4 -> OpenPgpV4Fingerprint(key)
|
||||
5 -> OpenPgpV5Fingerprint(key)
|
||||
6 -> OpenPgpV6Fingerprint(key)
|
||||
else ->
|
||||
throw IllegalArgumentException(
|
||||
"OpenPGP keys of version ${key.version} are not supported.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the given key.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
*
|
||||
* @param key key
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of (key: PGPPublicKey): OpenPgpFingerprint = when(key.version) {
|
||||
4 -> OpenPgpV4Fingerprint(key)
|
||||
5 -> OpenPgpV5Fingerprint(key)
|
||||
6 -> OpenPgpV6Fingerprint(key)
|
||||
else -> throw IllegalArgumentException("OpenPGP keys of version ${key.version} are not supported.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fingerprint of the primary key of the given key ring.
|
||||
* This method automatically matches key versions to fingerprint implementations.
|
||||
* Return the fingerprint of the primary key of the given key ring. This method
|
||||
* automatically matches key versions to fingerprint implementations.
|
||||
*
|
||||
* @param ring key ring
|
||||
* @return fingerprint
|
||||
*/
|
||||
@JvmStatic
|
||||
fun of (keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
||||
@JvmStatic fun of(keys: PGPKeyRing): OpenPgpFingerprint = of(keys.publicKey)
|
||||
|
||||
/**
|
||||
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string.
|
||||
* If the trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6 fingerprint.
|
||||
* In this case, we return a {@link _64DigitFingerprint}. Since this is ambiguous, it is generally recommended
|
||||
* to know the version of the key beforehand.
|
||||
* Try to parse an {@link OpenPgpFingerprint} from the given fingerprint string. If the
|
||||
* trimmed fingerprint without whitespace is 64 characters long, it is either a v5 or v6
|
||||
* fingerprint. In this case, we return a {@link _64DigitFingerprint}. Since this is
|
||||
* ambiguous, it is generally recommended to know the version of the key beforehand.
|
||||
*
|
||||
* @param fingerprint fingerprint
|
||||
* @return parsed fingerprint
|
||||
|
@ -146,7 +147,8 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
|||
// Might be v5 or v6 :/
|
||||
return _64DigitFingerprint(prep)
|
||||
}
|
||||
throw IllegalArgumentException("Fingerprint does not appear to match any known fingerprint pattern.")
|
||||
throw IllegalArgumentException(
|
||||
"Fingerprint does not appear to match any known fingerprint pattern.")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,6 +161,6 @@ abstract class OpenPgpFingerprint : CharSequence, Comparable<OpenPgpFingerprint>
|
|||
@JvmStatic
|
||||
@Deprecated("use the parse() methods of the versioned fingerprint subclasses instead.")
|
||||
fun parseFromBinary(binaryFingerprint: ByteArray): OpenPgpFingerprint =
|
||||
parse(Hex.toHexString(binaryFingerprint))
|
||||
parse(Hex.toHexString(binaryFingerprint))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,22 +4,26 @@
|
|||
|
||||
package org.pgpainless.key
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
import java.net.URI
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
|
||||
class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
||||
class OpenPgpV4Fingerprint : OpenPgpFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(fingerprint: String) : super(fingerprint)
|
||||
|
||||
constructor(bytes: ByteArray) : super(bytes)
|
||||
|
||||
constructor(key: PGPPublicKey) : super(key)
|
||||
|
||||
constructor(key: PGPSecretKey) : super(key)
|
||||
|
||||
constructor(keys: PGPKeyRing) : super(keys)
|
||||
|
||||
override fun getVersion() = 4
|
||||
|
||||
|
@ -47,7 +51,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
|||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||
}
|
||||
append(' ')
|
||||
for (i in 5 .. 8) {
|
||||
for (i in 5..8) {
|
||||
append(fingerprint, i * 4, (i + 1) * 4).append(' ')
|
||||
}
|
||||
append(fingerprint, 36, 40)
|
||||
|
@ -55,8 +59,7 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
|||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val SCHEME = "openpgp4fpr"
|
||||
@JvmStatic val SCHEME = "openpgp4fpr"
|
||||
|
||||
@JvmStatic
|
||||
fun fromUri(uri: URI): OpenPgpV4Fingerprint {
|
||||
|
@ -66,4 +69,4 @@ class OpenPgpV4Fingerprint: OpenPgpFingerprint {
|
|||
return OpenPgpV4Fingerprint(uri.schemeSpecificPart)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing
|
|||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded uppercase OpenPGP v5 fingerprint.
|
||||
*/
|
||||
class OpenPgpV5Fingerprint: _64DigitFingerprint {
|
||||
/** This class represents a hex encoded uppercase OpenPGP v5 fingerprint. */
|
||||
class OpenPgpV5Fingerprint : _64DigitFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(fingerprint: String) : super(fingerprint)
|
||||
|
||||
constructor(key: PGPPublicKey) : super(key)
|
||||
|
||||
constructor(key: PGPSecretKey) : super(key)
|
||||
|
||||
constructor(keys: PGPKeyRing) : super(keys)
|
||||
|
||||
constructor(bytes: ByteArray) : super(bytes)
|
||||
|
||||
override fun getVersion(): Int {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,18 +8,20 @@ import org.bouncycastle.openpgp.PGPKeyRing
|
|||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded, uppercase OpenPGP v6 fingerprint.
|
||||
*/
|
||||
class OpenPgpV6Fingerprint: _64DigitFingerprint {
|
||||
/** This class represents a hex encoded, uppercase OpenPGP v6 fingerprint. */
|
||||
class OpenPgpV6Fingerprint : _64DigitFingerprint {
|
||||
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(fingerprint: String) : super(fingerprint)
|
||||
|
||||
constructor(key: PGPPublicKey) : super(key)
|
||||
|
||||
constructor(key: PGPSecretKey) : super(key)
|
||||
|
||||
constructor(keys: PGPKeyRing) : super(keys)
|
||||
|
||||
constructor(bytes: ByteArray) : super(bytes)
|
||||
|
||||
override fun getVersion(): Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,30 @@ import org.bouncycastle.openpgp.PGPPublicKey
|
|||
* as well as the subkeys fingerprint.
|
||||
*/
|
||||
class SubkeyIdentifier(
|
||||
val primaryKeyFingerprint: OpenPgpFingerprint,
|
||||
val subkeyFingerprint: OpenPgpFingerprint) {
|
||||
val primaryKeyFingerprint: OpenPgpFingerprint,
|
||||
val subkeyFingerprint: OpenPgpFingerprint
|
||||
) {
|
||||
|
||||
constructor(fingerprint: OpenPgpFingerprint): this(fingerprint, fingerprint)
|
||||
constructor(keys: PGPKeyRing): this(keys.publicKey)
|
||||
constructor(key: PGPPublicKey): this(OpenPgpFingerprint.of(key))
|
||||
constructor(keys: PGPKeyRing, keyId: Long): this(
|
||||
OpenPgpFingerprint.of(keys.publicKey),
|
||||
OpenPgpFingerprint.of(keys.getPublicKey(keyId) ?:
|
||||
throw NoSuchElementException("OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}")))
|
||||
constructor(keys: PGPKeyRing, subkeyFingerprint: OpenPgpFingerprint): this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
|
||||
constructor(fingerprint: OpenPgpFingerprint) : this(fingerprint, fingerprint)
|
||||
|
||||
constructor(keys: PGPKeyRing) : this(keys.publicKey)
|
||||
|
||||
constructor(key: PGPPublicKey) : this(OpenPgpFingerprint.of(key))
|
||||
|
||||
constructor(
|
||||
keys: PGPKeyRing,
|
||||
keyId: Long
|
||||
) : this(
|
||||
OpenPgpFingerprint.of(keys.publicKey),
|
||||
OpenPgpFingerprint.of(
|
||||
keys.getPublicKey(keyId)
|
||||
?: throw NoSuchElementException(
|
||||
"OpenPGP key does not contain subkey ${keyId.openPgpKeyId()}")))
|
||||
|
||||
constructor(
|
||||
keys: PGPKeyRing,
|
||||
subkeyFingerprint: OpenPgpFingerprint
|
||||
) : this(OpenPgpFingerprint.of(keys), subkeyFingerprint)
|
||||
|
||||
val keyId = subkeyFingerprint.keyId
|
||||
val fingerprint = subkeyFingerprint
|
||||
|
@ -34,7 +47,7 @@ class SubkeyIdentifier(
|
|||
val isPrimaryKey = primaryKeyId == subkeyId
|
||||
|
||||
fun matches(fingerprint: OpenPgpFingerprint) =
|
||||
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
|
||||
primaryKeyFingerprint == fingerprint || subkeyFingerprint == fingerprint
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null) {
|
||||
|
@ -47,7 +60,8 @@ class SubkeyIdentifier(
|
|||
return false
|
||||
}
|
||||
|
||||
return primaryKeyFingerprint == other.primaryKeyFingerprint && subkeyFingerprint == other.subkeyFingerprint
|
||||
return primaryKeyFingerprint == other.primaryKeyFingerprint &&
|
||||
subkeyFingerprint == other.subkeyFingerprint
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
|
@ -55,4 +69,4 @@ class SubkeyIdentifier(
|
|||
}
|
||||
|
||||
override fun toString(): String = "$subkeyFingerprint $primaryKeyFingerprint"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,47 +2,47 @@
|
|||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.key;
|
||||
package org.pgpainless.key
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.Charset
|
||||
import org.bouncycastle.openpgp.PGPKeyRing
|
||||
import org.bouncycastle.openpgp.PGPPublicKey
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
import org.bouncycastle.util.encoders.Hex
|
||||
|
||||
/**
|
||||
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint.
|
||||
* Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the
|
||||
* key version.
|
||||
* This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. Since both
|
||||
* fingerprints use the same format, this class is used when parsing the fingerprint without knowing
|
||||
* the key version.
|
||||
*/
|
||||
open class _64DigitFingerprint: OpenPgpFingerprint {
|
||||
open class _64DigitFingerprint : OpenPgpFingerprint {
|
||||
|
||||
/**
|
||||
* Create an {@link _64DigitFingerprint}.
|
||||
*
|
||||
* @param fingerprint uppercase hexadecimal fingerprint of length 64
|
||||
*/
|
||||
constructor(fingerprint: String): super(fingerprint)
|
||||
constructor(bytes: ByteArray): super(bytes)
|
||||
constructor(key: PGPPublicKey): super(key)
|
||||
constructor(key: PGPSecretKey): super(key)
|
||||
constructor(keys: PGPKeyRing): super(keys)
|
||||
constructor(fingerprint: String) : super(fingerprint)
|
||||
|
||||
constructor(bytes: ByteArray) : super(bytes)
|
||||
|
||||
constructor(key: PGPPublicKey) : super(key)
|
||||
|
||||
constructor(key: PGPSecretKey) : super(key)
|
||||
|
||||
constructor(keys: PGPKeyRing) : super(keys)
|
||||
|
||||
override val keyId: Long
|
||||
get() {
|
||||
val bytes = Hex.decode(fingerprint.toByteArray(Charset.forName("UTF-8")))
|
||||
val buf = ByteBuffer.wrap(bytes);
|
||||
val buf = ByteBuffer.wrap(bytes)
|
||||
|
||||
// The key id is the left-most 8 bytes (conveniently a long).
|
||||
// We have to cast here in order to be compatible with java 8
|
||||
// https://github.com/eclipse/jetty.project/issues/3244
|
||||
(buf as Buffer).position(0);
|
||||
(buf as Buffer).position(0)
|
||||
|
||||
return buf.getLong()
|
||||
}
|
||||
|
@ -62,13 +62,13 @@ open class _64DigitFingerprint: OpenPgpFingerprint {
|
|||
override fun prettyPrint(): String {
|
||||
return buildString {
|
||||
for (i in 0 until 4) {
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ')
|
||||
}
|
||||
append(' ');
|
||||
append(' ')
|
||||
for (i in 4 until 7) {
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ');
|
||||
append(fingerprint, i * 8, (i + 1) * 8).append(' ')
|
||||
}
|
||||
append(fingerprint, 56, 64);
|
||||
append(fingerprint, 56, 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.key.certification
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
@ -22,28 +23,27 @@ import org.pgpainless.key.util.KeyRingUtils
|
|||
import org.pgpainless.signature.builder.ThirdPartyCertificationSignatureBuilder
|
||||
import org.pgpainless.signature.builder.ThirdPartyDirectKeySignatureBuilder
|
||||
import org.pgpainless.signature.subpackets.CertificationSubpackets
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* API for creating certifications and delegations (Signatures) on keys.
|
||||
* This API can be used to sign another persons OpenPGP key.
|
||||
* API for creating certifications and delegations (Signatures) on keys. This API can be used to
|
||||
* sign another persons OpenPGP key.
|
||||
*
|
||||
* A certification over a user-id is thereby used to attest, that the user believes that the user-id really belongs
|
||||
* to the owner of the certificate.
|
||||
* A delegation over a key can be used to delegate trust by marking the certificate as a trusted introducer.
|
||||
* A certification over a user-id is thereby used to attest, that the user believes that the user-id
|
||||
* really belongs to the owner of the certificate. A delegation over a key can be used to delegate
|
||||
* trust by marking the certificate as a trusted introducer.
|
||||
*/
|
||||
class CertifyCertificate {
|
||||
|
||||
/**
|
||||
* Create a certification over a User-Id.
|
||||
* By default, this method will use [CertificationType.GENERIC] to create the signature.
|
||||
* Create a certification over a User-Id. By default, this method will use
|
||||
* [CertificationType.GENERIC] to create the signature.
|
||||
*
|
||||
* @param userId user-id to certify
|
||||
* @param certificate certificate
|
||||
* @return API
|
||||
*/
|
||||
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing): CertificationOnUserId =
|
||||
userIdOnCertificate(userId, certificate, CertificationType.GENERIC)
|
||||
userIdOnCertificate(userId, certificate, CertificationType.GENERIC)
|
||||
|
||||
/**
|
||||
* Create a certification of the given [CertificationType] over a User-Id.
|
||||
|
@ -53,36 +53,40 @@ class CertifyCertificate {
|
|||
* @param certificationType type of signature
|
||||
* @return API
|
||||
*/
|
||||
fun userIdOnCertificate(userId: String, certificate: PGPPublicKeyRing, certificationType: CertificationType) =
|
||||
CertificationOnUserId(userId, certificate, certificationType)
|
||||
fun userIdOnCertificate(
|
||||
userId: String,
|
||||
certificate: PGPPublicKeyRing,
|
||||
certificationType: CertificationType
|
||||
) = CertificationOnUserId(userId, certificate, certificationType)
|
||||
|
||||
/**
|
||||
* Create a delegation (direct key signature) over a certificate.
|
||||
* This can be used to mark a certificate as a trusted introducer
|
||||
* (see [certificate] method with [Trustworthiness] argument).
|
||||
* Create a delegation (direct key signature) over a certificate. This can be used to mark a
|
||||
* certificate as a trusted introducer (see [certificate] method with [Trustworthiness]
|
||||
* argument).
|
||||
*
|
||||
* @param certificate certificate
|
||||
* @return API
|
||||
*/
|
||||
fun certificate(certificate: PGPPublicKeyRing): DelegationOnCertificate =
|
||||
certificate(certificate, null)
|
||||
certificate(certificate, null)
|
||||
|
||||
/**
|
||||
* Create a delegation (direct key signature) containing a [org.bouncycastle.bcpg.sig.TrustSignature] packet
|
||||
* over a certificate.
|
||||
* This can be used to mark a certificate as a trusted introducer.
|
||||
* Create a delegation (direct key signature) containing a
|
||||
* [org.bouncycastle.bcpg.sig.TrustSignature] packet over a certificate. This can be used to
|
||||
* mark a certificate as a trusted introducer.
|
||||
*
|
||||
* @param certificate certificate
|
||||
* @param trustworthiness trustworthiness of the certificate
|
||||
* @return API
|
||||
*/
|
||||
fun certificate(certificate: PGPPublicKeyRing, trustworthiness: Trustworthiness?) =
|
||||
DelegationOnCertificate(certificate, trustworthiness)
|
||||
DelegationOnCertificate(certificate, trustworthiness)
|
||||
|
||||
class CertificationOnUserId(
|
||||
val userId: String,
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val certificationType: CertificationType) {
|
||||
val userId: String,
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val certificationType: CertificationType
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create the certification using the given key.
|
||||
|
@ -92,11 +96,14 @@ class CertifyCertificate {
|
|||
* @return API
|
||||
* @throws PGPException in case of an OpenPGP related error
|
||||
*/
|
||||
fun withKey(certificationKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector): CertificationOnUserIdWithSubpackets {
|
||||
fun withKey(
|
||||
certificationKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector
|
||||
): CertificationOnUserIdWithSubpackets {
|
||||
|
||||
val secretKey = getCertifyingSecretKey(certificationKey)
|
||||
val sigBuilder = ThirdPartyCertificationSignatureBuilder(
|
||||
val sigBuilder =
|
||||
ThirdPartyCertificationSignatureBuilder(
|
||||
certificationType.asSignatureType(), secretKey, protector)
|
||||
|
||||
return CertificationOnUserIdWithSubpackets(certificate, userId, sigBuilder)
|
||||
|
@ -104,9 +111,9 @@ class CertifyCertificate {
|
|||
}
|
||||
|
||||
class CertificationOnUserIdWithSubpackets(
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val userId: String,
|
||||
val sigBuilder: ThirdPartyCertificationSignatureBuilder
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val userId: String,
|
||||
val sigBuilder: ThirdPartyCertificationSignatureBuilder
|
||||
) {
|
||||
|
||||
/**
|
||||
|
@ -116,7 +123,9 @@ class CertifyCertificate {
|
|||
* @return result
|
||||
* @throws PGPException in case of an OpenPGP related error
|
||||
*/
|
||||
fun buildWithSubpackets(subpacketCallback: CertificationSubpackets.Callback): CertificationResult {
|
||||
fun buildWithSubpackets(
|
||||
subpacketCallback: CertificationSubpackets.Callback
|
||||
): CertificationResult {
|
||||
sigBuilder.applyCallback(subpacketCallback)
|
||||
return build()
|
||||
}
|
||||
|
@ -129,14 +138,16 @@ class CertifyCertificate {
|
|||
*/
|
||||
fun build(): CertificationResult {
|
||||
val signature = sigBuilder.build(certificate, userId)
|
||||
val certifiedCertificate = KeyRingUtils.injectCertification(certificate, userId, signature)
|
||||
val certifiedCertificate =
|
||||
KeyRingUtils.injectCertification(certificate, userId, signature)
|
||||
return CertificationResult(certifiedCertificate, signature)
|
||||
}
|
||||
}
|
||||
|
||||
class DelegationOnCertificate(
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val trustworthiness: Trustworthiness?) {
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val trustworthiness: Trustworthiness?
|
||||
) {
|
||||
|
||||
/**
|
||||
* Build the delegation using the given certification key.
|
||||
|
@ -146,20 +157,24 @@ class CertifyCertificate {
|
|||
* @return API
|
||||
* @throws PGPException in case of an OpenPGP related error
|
||||
*/
|
||||
fun withKey(certificationKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector): DelegationOnCertificateWithSubpackets {
|
||||
fun withKey(
|
||||
certificationKey: PGPSecretKeyRing,
|
||||
protector: SecretKeyRingProtector
|
||||
): DelegationOnCertificateWithSubpackets {
|
||||
val secretKey = getCertifyingSecretKey(certificationKey)
|
||||
val sigBuilder = ThirdPartyDirectKeySignatureBuilder(secretKey, protector)
|
||||
if (trustworthiness != null) {
|
||||
sigBuilder.hashedSubpackets.setTrust(true, trustworthiness.depth, trustworthiness.amount)
|
||||
sigBuilder.hashedSubpackets.setTrust(
|
||||
true, trustworthiness.depth, trustworthiness.amount)
|
||||
}
|
||||
return DelegationOnCertificateWithSubpackets(certificate, sigBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
class DelegationOnCertificateWithSubpackets(
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val sigBuilder: ThirdPartyDirectKeySignatureBuilder) {
|
||||
val certificate: PGPPublicKeyRing,
|
||||
val sigBuilder: ThirdPartyDirectKeySignatureBuilder
|
||||
) {
|
||||
|
||||
/**
|
||||
* Apply the given signature subpackets and build the delegation signature.
|
||||
|
@ -168,7 +183,9 @@ class CertifyCertificate {
|
|||
* @return result
|
||||
* @throws PGPException in case of an OpenPGP related error
|
||||
*/
|
||||
fun buildWithSubpackets(subpacketsCallback: CertificationSubpackets.Callback): CertificationResult {
|
||||
fun buildWithSubpackets(
|
||||
subpacketsCallback: CertificationSubpackets.Callback
|
||||
): CertificationResult {
|
||||
sigBuilder.applyCallback(subpacketsCallback)
|
||||
return build()
|
||||
}
|
||||
|
@ -182,7 +199,8 @@ class CertifyCertificate {
|
|||
fun build(): CertificationResult {
|
||||
val delegatedKey = certificate.publicKey
|
||||
val delegation = sigBuilder.build(delegatedKey)
|
||||
val delegatedCertificate = KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
|
||||
val delegatedCertificate =
|
||||
KeyRingUtils.injectCertification(certificate, delegatedKey, delegation)
|
||||
return CertificationResult(delegatedCertificate, delegation)
|
||||
}
|
||||
}
|
||||
|
@ -194,8 +212,9 @@ class CertifyCertificate {
|
|||
* @param certification the newly created signature
|
||||
*/
|
||||
data class CertificationResult(
|
||||
val certifiedCertificate: PGPPublicKeyRing,
|
||||
val certification: PGPSignature)
|
||||
val certifiedCertificate: PGPPublicKeyRing,
|
||||
val certification: PGPSignature
|
||||
)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
@ -205,9 +224,7 @@ class CertifyCertificate {
|
|||
|
||||
val fingerprint = info.fingerprint
|
||||
val certificationPubKey = info.getPublicKey(fingerprint)
|
||||
requireNotNull(certificationPubKey) {
|
||||
"Primary key cannot be null."
|
||||
}
|
||||
requireNotNull(certificationPubKey) { "Primary key cannot be null." }
|
||||
if (!info.isKeyValidlyBound(certificationPubKey.keyID)) {
|
||||
throw RevokedKeyException(fingerprint)
|
||||
}
|
||||
|
@ -222,7 +239,7 @@ class CertifyCertificate {
|
|||
}
|
||||
|
||||
return certificationKey.getSecretKey(certificationPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
|
||||
?: throw MissingSecretKeyException(fingerprint, certificationPubKey.keyID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,32 +4,38 @@
|
|||
|
||||
package org.pgpainless.key.collection
|
||||
|
||||
import java.io.InputStream
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.pgpainless.implementation.ImplementationFactory
|
||||
import org.pgpainless.util.ArmorUtils
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was inspired by
|
||||
* [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
|
||||
* This class describes a logic of handling a collection of different [PGPKeyRing]. The logic was
|
||||
* inspired by [PGPSecretKeyRingCollection] and [PGPPublicKeyRingCollection].
|
||||
*/
|
||||
class PGPKeyRingCollection(
|
||||
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
|
||||
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
|
||||
val pgpSecretKeyRingCollection: PGPSecretKeyRingCollection,
|
||||
val pgpPublicKeyRingCollection: PGPPublicKeyRingCollection
|
||||
) {
|
||||
|
||||
constructor(encoding: ByteArray, isSilent: Boolean): this(encoding.inputStream(), isSilent)
|
||||
constructor(encoding: ByteArray, isSilent: Boolean) : this(encoding.inputStream(), isSilent)
|
||||
|
||||
constructor(inputStream: InputStream, isSilent: Boolean): this(parse(inputStream, isSilent))
|
||||
constructor(inputStream: InputStream, isSilent: Boolean) : this(parse(inputStream, isSilent))
|
||||
|
||||
constructor(collection: Collection<PGPKeyRing>, isSilent: Boolean): this(segment(collection, isSilent))
|
||||
constructor(
|
||||
collection: Collection<PGPKeyRing>,
|
||||
isSilent: Boolean
|
||||
) : this(segment(collection, isSilent))
|
||||
|
||||
private constructor(arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>): this(arguments.first, arguments.second)
|
||||
private constructor(
|
||||
arguments: Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection>
|
||||
) : this(arguments.first, arguments.second)
|
||||
|
||||
/**
|
||||
* The number of rings in this collection.
|
||||
*
|
||||
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this collection
|
||||
* @return total size of [PGPSecretKeyRingCollection] plus [PGPPublicKeyRingCollection] in this
|
||||
* collection
|
||||
*/
|
||||
val size: Int
|
||||
get() = pgpSecretKeyRingCollection.size() + pgpPublicKeyRingCollection.size()
|
||||
|
@ -41,11 +47,16 @@ class PGPKeyRingCollection(
|
|||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private fun parse(inputStream: InputStream, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||
private fun parse(
|
||||
inputStream: InputStream,
|
||||
isSilent: Boolean
|
||||
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
||||
val certificates = mutableListOf<PGPPublicKeyRing>()
|
||||
// Double getDecoderStream because of #96
|
||||
val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream))
|
||||
val objectFactory =
|
||||
ImplementationFactory.getInstance()
|
||||
.getPGPObjectFactory(ArmorUtils.getDecoderStream(inputStream))
|
||||
|
||||
for (obj in objectFactory) {
|
||||
if (obj == null) {
|
||||
|
@ -68,16 +79,21 @@ class PGPKeyRingCollection(
|
|||
}
|
||||
|
||||
if (!isSilent) {
|
||||
throw PGPException("${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||
throw PGPException(
|
||||
"${obj.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
||||
}
|
||||
}
|
||||
|
||||
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
|
||||
return PGPSecretKeyRingCollection(secretKeyRings) to
|
||||
PGPPublicKeyRingCollection(certificates)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun segment(collection: Collection<PGPKeyRing>, isSilent: Boolean): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||
private fun segment(
|
||||
collection: Collection<PGPKeyRing>,
|
||||
isSilent: Boolean
|
||||
): Pair<PGPSecretKeyRingCollection, PGPPublicKeyRingCollection> {
|
||||
val secretKeyRings = mutableListOf<PGPSecretKeyRing>()
|
||||
val certificates = mutableListOf<PGPPublicKeyRing>()
|
||||
|
||||
|
@ -87,12 +103,14 @@ class PGPKeyRingCollection(
|
|||
} else if (keyRing is PGPPublicKeyRing) {
|
||||
certificates.add(keyRing)
|
||||
} else if (!isSilent) {
|
||||
throw PGPException("${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||
throw PGPException(
|
||||
"${keyRing.javaClass.name} found where ${PGPSecretKeyRing::class.java.simpleName}" +
|
||||
" or ${PGPPublicKeyRing::class.java.simpleName} expected")
|
||||
}
|
||||
}
|
||||
|
||||
return PGPSecretKeyRingCollection(secretKeyRings) to PGPPublicKeyRingCollection(certificates)
|
||||
return PGPSecretKeyRingCollection(secretKeyRings) to
|
||||
PGPPublicKeyRingCollection(certificates)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import java.io.IOException
|
||||
import java.security.KeyPairGenerator
|
||||
import java.util.*
|
||||
import org.bouncycastle.extensions.unlock
|
||||
import org.bouncycastle.openpgp.*
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor
|
||||
|
@ -21,10 +24,6 @@ import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
|||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.io.IOException
|
||||
import java.security.KeyPairGenerator
|
||||
import java.util.*
|
||||
|
||||
|
||||
class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
||||
|
||||
|
@ -49,19 +48,19 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
userIds[userId.toString().trim()] = null
|
||||
}
|
||||
|
||||
override fun addUserId(userId: ByteArray): KeyRingBuilder = addUserId(Strings.fromUTF8ByteArray(userId))
|
||||
override fun addUserId(userId: ByteArray): KeyRingBuilder =
|
||||
addUserId(Strings.fromUTF8ByteArray(userId))
|
||||
|
||||
override fun setExpirationDate(expirationDate: Date?): KeyRingBuilder = apply {
|
||||
if (expirationDate == null) {
|
||||
this.expirationDate = null
|
||||
return@apply
|
||||
}
|
||||
this.expirationDate = expirationDate.let {
|
||||
require(Date() < expirationDate) {
|
||||
"Expiration date must be in the future."
|
||||
this.expirationDate =
|
||||
expirationDate.let {
|
||||
require(Date() < expirationDate) { "Expiration date must be in the future." }
|
||||
expirationDate
|
||||
}
|
||||
expirationDate
|
||||
}
|
||||
}
|
||||
|
||||
override fun setPassphrase(passphrase: Passphrase): KeyRingBuilder = apply {
|
||||
|
@ -85,17 +84,14 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
private fun keyIsCertificationCapable(keySpec: KeySpec) = keySpec.keyType.canCertify
|
||||
|
||||
override fun build(): PGPSecretKeyRing {
|
||||
val keyFingerprintCalculator = ImplementationFactory.getInstance()
|
||||
.v4FingerprintCalculator
|
||||
val keyFingerprintCalculator = ImplementationFactory.getInstance().v4FingerprintCalculator
|
||||
val secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator)
|
||||
val secretKeyDecryptor = buildSecretKeyDecryptor()
|
||||
|
||||
passphrase.clear() // Passphrase was used above, so we can get rid of it
|
||||
|
||||
// generate primary key
|
||||
requireNotNull(primaryKeySpec) {
|
||||
"Primary Key spec required."
|
||||
}
|
||||
requireNotNull(primaryKeySpec) { "Primary Key spec required." }
|
||||
val certKey = generateKeyPair(primaryKeySpec!!)
|
||||
val signer = buildContentSigner(certKey)
|
||||
val signatureGenerator = PGPSignatureGenerator(signer)
|
||||
|
@ -110,16 +106,28 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
val generator = PGPSignatureSubpacketGenerator()
|
||||
SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator)
|
||||
val hashedSubPackets = generator.generate()
|
||||
val ringGenerator = if (userIds.isEmpty()) {
|
||||
PGPKeyRingGenerator(certKey, keyFingerprintCalculator, hashedSubPackets, null, signer,
|
||||
val ringGenerator =
|
||||
if (userIds.isEmpty()) {
|
||||
PGPKeyRingGenerator(
|
||||
certKey,
|
||||
keyFingerprintCalculator,
|
||||
hashedSubPackets,
|
||||
null,
|
||||
signer,
|
||||
secretKeyEncryptor)
|
||||
} else {
|
||||
userIds.keys.first().let { primaryUserId ->
|
||||
PGPKeyRingGenerator(SignatureType.POSITIVE_CERTIFICATION.code, certKey, primaryUserId,
|
||||
keyFingerprintCalculator, hashedSubPackets, null, signer,
|
||||
} else {
|
||||
userIds.keys.first().let { primaryUserId ->
|
||||
PGPKeyRingGenerator(
|
||||
SignatureType.POSITIVE_CERTIFICATION.code,
|
||||
certKey,
|
||||
primaryUserId,
|
||||
keyFingerprintCalculator,
|
||||
hashedSubPackets,
|
||||
null,
|
||||
signer,
|
||||
secretKeyEncryptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSubKeys(certKey, ringGenerator)
|
||||
|
||||
|
@ -138,20 +146,26 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
val additionalUserId = userIdIterator.next()
|
||||
val userIdString = additionalUserId.key
|
||||
val callback = additionalUserId.value
|
||||
val subpackets = if (callback == null) {
|
||||
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
|
||||
} else {
|
||||
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also { callback.modifyHashedSubpackets(it) }
|
||||
}
|
||||
val subpackets =
|
||||
if (callback == null) {
|
||||
hashedSubPacketGenerator.also { it.setPrimaryUserId(null) }
|
||||
} else {
|
||||
SignatureSubpackets.createHashedSubpackets(primaryPubKey).also {
|
||||
callback.modifyHashedSubpackets(it)
|
||||
}
|
||||
}
|
||||
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.code, privateKey)
|
||||
signatureGenerator.setHashedSubpackets(
|
||||
SignatureSubpacketsHelper.toVector(subpackets))
|
||||
val additionalUserIdSignature = signatureGenerator.generateCertification(userIdString, primaryPubKey)
|
||||
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey, userIdString, additionalUserIdSignature)
|
||||
signatureGenerator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(subpackets))
|
||||
val additionalUserIdSignature =
|
||||
signatureGenerator.generateCertification(userIdString, primaryPubKey)
|
||||
primaryPubKey =
|
||||
PGPPublicKey.addCertification(
|
||||
primaryPubKey, userIdString, additionalUserIdSignature)
|
||||
}
|
||||
|
||||
// Reassemble secret key ring with modified primary key
|
||||
val primarySecretKey = PGPSecretKey(
|
||||
val primarySecretKey =
|
||||
PGPSecretKey(
|
||||
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor)
|
||||
val secretKeyList = mutableListOf(primarySecretKey)
|
||||
while (secretKeys.hasNext()) {
|
||||
|
@ -168,25 +182,34 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
} else {
|
||||
var hashedSubpackets = subKeySpec.subpackets
|
||||
try {
|
||||
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets)
|
||||
hashedSubpackets =
|
||||
addPrimaryKeyBindingSignatureIfNecessary(
|
||||
primaryKey, subKey, hashedSubpackets)
|
||||
} catch (e: IOException) {
|
||||
throw PGPException("Exception while adding primary key binding signature to signing subkey.", e)
|
||||
throw PGPException(
|
||||
"Exception while adding primary key binding signature to signing subkey.",
|
||||
e)
|
||||
}
|
||||
ringGenerator.addSubKey(subKey, hashedSubpackets, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addPrimaryKeyBindingSignatureIfNecessary(primaryKey: PGPKeyPair, subKey: PGPKeyPair, hashedSubpackets: PGPSignatureSubpacketVector): PGPSignatureSubpacketVector {
|
||||
private fun addPrimaryKeyBindingSignatureIfNecessary(
|
||||
primaryKey: PGPKeyPair,
|
||||
subKey: PGPKeyPair,
|
||||
hashedSubpackets: PGPSignatureSubpacketVector
|
||||
): PGPSignatureSubpacketVector {
|
||||
val keyFlagMask = hashedSubpackets.keyFlags
|
||||
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
|
||||
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
|
||||
!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
|
||||
return hashedSubpackets
|
||||
}
|
||||
|
||||
val bindingSignatureGenerator = PGPSignatureGenerator(buildContentSigner(subKey))
|
||||
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.code, subKey.privateKey)
|
||||
val primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
|
||||
val primaryKeyBindingSig =
|
||||
bindingSignatureGenerator.generateCertification(primaryKey.publicKey, subKey.publicKey)
|
||||
val subpacketGenerator = PGPSignatureSubpacketGenerator(hashedSubpackets)
|
||||
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig)
|
||||
return subpacketGenerator.generate()
|
||||
|
@ -194,25 +217,29 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
|
||||
private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder {
|
||||
val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm
|
||||
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
|
||||
certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
|
||||
return ImplementationFactory.getInstance()
|
||||
.getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId)
|
||||
}
|
||||
|
||||
private fun buildSecretKeyEncryptor(keyFingerprintCalculator: PGPDigestCalculator): PBESecretKeyEncryptor? {
|
||||
val keyEncryptionAlgorithm = PGPainless.getPolicy().symmetricKeyEncryptionAlgorithmPolicy.defaultSymmetricKeyAlgorithm
|
||||
check(passphrase.isValid) {
|
||||
"Passphrase was cleared."
|
||||
}
|
||||
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance()
|
||||
.getPBESecretKeyEncryptor(keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
|
||||
private fun buildSecretKeyEncryptor(
|
||||
keyFingerprintCalculator: PGPDigestCalculator
|
||||
): PBESecretKeyEncryptor? {
|
||||
val keyEncryptionAlgorithm =
|
||||
PGPainless.getPolicy()
|
||||
.symmetricKeyEncryptionAlgorithmPolicy
|
||||
.defaultSymmetricKeyAlgorithm
|
||||
check(passphrase.isValid) { "Passphrase was cleared." }
|
||||
return if (passphrase.isEmpty) null
|
||||
else
|
||||
ImplementationFactory.getInstance()
|
||||
.getPBESecretKeyEncryptor(
|
||||
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase)
|
||||
}
|
||||
|
||||
private fun buildSecretKeyDecryptor(): PBESecretKeyDecryptor? {
|
||||
check(passphrase.isValid) {
|
||||
"Passphrase was cleared."
|
||||
}
|
||||
return if (passphrase.isEmpty) null else ImplementationFactory.getInstance()
|
||||
.getPBESecretKeyDecryptor(passphrase)
|
||||
check(passphrase.isValid) { "Passphrase was cleared." }
|
||||
return if (passphrase.isEmpty) null
|
||||
else ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -222,16 +249,16 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
|
|||
fun generateKeyPair(spec: KeySpec): PGPKeyPair {
|
||||
spec.keyType.let { type ->
|
||||
// Create raw Key Pair
|
||||
val keyPair = KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
|
||||
.also { it.initialize(type.algorithmSpec) }
|
||||
.generateKeyPair()
|
||||
val keyPair =
|
||||
KeyPairGenerator.getInstance(type.name, ProviderFactory.provider)
|
||||
.also { it.initialize(type.algorithmSpec) }
|
||||
.generateKeyPair()
|
||||
|
||||
val keyCreationDate = spec.keyCreationDate ?: Date()
|
||||
// Form PGP Key Pair
|
||||
return ImplementationFactory.getInstance()
|
||||
.getPGPKeyPair(type.algorithm, keyPair, keyCreationDate)
|
||||
.getPGPKeyPair(type.algorithm, keyPair, keyCreationDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||
import org.pgpainless.util.Passphrase
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPException
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||
import org.pgpainless.util.Passphrase
|
||||
|
||||
interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
||||
|
||||
|
@ -29,6 +29,9 @@ interface KeyRingBuilderInterface<B : KeyRingBuilderInterface<B>> {
|
|||
|
||||
fun setPassphrase(passphrase: Passphrase): B
|
||||
|
||||
@Throws(NoSuchAlgorithmException::class, PGPException::class, InvalidAlgorithmParameterException::class)
|
||||
@Throws(
|
||||
NoSuchAlgorithmException::class,
|
||||
PGPException::class,
|
||||
InvalidAlgorithmParameterException::class)
|
||||
fun build(): PGPSecretKeyRing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ import org.pgpainless.util.Passphrase
|
|||
class KeyRingTemplates {
|
||||
|
||||
/**
|
||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
|
||||
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
|
||||
* dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||
*
|
||||
* @param userId userId or null
|
||||
* @param length length of the RSA keys
|
||||
|
@ -26,160 +26,187 @@ class KeyRingTemplates {
|
|||
* @return key
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun rsaKeyRing(userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
||||
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
|
||||
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
|
||||
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId)
|
||||
}
|
||||
}.build()
|
||||
fun rsaKeyRing(
|
||||
userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing =
|
||||
buildKeyRing()
|
||||
.apply {
|
||||
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER))
|
||||
addSubkey(getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA))
|
||||
addSubkey(
|
||||
getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification,
|
||||
* a dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||
* Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, a
|
||||
* dedicated RSA subkey used for signing and a third RSA subkey used for encryption.
|
||||
*
|
||||
* @param userId userId or null
|
||||
* @param length length of the RSA keys
|
||||
* @param password passphrase to encrypt the key with. Can be null or blank for unencrypted keys.
|
||||
* @param password passphrase to encrypt the key with. Can be null or blank for unencrypted
|
||||
* keys.
|
||||
* @return key
|
||||
*/
|
||||
fun rsaKeyRing(userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
password: String?
|
||||
): PGPSecretKeyRing = password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
rsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||
fun rsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?): PGPSecretKeyRing =
|
||||
password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
rsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
rsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
|
||||
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
|
||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
|
||||
* KeyPair consists of a single RSA master key which is used for signing, encryption and
|
||||
* certification.
|
||||
*
|
||||
* @param userId user id.
|
||||
* @param length length in bits.
|
||||
* @param password Password of the key. Can be empty for unencrypted keys.
|
||||
*
|
||||
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun simpleRsaKeyRing(userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
||||
setPrimaryKey(getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId.toString())
|
||||
}
|
||||
}.build()
|
||||
fun simpleRsaKeyRing(
|
||||
userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing =
|
||||
buildKeyRing()
|
||||
.apply {
|
||||
setPrimaryKey(
|
||||
getBuilder(
|
||||
KeyType.RSA(length),
|
||||
KeyFlag.CERTIFY_OTHER,
|
||||
KeyFlag.SIGN_DATA,
|
||||
KeyFlag.ENCRYPT_COMMS))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId.toString())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
|
||||
* The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
|
||||
* Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. The
|
||||
* KeyPair consists of a single RSA master key which is used for signing, encryption and
|
||||
* certification.
|
||||
*
|
||||
* @param userId user id.
|
||||
* @param length length in bits.
|
||||
* @param password Password of the key. Can be null or blank for unencrypted keys.
|
||||
*
|
||||
* @return {@link PGPSecretKeyRing} containing the KeyPair.
|
||||
*/
|
||||
fun simpleRsaKeyRing(userId: CharSequence?,
|
||||
length: RsaLength,
|
||||
password: String?
|
||||
) = password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||
fun simpleRsaKeyRing(userId: CharSequence?, length: RsaLength, password: String?) =
|
||||
password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
simpleRsaKeyRing(userId, length, Passphrase.fromPassword(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey.
|
||||
* The EdDSA primary key is used for signing messages and certifying the sub key.
|
||||
* The XDH subkey is used for encryption and decryption of messages.
|
||||
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
|
||||
* EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
|
||||
* used for encryption and decryption of messages.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @param passphrase Password of the private key. Can be empty for an unencrypted key.
|
||||
*
|
||||
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun simpleEcKeyRing(userId: CharSequence?,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
||||
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
|
||||
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId.toString())
|
||||
}
|
||||
}.build()
|
||||
fun simpleEcKeyRing(
|
||||
userId: CharSequence?,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing =
|
||||
buildKeyRing()
|
||||
.apply {
|
||||
setPrimaryKey(
|
||||
getBuilder(
|
||||
KeyType.EDDSA(EdDSACurve._Ed25519),
|
||||
KeyFlag.CERTIFY_OTHER,
|
||||
KeyFlag.SIGN_DATA))
|
||||
addSubkey(
|
||||
getBuilder(
|
||||
KeyType.XDH(XDHSpec._X25519),
|
||||
KeyFlag.ENCRYPT_STORAGE,
|
||||
KeyFlag.ENCRYPT_COMMS))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId.toString())
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey.
|
||||
* The EdDSA primary key is used for signing messages and certifying the sub key.
|
||||
* The XDH subkey is used for encryption and decryption of messages.
|
||||
* Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. The
|
||||
* EdDSA primary key is used for signing messages and certifying the sub key. The XDH subkey is
|
||||
* used for encryption and decryption of messages.
|
||||
*
|
||||
* @param userId user-id
|
||||
* @param passphrase Password of the private key. Can be null or blank for an unencrypted key.
|
||||
*
|
||||
* @return {@link PGPSecretKeyRing} containing the key pairs.
|
||||
*/
|
||||
fun simpleEcKeyRing(userId: CharSequence?,
|
||||
password: String?
|
||||
): PGPSecretKeyRing = password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
simpleEcKeyRing(userId, Passphrase.fromPassword(it))
|
||||
fun simpleEcKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
|
||||
password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
simpleEcKeyRing(userId, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
simpleEcKeyRing(userId, Passphrase.fromPassword(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify
|
||||
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
|
||||
* certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||
*
|
||||
* @param userId primary user id
|
||||
* @param passphrase passphrase for the private key. Can be empty for an unencrypted key.
|
||||
* @return key ring
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun modernKeyRing(userId: CharSequence?,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing = buildKeyRing().apply {
|
||||
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
addSubkey(getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId)
|
||||
}
|
||||
}.build()
|
||||
fun modernKeyRing(
|
||||
userId: CharSequence?,
|
||||
passphrase: Passphrase = Passphrase.emptyPassphrase()
|
||||
): PGPSecretKeyRing =
|
||||
buildKeyRing()
|
||||
.apply {
|
||||
setPrimaryKey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
addSubkey(
|
||||
getBuilder(
|
||||
KeyType.XDH(XDHSpec._X25519),
|
||||
KeyFlag.ENCRYPT_COMMS,
|
||||
KeyFlag.ENCRYPT_STORAGE))
|
||||
addSubkey(getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
|
||||
setPassphrase(passphrase)
|
||||
if (userId != null) {
|
||||
addUserId(userId)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to certify
|
||||
* an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||
* Generate a modern PGP key ring consisting of an ed25519 EdDSA primary key which is used to
|
||||
* certify an X25519 XDH encryption subkey and an ed25519 EdDSA signing key.
|
||||
*
|
||||
* @param userId primary user id
|
||||
* @param password passphrase for the private key. Can be null or blank for an unencrypted key.
|
||||
* @return key ring
|
||||
*/
|
||||
fun modernKeyRing(userId: CharSequence?,
|
||||
password: String?
|
||||
): PGPSecretKeyRing = password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
modernKeyRing(userId, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
modernKeyRing(userId, Passphrase.fromPassword(it))
|
||||
fun modernKeyRing(userId: CharSequence?, password: String?): PGPSecretKeyRing =
|
||||
password.let {
|
||||
if (it.isNullOrBlank()) {
|
||||
modernKeyRing(userId, Passphrase.emptyPassphrase())
|
||||
} else {
|
||||
modernKeyRing(userId, Passphrase.fromPassword(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,18 +4,18 @@
|
|||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import java.util.*
|
||||
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector
|
||||
import org.pgpainless.algorithm.KeyFlag
|
||||
import org.pgpainless.key.generation.type.KeyType
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper
|
||||
import java.util.*
|
||||
|
||||
data class KeySpec(
|
||||
val keyType: KeyType,
|
||||
val subpacketGenerator: SignatureSubpackets,
|
||||
val isInheritedSubPackets: Boolean,
|
||||
val keyCreationDate: Date
|
||||
val keyType: KeyType,
|
||||
val subpacketGenerator: SignatureSubpackets,
|
||||
val isInheritedSubPackets: Boolean,
|
||||
val keyCreationDate: Date
|
||||
) {
|
||||
|
||||
val subpackets: PGPSignatureSubpacketVector
|
||||
|
@ -25,4 +25,4 @@ data class KeySpec(
|
|||
@JvmStatic
|
||||
fun getBuilder(type: KeyType, vararg flags: KeyFlag) = KeySpecBuilder(type, *flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,41 +4,47 @@
|
|||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import java.util.*
|
||||
import org.pgpainless.PGPainless
|
||||
import org.pgpainless.algorithm.*
|
||||
import org.pgpainless.key.generation.type.KeyType
|
||||
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpackets
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||
import java.util.*
|
||||
|
||||
class KeySpecBuilder constructor(
|
||||
private val type: KeyType,
|
||||
private val keyFlags: List<KeyFlag>,
|
||||
class KeySpecBuilder
|
||||
constructor(
|
||||
private val type: KeyType,
|
||||
private val keyFlags: List<KeyFlag>,
|
||||
) : KeySpecBuilderInterface {
|
||||
|
||||
private val hashedSubpackets: SelfSignatureSubpackets = SignatureSubpackets()
|
||||
private val algorithmSuite: AlgorithmSuite = PGPainless.getPolicy().keyGenerationAlgorithmSuite
|
||||
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> = algorithmSuite.compressionAlgorithms
|
||||
private var preferredCompressionAlgorithms: Set<CompressionAlgorithm> =
|
||||
algorithmSuite.compressionAlgorithms
|
||||
private var preferredHashAlgorithms: Set<HashAlgorithm> = algorithmSuite.hashAlgorithms
|
||||
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> = algorithmSuite.symmetricKeyAlgorithms
|
||||
private var preferredSymmetricAlgorithms: Set<SymmetricKeyAlgorithm> =
|
||||
algorithmSuite.symmetricKeyAlgorithms
|
||||
private var keyCreationDate = Date()
|
||||
|
||||
constructor(type: KeyType, vararg keyFlags: KeyFlag): this(type, listOf(*keyFlags))
|
||||
constructor(type: KeyType, vararg keyFlags: KeyFlag) : this(type, listOf(*keyFlags))
|
||||
|
||||
init {
|
||||
SignatureSubpacketsUtil.assureKeyCanCarryFlags(type, *keyFlags.toTypedArray())
|
||||
}
|
||||
|
||||
override fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder = apply {
|
||||
this.preferredCompressionAlgorithms = algorithms.toSet()
|
||||
}
|
||||
override fun overridePreferredCompressionAlgorithms(
|
||||
vararg algorithms: CompressionAlgorithm
|
||||
): KeySpecBuilder = apply { this.preferredCompressionAlgorithms = algorithms.toSet() }
|
||||
|
||||
override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder = apply {
|
||||
this.preferredHashAlgorithms = algorithms.toSet()
|
||||
}
|
||||
override fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder =
|
||||
apply {
|
||||
this.preferredHashAlgorithms = algorithms.toSet()
|
||||
}
|
||||
|
||||
override fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder = apply {
|
||||
override fun overridePreferredSymmetricKeyAlgorithms(
|
||||
vararg algorithms: SymmetricKeyAlgorithm
|
||||
): KeySpecBuilder = apply {
|
||||
require(!algorithms.contains(SymmetricKeyAlgorithm.NULL)) {
|
||||
"NULL (unencrypted) is an invalid symmetric key algorithm preference."
|
||||
}
|
||||
|
@ -50,14 +56,14 @@ class KeySpecBuilder constructor(
|
|||
}
|
||||
|
||||
override fun build(): KeySpec {
|
||||
return hashedSubpackets.apply {
|
||||
setKeyFlags(keyFlags)
|
||||
setPreferredCompressionAlgorithms(preferredCompressionAlgorithms)
|
||||
setPreferredHashAlgorithms(preferredHashAlgorithms)
|
||||
setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms)
|
||||
setFeatures(Feature.MODIFICATION_DETECTION)
|
||||
}.let {
|
||||
KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate)
|
||||
}
|
||||
return hashedSubpackets
|
||||
.apply {
|
||||
setKeyFlags(keyFlags)
|
||||
setPreferredCompressionAlgorithms(preferredCompressionAlgorithms)
|
||||
setPreferredHashAlgorithms(preferredHashAlgorithms)
|
||||
setPreferredSymmetricKeyAlgorithms(preferredSymmetricAlgorithms)
|
||||
setFeatures(Feature.MODIFICATION_DETECTION)
|
||||
}
|
||||
.let { KeySpec(type, hashedSubpackets as SignatureSubpackets, false, keyCreationDate) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,24 @@
|
|||
|
||||
package org.pgpainless.key.generation
|
||||
|
||||
import java.util.*
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||
import org.pgpainless.algorithm.HashAlgorithm
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
||||
import java.util.*
|
||||
|
||||
interface KeySpecBuilderInterface {
|
||||
|
||||
fun overridePreferredCompressionAlgorithms(vararg algorithms: CompressionAlgorithm): KeySpecBuilder
|
||||
fun overridePreferredCompressionAlgorithms(
|
||||
vararg algorithms: CompressionAlgorithm
|
||||
): KeySpecBuilder
|
||||
|
||||
fun overridePreferredHashAlgorithms(vararg algorithms: HashAlgorithm): KeySpecBuilder
|
||||
|
||||
fun overridePreferredSymmetricKeyAlgorithms(vararg algorithms: SymmetricKeyAlgorithm): KeySpecBuilder
|
||||
fun overridePreferredSymmetricKeyAlgorithms(
|
||||
vararg algorithms: SymmetricKeyAlgorithm
|
||||
): KeySpecBuilder
|
||||
|
||||
fun setKeyCreationDate(creationDate: Date): KeySpecBuilder
|
||||
|
||||
fun build(): KeySpec
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@ package org.pgpainless.key.generation.type
|
|||
interface KeyLength {
|
||||
|
||||
val length: Int
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
package org.pgpainless.key.generation.type
|
||||
|
||||
import java.security.spec.AlgorithmParameterSpec
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.key.generation.type.ecc.EllipticCurve
|
||||
import org.pgpainless.key.generation.type.ecc.ecdh.ECDH
|
||||
|
@ -14,7 +15,6 @@ import org.pgpainless.key.generation.type.rsa.RSA
|
|||
import org.pgpainless.key.generation.type.rsa.RsaLength
|
||||
import org.pgpainless.key.generation.type.xdh.XDH
|
||||
import org.pgpainless.key.generation.type.xdh.XDHSpec
|
||||
import java.security.spec.AlgorithmParameterSpec
|
||||
|
||||
@Suppress("INAPPLICABLE_JVM_NAME") // https://youtrack.jetbrains.com/issue/KT-31420
|
||||
interface KeyType {
|
||||
|
@ -35,20 +35,22 @@ interface KeyType {
|
|||
|
||||
/**
|
||||
* Return the strength of the key in bits.
|
||||
*
|
||||
* @return strength of the key in bits
|
||||
*/
|
||||
val bitStrength: Int
|
||||
|
||||
/**
|
||||
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the key.
|
||||
* Return an implementation of {@link AlgorithmParameterSpec} that can be used to generate the
|
||||
* key.
|
||||
*
|
||||
* @return algorithm parameter spec
|
||||
*/
|
||||
val algorithmSpec: AlgorithmParameterSpec
|
||||
|
||||
/**
|
||||
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key flag.
|
||||
* See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
|
||||
* Return true if the key that is generated from this type is able to carry the SIGN_DATA key
|
||||
* flag. See {@link org.pgpainless.algorithm.KeyFlag#SIGN_DATA}.
|
||||
*
|
||||
* @return true if the key can sign.
|
||||
*/
|
||||
|
@ -56,8 +58,8 @@ interface KeyType {
|
|||
@JvmName("canSign") get() = algorithm.signingCapable
|
||||
|
||||
/**
|
||||
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER key flag.
|
||||
* See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
|
||||
* Return true if the key that is generated from this type is able to carry the CERTIFY_OTHER
|
||||
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#CERTIFY_OTHER}.
|
||||
*
|
||||
* @return true if the key is able to certify other keys
|
||||
*/
|
||||
|
@ -65,8 +67,8 @@ interface KeyType {
|
|||
@JvmName("canCertify") get() = canSign
|
||||
|
||||
/**
|
||||
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION key flag.
|
||||
* See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
|
||||
* Return true if the key that is generated from this type is able to carry the AUTHENTICATION
|
||||
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#AUTHENTICATION}.
|
||||
*
|
||||
* @return true if the key can be used for authentication purposes.
|
||||
*/
|
||||
|
@ -74,8 +76,8 @@ interface KeyType {
|
|||
@JvmName("canAuthenticate") get() = canSign
|
||||
|
||||
/**
|
||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS key flag.
|
||||
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
|
||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_COMMS
|
||||
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS}.
|
||||
*
|
||||
* @return true if the key can encrypt communication
|
||||
*/
|
||||
|
@ -83,8 +85,8 @@ interface KeyType {
|
|||
@JvmName("canEncryptCommunication") get() = algorithm.encryptionCapable
|
||||
|
||||
/**
|
||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE key flag.
|
||||
* See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
|
||||
* Return true if the key that is generated from this type is able to carry the ENCRYPT_STORAGE
|
||||
* key flag. See {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE}.
|
||||
*
|
||||
* @return true if the key can encrypt for storage
|
||||
*/
|
||||
|
@ -92,19 +94,14 @@ interface KeyType {
|
|||
@JvmName("canEncryptStorage") get() = algorithm.encryptionCapable
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun RSA(length: RsaLength): RSA = RSA.withLength(length)
|
||||
@JvmStatic fun RSA(length: RsaLength): RSA = RSA.withLength(length)
|
||||
|
||||
@JvmStatic
|
||||
fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
|
||||
@JvmStatic fun ECDH(curve: EllipticCurve): ECDH = ECDH.fromCurve(curve)
|
||||
|
||||
@JvmStatic
|
||||
fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
|
||||
@JvmStatic fun ECDSA(curve: EllipticCurve): ECDSA = ECDSA.fromCurve(curve)
|
||||
|
||||
@JvmStatic
|
||||
fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
|
||||
@JvmStatic fun EDDSA(curve: EdDSACurve): EdDSA = EdDSA.fromCurve(curve)
|
||||
|
||||
@JvmStatic
|
||||
fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
|
||||
@JvmStatic fun XDH(curve: XDHSpec): XDH = XDH.fromSpec(curve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,20 @@
|
|||
|
||||
package org.pgpainless.key.generation.type.ecc
|
||||
|
||||
|
||||
/**
|
||||
* Elliptic curves for use with [org.pgpainless.key.generation.type.ecc.ecdh.ECDH] and
|
||||
* [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA].
|
||||
* For curve25519 related curve definitions see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve].
|
||||
* [org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA]. For curve25519 related curve definitions
|
||||
* see [XDHSpec] and [org.pgpainless.key.generation.type.eddsa.EdDSACurve].
|
||||
*/
|
||||
enum class EllipticCurve(
|
||||
val curveName: String,
|
||||
val bitStrength: Int
|
||||
) {
|
||||
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see https://tools.ietf.org/search/rfc4492#page-32
|
||||
_P384("secp384r1", 384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
|
||||
_P521("secp521r1", 521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
|
||||
enum class EllipticCurve(val curveName: String, val bitStrength: Int) {
|
||||
_P256("prime256v1", 256), // prime256v1 is equivalent to P-256, see
|
||||
// https://tools.ietf.org/search/rfc4492#page-32
|
||||
_P384(
|
||||
"secp384r1",
|
||||
384), // secp384r1 is equivalent to P-384, see https://tools.ietf.org/search/rfc4492#page-32
|
||||
_P521(
|
||||
"secp521r1",
|
||||
521), // secp521r1 is equivalent to P-521, see https://tools.ietf.org/search/rfc4492#page-32
|
||||
_SECP256K1("secp256k1", 256),
|
||||
_BRAINPOOLP256R1("brainpoolP256r1", 256),
|
||||
_BRAINPOOLP384R1("brainpoolP384r1", 384),
|
||||
|
@ -24,4 +25,4 @@ enum class EllipticCurve(
|
|||
;
|
||||
|
||||
fun getName(): String = curveName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ class ECDH private constructor(val curve: EllipticCurve) : KeyType {
|
|||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromCurve(curve: EllipticCurve) = ECDH(curve)
|
||||
@JvmStatic fun fromCurve(curve: EllipticCurve) = ECDH(curve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ class ECDSA private constructor(val curve: EllipticCurve) : KeyType {
|
|||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
|
||||
@JvmStatic fun fromCurve(curve: EllipticCurve) = ECDSA(curve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ class EdDSA private constructor(val curve: EdDSACurve) : KeyType {
|
|||
override val algorithmSpec = ECNamedCurveGenParameterSpec(curve.curveName)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
|
||||
@JvmStatic fun fromCurve(curve: EdDSACurve) = EdDSA(curve)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
|
||||
package org.pgpainless.key.generation.type.eddsa
|
||||
|
||||
enum class EdDSACurve(
|
||||
val curveName: String,
|
||||
val bitStrength: Int) {
|
||||
enum class EdDSACurve(val curveName: String, val bitStrength: Int) {
|
||||
_Ed25519("ed25519", 256),
|
||||
;
|
||||
|
||||
fun getName() = curveName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ class ElGamal private constructor(length: ElGamalLength) : KeyType {
|
|||
override val algorithmSpec = ElGamalParameterSpec(length.p, length.g)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun withLength(length: ElGamalLength) = ElGamal(length)
|
||||
@JvmStatic fun withLength(length: ElGamalLength) = ElGamal(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,61 +4,54 @@
|
|||
|
||||
package org.pgpainless.key.generation.type.elgamal
|
||||
|
||||
import org.pgpainless.key.generation.type.KeyLength
|
||||
import java.math.BigInteger
|
||||
import org.pgpainless.key.generation.type.KeyLength
|
||||
|
||||
/**
|
||||
* The following primes are taken from RFC-3526.
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt">
|
||||
* RFC-3526: More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
|
||||
*
|
||||
* @see <a href="https://www.ietf.org/rfc/rfc3526.txt"> RFC-3526: More Modular Exponential (MODP)
|
||||
* Diffie-Hellman groups for Internet Key Exchange (IKE)</a>
|
||||
* @deprecated the use of ElGamal keys is no longer recommended.
|
||||
*/
|
||||
|
||||
@Deprecated("The use of ElGamal keys is no longer recommended.")
|
||||
enum class ElGamalLength(
|
||||
override val length: Int,
|
||||
p: String,
|
||||
g: String
|
||||
) : KeyLength {
|
||||
enum class ElGamalLength(override val length: Int, p: String, g: String) : KeyLength {
|
||||
|
||||
/**
|
||||
* prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_1536(1536, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "2"),
|
||||
/** prime: 2^1536 - 2^1472 - 1 + 2^64 * { [2^1406 pi] + 741804 }. generator: 2 */
|
||||
_1536(
|
||||
1536,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
|
||||
"2"),
|
||||
|
||||
/**
|
||||
* prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_2048(2048, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", "2"),
|
||||
/** prime: 2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }. generator: 2 */
|
||||
_2048(
|
||||
2048,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF",
|
||||
"2"),
|
||||
|
||||
/**
|
||||
* prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_3072(3072, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "2"),
|
||||
/** prime: 2^3072 - 2^3008 - 1 + 2^64 * { [2^2942 pi] + 1690314 }. generator: 2 */
|
||||
_3072(
|
||||
3072,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
|
||||
"2"),
|
||||
|
||||
/**
|
||||
* prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_4096(4096, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF", "2"),
|
||||
/** prime: 2^4096 - 2^4032 - 1 + 2^64 * { [2^3966 pi] + 240904 }. generator: 2 */
|
||||
_4096(
|
||||
4096,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF",
|
||||
"2"),
|
||||
|
||||
/**
|
||||
* prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_6144(6144, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF", "2"),
|
||||
/** prime: 2^6144 - 2^6080 - 1 + 2^64 * { [2^6014 pi] + 929484 }. generator: 2 */
|
||||
_6144(
|
||||
6144,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF",
|
||||
"2"),
|
||||
|
||||
/**
|
||||
* prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }.
|
||||
* generator: 2
|
||||
*/
|
||||
_8192(8192, "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF", "2")
|
||||
;
|
||||
/** prime: 2^8192 - 2^8128 - 1 + 2^64 * { [2^8062 pi] + 4743158 }. generator: 2 */
|
||||
_8192(
|
||||
8192,
|
||||
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF",
|
||||
"2");
|
||||
|
||||
val p: BigInteger
|
||||
val g: BigInteger
|
||||
|
@ -67,6 +60,4 @@ enum class ElGamalLength(
|
|||
this.p = BigInteger(p, 16)
|
||||
this.g = BigInteger(g, 16)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
package org.pgpainless.key.generation.type.rsa
|
||||
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.key.generation.type.KeyType
|
||||
import java.security.spec.RSAKeyGenParameterSpec
|
||||
|
||||
/**
|
||||
* Key type that specifies the RSA_GENERAL algorithm.
|
||||
*/
|
||||
class RSA private constructor(length: RsaLength): KeyType {
|
||||
/** Key type that specifies the RSA_GENERAL algorithm. */
|
||||
class RSA private constructor(length: RsaLength) : KeyType {
|
||||
|
||||
override val name = "RSA"
|
||||
override val algorithm = PublicKeyAlgorithm.RSA_GENERAL
|
||||
|
@ -19,7 +17,6 @@ class RSA private constructor(length: RsaLength): KeyType {
|
|||
override val algorithmSpec = RSAKeyGenParameterSpec(length.length, RSAKeyGenParameterSpec.F4)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun withLength(length: RsaLength) = RSA(length)
|
||||
@JvmStatic fun withLength(length: RsaLength) = RSA(length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,4 @@ enum class RsaLength(override val length: Int) : KeyLength {
|
|||
_3072(3072),
|
||||
_4096(4096),
|
||||
_8192(8192)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,13 @@ import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec
|
|||
import org.pgpainless.algorithm.PublicKeyAlgorithm
|
||||
import org.pgpainless.key.generation.type.KeyType
|
||||
|
||||
class XDH private constructor(spec: XDHSpec): KeyType {
|
||||
class XDH private constructor(spec: XDHSpec) : KeyType {
|
||||
override val name = "XDH"
|
||||
override val algorithm = PublicKeyAlgorithm.ECDH
|
||||
override val bitStrength = spec.bitStrength
|
||||
override val algorithmSpec = ECNamedCurveGenParameterSpec(spec.algorithmName)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromSpec(spec: XDHSpec) = XDH(spec)
|
||||
@JvmStatic fun fromSpec(spec: XDHSpec) = XDH(spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,9 @@
|
|||
|
||||
package org.pgpainless.key.generation.type.xdh
|
||||
|
||||
enum class XDHSpec(
|
||||
val algorithmName: String,
|
||||
val curveName: String,
|
||||
val bitStrength: Int) {
|
||||
enum class XDHSpec(val algorithmName: String, val curveName: String, val bitStrength: Int) {
|
||||
_X25519("X25519", "curve25519", 256),
|
||||
;
|
||||
|
||||
fun getName() = algorithmName
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,78 +11,79 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm
|
|||
import org.pgpainless.key.SubkeyIdentifier
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil
|
||||
|
||||
abstract class KeyAccessor(
|
||||
protected val info: KeyRingInfo,
|
||||
protected val key: SubkeyIdentifier
|
||||
) {
|
||||
abstract class KeyAccessor(protected val info: KeyRingInfo, protected val key: SubkeyIdentifier) {
|
||||
|
||||
/**
|
||||
* Depending on the way we address the key (key-id or user-id), return the respective [PGPSignature]
|
||||
* which contains the algorithm preferences we are going to use.
|
||||
* Depending on the way we address the key (key-id or user-id), return the respective
|
||||
* [PGPSignature] which contains the algorithm preferences we are going to use.
|
||||
*
|
||||
* <p>
|
||||
* If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification,
|
||||
* while we would instead rely on those in the direct-key signature if we'd address the key by key-id.
|
||||
* If we address a key via its user-id, we want to rely on the algorithm preferences in the
|
||||
* user-id certification, while we would instead rely on those in the direct-key signature if
|
||||
* we'd address the key by key-id.
|
||||
*
|
||||
* @return signature
|
||||
*/
|
||||
abstract val signatureWithPreferences: PGPSignature
|
||||
|
||||
/**
|
||||
* Preferred symmetric key encryption algorithms.
|
||||
*/
|
||||
/** Preferred symmetric key encryption algorithms. */
|
||||
val preferredSymmetricKeyAlgorithms: Set<SymmetricKeyAlgorithm>
|
||||
get() = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences)
|
||||
get() =
|
||||
SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(signatureWithPreferences)
|
||||
|
||||
/**
|
||||
* Preferred hash algorithms.
|
||||
*/
|
||||
/** Preferred hash algorithms. */
|
||||
val preferredHashAlgorithms: Set<HashAlgorithm>
|
||||
get() = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signatureWithPreferences)
|
||||
|
||||
/**
|
||||
* Preferred compression algorithms.
|
||||
*/
|
||||
/** Preferred compression algorithms. */
|
||||
val preferredCompressionAlgorithms: Set<CompressionAlgorithm>
|
||||
get() = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences)
|
||||
get() =
|
||||
SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(signatureWithPreferences)
|
||||
|
||||
/**
|
||||
* Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`).
|
||||
* In this case we are sourcing preferred algorithms from the user-id certification first.
|
||||
* Address the key via a user-id (e.g. `Alice <alice@wonderland.lit>`). In this case we are
|
||||
* sourcing preferred algorithms from the user-id certification first.
|
||||
*/
|
||||
class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence): KeyAccessor(info, key) {
|
||||
class ViaUserId(info: KeyRingInfo, key: SubkeyIdentifier, private val userId: CharSequence) :
|
||||
KeyAccessor(info, key) {
|
||||
override val signatureWithPreferences: PGPSignature
|
||||
get() = checkNotNull(info.getLatestUserIdCertification(userId.toString())) {
|
||||
"No valid user-id certification signature found for '$userId'."
|
||||
}
|
||||
get() =
|
||||
checkNotNull(info.getLatestUserIdCertification(userId.toString())) {
|
||||
"No valid user-id certification signature found for '$userId'."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Address the key via key-id.
|
||||
* In this case we are sourcing preferred algorithms from the keys direct-key signature first.
|
||||
* Address the key via key-id. In this case we are sourcing preferred algorithms from the keys
|
||||
* direct-key signature first.
|
||||
*/
|
||||
class ViaKeyId(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
|
||||
override val signatureWithPreferences: PGPSignature
|
||||
get() {
|
||||
// If the key is located by Key ID, the algorithm of the primary User ID of the key provides the
|
||||
// If the key is located by Key ID, the algorithm of the primary User ID of the key
|
||||
// provides the
|
||||
// preferred symmetric algorithm.
|
||||
info.primaryUserId?.let {
|
||||
userId -> info.getLatestUserIdCertification(userId).let { if (it != null) return it }
|
||||
info.primaryUserId?.let { userId ->
|
||||
info.getLatestUserIdCertification(userId).let { if (it != null) return it }
|
||||
}
|
||||
|
||||
return checkNotNull(info.latestDirectKeySelfSignature) { "No valid signature found." }
|
||||
return checkNotNull(info.latestDirectKeySelfSignature) {
|
||||
"No valid signature found."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubKey(info: KeyRingInfo, key: SubkeyIdentifier): KeyAccessor(info, key) {
|
||||
class SubKey(info: KeyRingInfo, key: SubkeyIdentifier) : KeyAccessor(info, key) {
|
||||
override val signatureWithPreferences: PGPSignature
|
||||
get() = checkNotNull(
|
||||
get() =
|
||||
checkNotNull(
|
||||
if (key.isPrimaryKey) {
|
||||
info.latestDirectKeySelfSignature ?:
|
||||
info.primaryUserId?.let { info.getLatestUserIdCertification(it) }
|
||||
info.latestDirectKeySelfSignature
|
||||
?: info.primaryUserId?.let { info.getLatestUserIdCertification(it) }
|
||||
} else {
|
||||
info.getCurrentSubkeyBindingSignature(key.subkeyId)
|
||||
}) {
|
||||
"No valid signature found."
|
||||
}
|
||||
) { "No valid signature found." }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,64 +12,70 @@ import org.bouncycastle.openpgp.PGPPublicKey
|
|||
import org.bouncycastle.openpgp.PGPSecretKey
|
||||
|
||||
@Deprecated("Deprecated in favor of extension functions to PGPSecretKey and PGPPublicKey.")
|
||||
class KeyInfo private constructor(
|
||||
val secretKey: PGPSecretKey?,
|
||||
val publicKey: PGPPublicKey) {
|
||||
class KeyInfo private constructor(val secretKey: PGPSecretKey?, val publicKey: PGPPublicKey) {
|
||||
|
||||
constructor(secretKey: PGPSecretKey): this(secretKey, secretKey.publicKey)
|
||||
constructor(publicKey: PGPPublicKey): this(null, publicKey)
|
||||
constructor(secretKey: PGPSecretKey) : this(secretKey, secretKey.publicKey)
|
||||
|
||||
constructor(publicKey: PGPPublicKey) : this(null, publicKey)
|
||||
|
||||
/**
|
||||
* Return the name of the elliptic curve used by this key, or throw an [IllegalArgumentException] if the key
|
||||
* is not based on elliptic curves, or on an unknown curve.
|
||||
* Return the name of the elliptic curve used by this key, or throw an
|
||||
* [IllegalArgumentException] if the key is not based on elliptic curves, or on an unknown
|
||||
* curve.
|
||||
*/
|
||||
@Deprecated("Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.",
|
||||
ReplaceWith("publicKey.getCurveName()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling getCurveName() on the PGPPublicKey itself.",
|
||||
ReplaceWith("publicKey.getCurveName()"))
|
||||
val curveName: String
|
||||
get() = publicKey.getCurveName()
|
||||
|
||||
/**
|
||||
* Return true, if the secret key is encrypted.
|
||||
* This method returns false, if the secret key is null.
|
||||
* Return true, if the secret key is encrypted. This method returns false, if the secret key is
|
||||
* null.
|
||||
*/
|
||||
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isEncrypted()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isEncrypted()"))
|
||||
val isEncrypted: Boolean
|
||||
get() = secretKey?.isEncrypted() ?: false
|
||||
|
||||
/**
|
||||
* Return true, if the secret key is decrypted.
|
||||
* This method returns true, if the secret key is null.
|
||||
* Return true, if the secret key is decrypted. This method returns true, if the secret key is
|
||||
* null.
|
||||
*/
|
||||
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isDecrypted()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isDecrypted()"))
|
||||
val isDecrypted: Boolean
|
||||
get() = secretKey?.isDecrypted() ?: true
|
||||
|
||||
/**
|
||||
* Return true, if the secret key is using the GNU_DUMMY_S2K s2k type.
|
||||
* This method returns false, if the secret key is null.
|
||||
* Return true, if the secret key is using the GNU_DUMMY_S2K s2k type. This method returns
|
||||
* false, if the secret key is null.
|
||||
*/
|
||||
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||
val hasDummyS2K: Boolean
|
||||
@JvmName("hasDummyS2K")
|
||||
get() = secretKey?.hasDummyS2K() ?: false
|
||||
@JvmName("hasDummyS2K") get() = secretKey?.hasDummyS2K() ?: false
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Deprecated("Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isEncrypted()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling isEncrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isEncrypted()"))
|
||||
fun isEncrypted(secretKey: PGPSecretKey?) = secretKey.isEncrypted()
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isDecrypted()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling isDecrypted() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.isDecrypted()"))
|
||||
fun isDecrypted(secretKey: PGPSecretKey?) = secretKey.isDecrypted()
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||
@Deprecated(
|
||||
"Deprecated in favor of calling hasDummyS2K() on the PGPSecretKey itself.",
|
||||
ReplaceWith("secretKey.hasDummyS2K()"))
|
||||
fun hasDummyS2K(secretKey: PGPSecretKey?) = secretKey.hasDummyS2K()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue