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 8c557ad945
commit 0b4f1a0f01
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()) =
KeyRingInfo(key, referenceTime)
fun inspectKeyRing(key: OpenPGPKey, referenceTime: Date = Date()) =
fun inspectKeyRing(key: OpenPGPCertificate, referenceTime: Date = Date()) =
KeyRingInfo(key, getPolicy(), referenceTime)
/**

View file

@ -5,14 +5,16 @@
package org.pgpainless.encryption_signing
import java.util.*
import org.bouncycastle.openpgp.PGPPublicKey
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.pgpainless.PGPainless.Companion.inspectKeyRing
import org.pgpainless.algorithm.EncryptionPurpose
import org.pgpainless.algorithm.SymmetricKeyAlgorithm
import org.pgpainless.authentication.CertificateAuthority
import org.pgpainless.bouncycastle.extensions.toOpenPGPCertificate
import org.pgpainless.encryption_signing.EncryptionOptions.EncryptionKeySelector
import org.pgpainless.exception.KeyException
import org.pgpainless.exception.KeyException.*
import org.pgpainless.implementation.ImplementationFactory
import org.pgpainless.key.OpenPgpFingerprint
@ -50,10 +52,10 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
constructor() : this(EncryptionPurpose.ANY)
/**
* Factory method to create an [EncryptionOptions] object which will encrypt for keys which
* carry the flag [org.pgpainless.algorithm.KeyFlag.ENCRYPT_COMMS].
* Set the evaluation date for certificate evaluation.
*
* @return encryption options
* @param evaluationDate reference time
* @return this
*/
fun setEvaluationDate(evaluationDate: Date) = apply { this.evaluationDate = evaluationDate }
@ -81,7 +83,9 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
authority
.lookupByUserId(userId, email, evaluationDate, targetAmount)
.filter { it.isAuthenticated() }
.forEach { addRecipient(it.certificate).also { foundAcceptable = true } }
.forEach {
addRecipient(it.certificate.toOpenPGPCertificate()).also { foundAcceptable = true }
}
require(foundAcceptable) {
"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.
* [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
* @return this
*/
@Deprecated("Repeatedly pass OpenPGPCertificate instances instead.")
fun addRecipients(keys: Iterable<PGPPublicKeyRing>) = apply {
keys.toList().let {
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.
* [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 selector encryption key selector
* @return this
*/
@Deprecated("Repeatedly pass OpenPGPCertificate instances instead.")
fun addRecipients(keys: Iterable<PGPPublicKeyRing>, selector: EncryptionKeySelector) = apply {
keys.toList().let {
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.
*
* @param key key ring
* @return this
*/
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith =
ReplaceWith("addRecipient(key.toOpenPGPCertificate(), 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
* 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 userId user id
* @return this
*/
@Deprecated(
"Pass in OpenPGPCertificate instead.",
replaceWith = ReplaceWith("addRecipient(key.toOpenPGPCertificate(), userId)"))
fun addRecipient(key: PGPPublicKeyRing, userId: CharSequence) =
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(
key: PGPPublicKeyRing,
userId: CharSequence,
encryptionKeySelector: EncryptionKeySelector
) = apply {
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))
}
) = addRecipient(key.toOpenPGPCertificate(), userId, encryptionKeySelector)
for (subkey in subkeys) {
val keyId = SubkeyIdentifier(key, subkey.keyID)
_keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaUserId(info, keyId, userId.toString())
addRecipientKey(key, subkey, false)
}
}
/**
* Encrypt the message for the given recipients [OpenPGPCertificate], filtering encryption
* subkeys through the given [EncryptionKeySelector].
*
* @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
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(
key: PGPPublicKeyRing,
selector: EncryptionKeySelector = encryptionKeySelector
) = apply { addAsRecipient(key, selector, true) }
) = addHiddenRecipient(key.toOpenPGPCertificate(), selector)
private fun addAsRecipient(
key: PGPPublicKeyRing,
cert: OpenPGPCertificate,
selector: EncryptionKeySelector,
wildcardKeyId: Boolean
) = apply {
val info = KeyRingInfo(key, evaluationDate)
val info = inspectKeyRing(cert, evaluationDate)
val primaryKeyExpiration =
try {
info.primaryKeyExpirationDate
} catch (e: NoSuchElementException) {
throw UnacceptableSelfSignatureException(OpenPgpFingerprint.of(key))
throw UnacceptableSelfSignatureException(
OpenPgpFingerprint.of(cert.pgpPublicKeyRing))
}
if (primaryKeyExpiration != null && primaryKeyExpiration < evaluationDate) {
throw ExpiredKeyException(OpenPgpFingerprint.of(key), primaryKeyExpiration)
throw ExpiredKeyException(
OpenPgpFingerprint.of(cert.pgpPublicKeyRing), primaryKeyExpiration)
}
var encryptionSubkeys =
selector.selectEncryptionSubkeys(
info.getEncryptionSubkeys(purpose).map { it.pgpPublicKey })
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
@ -197,31 +311,26 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
info.validSubkeys
.filter { it.pgpPublicKey.isEncryptionKey }
.filter { info.getKeyFlagsOf(it.keyIdentifier).isEmpty() }
.map { it.pgpPublicKey }
}
if (encryptionSubkeys.isEmpty()) {
throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key))
throw UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(cert.pgpPublicKeyRing))
}
for (subkey in encryptionSubkeys) {
val keyId = SubkeyIdentifier(key, subkey.keyID)
val keyId = SubkeyIdentifier(subkey)
_keyRingInfo[keyId] = info
_keyViews[keyId] = KeyAccessor.ViaKeyId(info, keyId)
addRecipientKey(key, subkey, wildcardKeyId)
addRecipientKey(subkey, wildcardKeyId)
}
}
private fun addRecipientKey(
certificate: PGPPublicKeyRing,
key: PGPPublicKey,
wildcardKeyId: Boolean
) {
_encryptionKeyIdentifiers.add(SubkeyIdentifier(certificate, key.keyID))
private fun addRecipientKey(key: OpenPGPComponentKey, wildcardKeyId: Boolean) {
_encryptionKeyIdentifiers.add(SubkeyIdentifier(key))
addEncryptionMethod(
ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(key).also {
it.setUseWildcardKeyID(wildcardKeyId)
})
ImplementationFactory.getInstance()
.getPublicKeyKeyEncryptionMethodGenerator(key.pgpPublicKey)
.also { it.setUseWildcardKeyID(wildcardKeyId) })
}
/**
@ -295,7 +404,9 @@ class EncryptionOptions(private val purpose: EncryptionPurpose) {
fun hasEncryptionMethod() = _encryptionMethods.isNotEmpty()
fun interface EncryptionKeySelector {
fun selectEncryptionSubkeys(encryptionCapableKeys: List<PGPPublicKey>): List<PGPPublicKey>
fun selectEncryptionSubkeys(
encryptionCapableKeys: List<OpenPGPComponentKey>
): List<OpenPGPComponentKey>
}
companion object {

View file

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

View file

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