1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Port EncryptionOptions over to OpenPGPCertificate

This commit is contained in:
Paul Schaub 2025-02-17 12:01:13 +01:00
parent 4a90b8721f
commit 30d584c696
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 165 additions and 53 deletions

View file

@ -204,7 +204,7 @@ class PGPainless(
fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) = fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) =
KeyRingInfo(key, referenceTime) KeyRingInfo(key, referenceTime)
fun inspectKeyRing(key: OpenPGPKey, referenceTime: Date = Date()) = fun inspectKeyRing(key: OpenPGPCertificate, referenceTime: Date = Date()) =
KeyRingInfo(key, getPolicy(), referenceTime) KeyRingInfo(key, getPolicy(), referenceTime)
/** /**

View file

@ -5,14 +5,16 @@
package org.pgpainless.encryption_signing package org.pgpainless.encryption_signing
import java.util.* import java.util.*
import org.bouncycastle.openpgp.PGPPublicKey
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey
import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator import org.bouncycastle.openpgp.operator.PGPKeyEncryptionMethodGenerator
import org.pgpainless.PGPainless.Companion.inspectKeyRing
import org.pgpainless.algorithm.EncryptionPurpose import org.pgpainless.algorithm.EncryptionPurpose
import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.authentication.CertificateAuthority import org.pgpainless.authentication.CertificateAuthority
import org.pgpainless.bouncycastle.extensions.toOpenPGPCertificate
import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector
import org.pgpainless.exception.KeyException
import org.pgpainless.exception.KeyException.* import org.pgpainless.exception.KeyException.*
import org.pgpainless.implementation.ImplementationFactory import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.key.OpenPgpFingerprint import org.pgpainless.key.OpenPgpFingerprint
@ -50,10 +52,10 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
constructor() : this(EncryptionPurpose.ANY) constructor() : this(EncryptionPurpose.ANY)
/** /**
* Factory method to create an [EncryptionOptions] object which will encrypt for keys which * Set the evaluation date for certificate evaluation.
* carry the flag [org.pgpainless.algorithm.KeyFlag.ENCRYPT_COMMS].
* *
* @return encryption options * @param evaluationDate reference time
* @return this
*/ */
fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate } fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate }
@ -81,7 +83,9 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
authority authority
.lookupByUserId(userId, email, evaluationDate, targetAmount) .lookupByUserId(userId, email, evaluationDate, targetAmount)
.filter { it.isAuthenticated() } .filter { it.isAuthenticated() }
.forEach { addRecipient(it.certificate).also { foundAcceptable = true } } .forEach {
addRecipient(it.certificate.toOpenPGPCertificate()).also { foundAcceptable = true }
}
require(foundAcceptable) { require(foundAcceptable) {
"Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount." "Could not identify any trust-worthy certificates for '$userId' and target trust amount $targetAmount."
} }
@ -89,11 +93,14 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
/** /**
* Add all key rings in the provided [Iterable] (e.g. * Add all key rings in the provided [Iterable] (e.g.
* [org.bouncycastle.openpgp.PGPPublicKeyRingCollection]) as recipients. * [org.bouncycastle.openpgp.PGPPublicKeyRingCollection]) as recipients. Note: This method is
* deprecated. Instead, repeatedly call [addRecipient], passing in individual
* [OpenPGPCertificate] instances.
* *
* @param keys keys * @param keys keys
* @return this * @return this
*/ */
@Deprecated("Repeatedly pass OpenPGPCertificate instances instead.")
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply { fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
keys.toList().let { keys.toList().let {
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." } require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
@ -104,12 +111,15 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
/** /**
* Add all key rings in the provided [Iterable] (e.g. * Add all key rings in the provided [Iterable] (e.g.
* [org.bouncycastle.openpgp.PGPPublicKeyRingCollection]) as recipients. Per key ring, the * [org.bouncycastle.openpgp.PGPPublicKeyRingCollection]) as recipients. Per key ring, the
* selector is applied to select one or more encryption subkeys. * selector is applied to select one or more encryption subkeys. Note: This method is
* deprecated. Instead, repeatedly call [addRecipient], passing in individual
* [OpenPGPCertificate] instances.
* *
* @param keys keys * @param keys keys
* @param selector encryption key selector * @param selector encryption key selector
* @return this * @return this
*/ */
@Deprecated("Repeatedly pass OpenPGPCertificate instances instead.")
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply { fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
keys.toList().let { keys.toList().let {
require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." } require(it.isNotEmpty()) { "Set of recipient keys cannot be empty." }
@ -117,76 +127,180 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
} }
} }
/**
* Encrypt the message to the recipients [OpenPGPCertificate].
*
* @param cert recipient certificate
* @return this
*/
fun addRecipient(cert: OpenPGPCertificate) = addRecipient(cert, encryptionKeySelector)
/** /**
* Add a recipient by providing a key. * Add a recipient by providing a key.
* *
* @param key key ring * @param key key ring
* @return this * @return this
*/ */
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith =
ReplaceWith("addRecipient(key.toOpenPGPCertificate(), encryptionKeySelector)"))
fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector) fun addRecipient(key: PGPPublicKeyRing) = addRecipient(key, encryptionKeySelector)
/**
* Encrypt the message for the given recipients [OpenPGPCertificate], sourcing algorithm
* preferences by inspecting the binding signature on the passed [userId].
*
* @param cert recipient certificate
* @param userId recipient user-id
* @return this
*/
fun addRecipient(cert: OpenPGPCertificate, userId: CharSequence) =
addRecipient(cert, userId, encryptionKeySelector)
/** /**
* Add a recipient by providing a key and recipient user-id. The user-id is used to determine * Add a recipient by providing a key and recipient user-id. The user-id is used to determine
* the recipients preferences (algorithms etc.). * the recipients preferences (algorithms etc.). Note: This method is deprecated. Replace the
* [PGPPublicKeyRing] instance with an [OpenPGPCertificate].
* *
* @param key key ring * @param key key ring
* @param userId user id * @param userId user id
* @return this * @return this
*/ */
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith = ReplaceWith("addRecipient(key.toOpenPGPCertificate(), userId)"))
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) = fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
addRecipient(key, userId, encryptionKeySelector) addRecipient(key, userId, encryptionKeySelector)
/**
* Encrypt the message for the given recipients [OpenPGPCertificate], sourcing algorithm
* preferences by inspecting the binding signature on the given [userId] and filtering the
* recipient subkeys through the given [EncryptionKeySelector].
*
* @param cert recipient certificate
* @param userId user-id for sourcing algorithm preferences
* @param encryptionKeySelector decides which subkeys to encrypt for
* @return this
*/
fun addRecipient(
cert: OpenPGPCertificate,
userId: CharSequence,
encryptionKeySelector: EncryptionKeySelector
) = apply {
val info = inspectKeyRing(cert, evaluationDate)
val subkeys =
encryptionKeySelector.selectEncryptionSubkeys(
info.getEncryptionSubkeys(userId, purpose))
if (subkeys.isEmpty()) {
throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(cert.pgpPublicKeyRing))
}
for (subkey in subkeys) {
val keyId = SubkeyIdentifier(subkey)
_keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString())
addRecipientKey(subkey, false)
}
}
/**
* Encrypt the message for the given recipients public key, sourcing algorithm preferences by
* inspecting the binding signature on the given [userId] and filtering the recipient subkeys
* through the given [EncryptionKeySelector].
*
* @param key recipient public key
* @param userId user-id for sourcing algorithm preferences
* @param encryptionKeySelector decides which subkeys to encrypt for
* @return this
*/
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith =
ReplaceWith("addRecipient(key.toOpenPGPCertificate(), userId, encryptionKeySelector)"))
fun addRecipient( fun addRecipient(
key: PGPPublicKeyRing, key: PGPPublicKeyRing,
userId: CharSequence, userId: CharSequence,
encryptionKeySelector: EncryptionKeySelector encryptionKeySelector: EncryptionKeySelector
) = apply { ) = addRecipient(key.toOpenPGPCertificate(), userId, encryptionKeySelector)
val info = KeyRingInfo(key, evaluationDate)
val subkeys =
encryptionKeySelector.selectEncryptionSubkeys(
info.getEncryptionSubkeys(userId, purpose).map { it.pgpPublicKey })
if (subkeys.isEmpty()) {
throw KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
}
for (subkey in subkeys) { /**
val keyId = SubkeyIdentifier(key, subkey.keyID) * Encrypt the message for the given recipients [OpenPGPCertificate], filtering encryption
_keyRingInfo[keyId] = info * subkeys through the given [EncryptionKeySelector].
_keyViews[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString()) *
addRecipientKey(key, subkey, false) * @param cert recipient certificate
} * @param encryptionKeySelector decides, which subkeys to encrypt for
} * @return this
*/
fun addRecipient(cert: OpenPGPCertificate, encryptionKeySelector: EncryptionKeySelector) =
addAsRecipient(cert, encryptionKeySelector, false)
fun addRecipient(key: PGPPublicKeyRing, encryptionKeySelector: EncryptionKeySelector) = apply { /**
addAsRecipient(key, encryptionKeySelector, false) * Encrypt the message for the given recipients public key, filtering encryption subkeys through
} * the given [EncryptionKeySelector].
*
* @param key recipient public key
* @param encryptionKeySelector decides, which subkeys to encrypt for
* @return this
*/
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith =
ReplaceWith("addRecipient(key.toOpenPGPCertificate(), encryptionKeySelector)"))
fun addRecipient(key: PGPPublicKeyRing, encryptionKeySelector: EncryptionKeySelector) =
addRecipient(key.toOpenPGPCertificate(), encryptionKeySelector)
/**
* Encrypt the message for the recipients [OpenPGPCertificate], keeping the recipient anonymous
* by setting a wildcard key-id / fingerprint.
*
* @param cert recipient certificate
* @param selector decides, which subkeys to encrypt for
* @return this
*/
@JvmOverloads @JvmOverloads
fun addHiddenRecipient(
cert: OpenPGPCertificate,
selector: EncryptionKeySelector = encryptionKeySelector
) = addAsRecipient(cert, selector, true)
/**
* Encrypt the message for the recipients public key, keeping the recipient anonymous by setting
* a wildcard key-id / fingerprint.
*
* @param key recipient public key
* @param selector decides, which subkeys to encrypt for
* @return this
*/
@JvmOverloads
@Deprecated(
"Pass in an OpenPGPCertificate instead.",
replaceWith = ReplaceWith("addHiddenRecipient(key.toOpenPGPCertificate(), selector)"))
fun addHiddenRecipient( fun addHiddenRecipient(
key: PGPPublicKeyRing, key: PGPPublicKeyRing,
selector: EncryptionKeySelector = encryptionKeySelector selector: EncryptionKeySelector = encryptionKeySelector
) = apply { addAsRecipient(key, selector, true) } ) = addHiddenRecipient(key.toOpenPGPCertificate(), selector)
private fun addAsRecipient( private fun addAsRecipient(
key: PGPPublicKeyRing, cert: OpenPGPCertificate,
selector: EncryptionKeySelector, selector: EncryptionKeySelector,
wildcardKeyId: Boolean wildcardKeyId: Boolean
) = apply { ) = apply {
val info = KeyRingInfo(key, evaluationDate) val info = inspectKeyRing(cert, evaluationDate)
val primaryKeyExpiration = val primaryKeyExpiration =
try { try {
info.primaryKeyExpirationDate info.primaryKeyExpirationDate
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key)) throw UnacceptableSelfSignatureException(
OpenPgpFingerprint.of(cert.pgpPublicKeyRing))
} }
if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) { if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) {
throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration) throw ExpiredKeyException(
OpenPgpFingerprint.of(cert.pgpPublicKeyRing), primaryKeyExpiration)
} }
var encryptionSubkeys = var encryptionSubkeys = selector.selectEncryptionSubkeys(info.getEncryptionSubkeys(purpose))
selector.selectEncryptionSubkeys(
info.getEncryptionSubkeys(purpose).map { it.pgpPublicKey })
// There are some legacy keys around without key flags. // There are some legacy keys around without key flags.
// If we allow encryption for those keys, we add valid keys without any key flags, if they // If we allow encryption for those keys, we add valid keys without any key flags, if they
@ -197,31 +311,26 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
info.validSubkeys info.validSubkeys
.filter { it.pgpPublicKey.isEncryptionKey } .filter { it.pgpPublicKey.isEncryptionKey }
.filter { info.getKeyFlagsOf(it.keyIdentifier).isEmpty() } .filter { info.getKeyFlagsOf(it.keyIdentifier).isEmpty() }
.map { it.pgpPublicKey }
} }
if (encryptionSubkeys.isEmpty()) { if (encryptionSubkeys.isEmpty()) {
throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)) throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(cert.pgpPublicKeyRing))
} }
for (subkey in encryptionSubkeys) { for (subkey in encryptionSubkeys) {
val keyId = SubkeyIdentifier(key, subkey.keyID) val keyId = SubkeyIdentifier(subkey)
_keyRingInfo[keyId] = info _keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaKeyId(info, keyId) _keyViews[keyId] = KeyAccessor.ViaKeyId(info, keyId)
addRecipientKey(key, subkey, wildcardKeyId) addRecipientKey(subkey, wildcardKeyId)
} }
} }
private fun addRecipientKey( private fun addRecipientKey(key: OpenPGPComponentKey, wildcardKeyId: Boolean) {
certificate: PGPPublicKeyRing, _encryptionKeyIdentifiers.add(SubkeyIdentifier(key))
key: PGPPublicKey,
wildcardKeyId: Boolean
) {
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
addEncryptionMethod( addEncryptionMethod(
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also { ImplementationFactory.getInstance()
it.setUseWildcardKeyID(wildcardKeyId) .getPublicKeyKeyEncryptionMethodGenerator(key.pgpPublicKey)
}) .also { it.setUseWildcardKeyID(wildcardKeyId) })
} }
/** /**
@ -295,7 +404,9 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty() fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
fun interface EncryptionKeySelector { fun interface EncryptionKeySelector {
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey> fun selectEncryptionSubkeys(
encryptionCapableKeys: List<OpenPGPComponentKey>
): List<OpenPGPComponentKey>
} }
companion object { companion object {

View file

@ -164,7 +164,7 @@ class CertificateValidator {
if (signingSubkey.keyID == primaryKey.keyID) { // signing key is primary key if (signingSubkey.keyID == primaryKey.keyID) { // signing key is primary key
if (directKeyAndRevSigs.isNotEmpty()) { if (directKeyAndRevSigs.isNotEmpty()) {
val directKeySig = directKeyAndRevSigs[0]!! val directKeySig = directKeyAndRevSigs[0]
val flags = SignatureSubpacketsUtil.getKeyFlags(directKeySig) val flags = SignatureSubpacketsUtil.getKeyFlags(directKeySig)
if (flags != null && KeyFlag.hasKeyFlag(flags.flags, KeyFlag.SIGN_DATA)) { if (flags != null && KeyFlag.hasKeyFlag(flags.flags, KeyFlag.SIGN_DATA)) {
return true return true

View file

@ -20,6 +20,7 @@ import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -153,7 +154,7 @@ public class EncryptionOptionsTest {
() -> options.addRecipient(publicKeys, new EncryptionOptions.EncryptionKeySelector() { () -> options.addRecipient(publicKeys, new EncryptionOptions.EncryptionKeySelector() {
@NotNull @NotNull
@Override @Override
public List<PGPPublicKey> selectEncryptionSubkeys(@NotNull List<? extends PGPPublicKey> encryptionCapableKeys) { public List<OpenPGPCertificate.OpenPGPComponentKey> selectEncryptionSubkeys(@NotNull List<? extends OpenPGPCertificate.OpenPGPComponentKey> encryptionCapableKeys) {
return Collections.emptyList(); return Collections.emptyList();
} }
})); }));
@ -161,7 +162,7 @@ public class EncryptionOptionsTest {
assertThrows(KeyException.UnacceptableEncryptionKeyException.class, assertThrows(KeyException.UnacceptableEncryptionKeyException.class,
() -> options.addRecipient(publicKeys, "test@pgpainless.org", new EncryptionOptions.EncryptionKeySelector() { () -> options.addRecipient(publicKeys, "test@pgpainless.org", new EncryptionOptions.EncryptionKeySelector() {
@Override @Override
public List<PGPPublicKey> selectEncryptionSubkeys(@Nonnull List<? extends PGPPublicKey> encryptionCapableKeys) { public List<OpenPGPCertificate.OpenPGPComponentKey> selectEncryptionSubkeys(@Nonnull List<? extends OpenPGPCertificate.OpenPGPComponentKey> encryptionCapableKeys) {
return Collections.emptyList(); return Collections.emptyList();
} }
})); }));