diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index 47768dcb..d8670947 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -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) /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt index c6683480..4dbfe64b 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/EncryptionOptions.kt @@ -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) = 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, 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): List + fun selectEncryptionSubkeys( + encryptionCapableKeys: List + ): List } companion object { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt index 61bdd282..e58a677f 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/CertificateValidator.kt @@ -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 diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java index 3c8b4749..809b90ae 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java @@ -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 selectEncryptionSubkeys(@NotNull List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@NotNull List 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 selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return Collections.emptyList(); } }));