From c039ab543aa60aa192e28877d3430c4618d7ed7e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 10 Feb 2025 17:05:16 +0100 Subject: [PATCH] WIP: Transform Options and OpenPgpMessageInputStream --- .../java/org/gnupg/GnuPGDummyKeyUtil.java | 6 + .../main/kotlin/org/pgpainless/PGPainless.kt | 19 ++- .../OpenPGPCertificateExtensions.kt | 8 ++ .../extensions/OpenPGPKeyExtensions.kt | 8 ++ .../extensions/PGPSecretKeyRingExtensions.kt | 5 +- .../ConsumerOptions.kt | 55 +++++++-- .../MessageMetadata.kt | 15 ++- .../OpenPgpMessageInputStream.kt | 109 ++++++++++-------- .../key/protection/SecretKeyRingProtector.kt | 20 +++- .../protection/UnprotectedKeysProtector.kt | 13 ++- .../consumer/OnePassSignatureCheck.kt | 7 +- .../java/org/gnupg/GnuPGDummyKeyUtilTest.java | 53 +++++---- .../pgpainless/example/DecryptOrVerify.java | 24 ++-- 13 files changed, 231 insertions(+), 111 deletions(-) create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt diff --git a/pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java b/pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java index 42af92d8..754ef3fa 100644 --- a/pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java +++ b/pgpainless-core/src/main/java/org/gnupg/GnuPGDummyKeyUtil.java @@ -10,6 +10,7 @@ import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.bcpg.SecretSubkeyPacket; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.pgpainless.key.SubkeyIdentifier; import javax.annotation.Nonnull; @@ -60,6 +61,11 @@ public final class GnuPGDummyKeyUtil { return hardwareBackedKeys; } + public static Builder modify(@Nonnull OpenPGPKey key) + { + return modify(key.getPGPSecretKeyRing()); + } + /** * Modify the given {@link PGPSecretKeyRing}. * diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt index 04ae3481..47768dcb 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/PGPainless.kt @@ -11,7 +11,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.api.OpenPGPApi +import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPImplementation +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKeyReader import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi import org.pgpainless.algorithm.OpenPGPKeyVersion import org.pgpainless.bouncycastle.PolicyAdapter @@ -43,6 +46,14 @@ class PGPainless( fun generateKey(version: OpenPGPKeyVersion = OpenPGPKeyVersion.v4): KeyRingTemplates = KeyRingTemplates(version) + fun readKey(): OpenPGPKeyReader = api.readKeyOrCertificate() + + fun toKey(secretKeyRing: PGPSecretKeyRing): OpenPGPKey = + OpenPGPKey(secretKeyRing, implementation) + + fun toCertificate(publicKeyRing: PGPPublicKeyRing): OpenPGPCertificate = + OpenPGPCertificate(publicKeyRing, implementation) + companion object { @Volatile private var instance: PGPainless? = null @@ -81,7 +92,9 @@ class PGPainless( * * @return builder */ - @JvmStatic fun readKeyRing() = KeyRingReader() + @Deprecated("Use readKey() instead.", replaceWith = ReplaceWith("readKey()")) + @JvmStatic + fun readKeyRing() = KeyRingReader() /** * Extract a public key certificate from a secret key. @@ -90,6 +103,7 @@ class PGPainless( * @return public key certificate */ @JvmStatic + @Deprecated("Use toKey() and then .toCertificate() instead.") fun extractCertificate(secretKey: PGPSecretKeyRing) = KeyRingUtils.publicKeyRingFrom(secretKey) @@ -190,6 +204,9 @@ class PGPainless( fun inspectKeyRing(key: PGPKeyRing, referenceTime: Date = Date()) = KeyRingInfo(key, referenceTime) + fun inspectKeyRing(key: OpenPGPKey, referenceTime: Date = Date()) = + KeyRingInfo(key, getPolicy(), referenceTime) + /** * Access, and make changes to PGPainless policy on acceptable/default algorithms etc. * diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt new file mode 100644 index 00000000..31e4fae6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPCertificateExtensions.kt @@ -0,0 +1,8 @@ +package org.pgpainless.bouncycastle.extensions + +import org.bouncycastle.openpgp.PGPOnePassSignature +import org.bouncycastle.openpgp.api.OpenPGPCertificate +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey + +fun OpenPGPCertificate.getSigningKeyFor(ops: PGPOnePassSignature): OpenPGPComponentKey? = + this.getKey(ops.keyIdentifier) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt new file mode 100644 index 00000000..c78a79db --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/OpenPGPKeyExtensions.kt @@ -0,0 +1,8 @@ +package org.pgpainless.bouncycastle.extensions + +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey + +fun OpenPGPKey.getSecretKeyFor(pkesk: PGPPublicKeyEncryptedData): OpenPGPSecretKey? = + this.getSecretKey(pkesk.keyIdentifier) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt index 99c562e6..53e7e0c5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/bouncycastle/extensions/PGPSecretKeyRingExtensions.kt @@ -73,7 +73,4 @@ fun PGPSecretKeyRing.getSecretKeyFor(onePassSignature: PGPOnePassSignature): PGP 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.") - } + this.getSecretKey(pkesk.keyIdentifier) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt index 39a4e8e4..de03b9d3 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/ConsumerOptions.kt @@ -4,11 +4,16 @@ package org.pgpainless.decryption_verification +import org.bouncycastle.bcpg.KeyIdentifier import java.io.IOException import java.io.InputStream import java.util.* import org.bouncycastle.openpgp.* +import org.bouncycastle.openpgp.api.OpenPGPCertificate +import org.bouncycastle.openpgp.api.OpenPGPImplementation +import org.bouncycastle.openpgp.api.OpenPGPKey import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory +import org.pgpainless.PGPainless import org.pgpainless.bouncycastle.extensions.getPublicKeyFor import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy @@ -19,7 +24,9 @@ import org.pgpainless.util.Passphrase import org.pgpainless.util.SessionKey /** Options for decryption and signature verification. */ -class ConsumerOptions { +class ConsumerOptions( + private val implementation: OpenPGPImplementation +) { private var ignoreMDCErrors = false var isDisableAsciiArmorCRC = false @@ -34,7 +41,7 @@ class ConsumerOptions { private var sessionKey: SessionKey? = null private val customDecryptorFactories = mutableMapOf() - private val decryptionKeys = mutableMapOf() + private val decryptionKeys = mutableMapOf() private val decryptionPassphrases = mutableSetOf() private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy() @@ -65,6 +72,10 @@ class ConsumerOptions { fun getVerifyNotAfter() = verifyNotAfter + fun addVerificationCert(verificationCert: OpenPGPCertificate): ConsumerOptions = apply { + this.certificates.addCertificate(verificationCert) + } + /** * Add a certificate (public key ring) for signature verification. * @@ -155,6 +166,12 @@ class ConsumerOptions { fun getSessionKey() = sessionKey + @JvmOverloads + fun addDecryptionKey( + key: OpenPGPKey, + protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() + ) = apply { decryptionKeys[key] = protector } + /** * Add a key for message decryption. If the key is encrypted, the [SecretKeyRingProtector] is * used to decrypt it when needed. @@ -167,7 +184,7 @@ class ConsumerOptions { fun addDecryptionKey( key: PGPSecretKeyRing, protector: SecretKeyRingProtector = SecretKeyRingProtector.unprotectedKeys() - ) = apply { decryptionKeys[key] = protector } + ) = addDecryptionKey(OpenPGPKey(key, implementation), protector) /** * Add the keys in the provided key collection for message decryption. @@ -270,7 +287,7 @@ class ConsumerOptions { * @param decryptionKeyRing secret key * @return protector for that particular secret key */ - fun getSecretKeyProtector(decryptionKeyRing: PGPSecretKeyRing): SecretKeyRingProtector? { + fun getSecretKeyProtector(decryptionKeyRing: OpenPGPKey): SecretKeyRingProtector? { return decryptionKeys[decryptionKeyRing] } @@ -378,14 +395,20 @@ class ConsumerOptions { * available signer certificates. */ class CertificateSource { - private val explicitCertificates: MutableSet = mutableSetOf() + private val explicitCertificates: MutableSet = mutableSetOf() /** * Add a certificate as verification cert explicitly. * * @param certificate certificate */ - fun addCertificate(certificate: PGPPublicKeyRing) { + fun addCertificate(certificate: PGPPublicKeyRing, + implementation: OpenPGPImplementation = PGPainless.getInstance().implementation + ) { + explicitCertificates.add(OpenPGPCertificate(certificate, implementation)) + } + + fun addCertificate(certificate: OpenPGPCertificate) { explicitCertificates.add(certificate) } @@ -394,7 +417,7 @@ class ConsumerOptions { * * @return explicitly set verification certs */ - fun getExplicitCertificates(): Set { + fun getExplicitCertificates(): Set { return explicitCertificates.toSet() } @@ -406,15 +429,23 @@ class ConsumerOptions { * @param keyId key id * @return certificate */ - fun getCertificate(keyId: Long): PGPPublicKeyRing? { - return explicitCertificates.firstOrNull { it.getPublicKey(keyId) != null } + fun getCertificate(keyId: Long): OpenPGPCertificate? { + return getCertificate(KeyIdentifier(keyId)) } - fun getCertificate(signature: PGPSignature): PGPPublicKeyRing? = - explicitCertificates.firstOrNull { it.getPublicKeyFor(signature) != null } + fun getCertificate(identifier: KeyIdentifier): OpenPGPCertificate? { + return explicitCertificates.firstOrNull { it.getKey(identifier) != null } + } + + fun getCertificate(signature: PGPSignature): OpenPGPCertificate? = + explicitCertificates.firstOrNull { it.getSigningKeyFor(signature) != null } } companion object { - @JvmStatic fun get() = ConsumerOptions() + @JvmStatic + @JvmOverloads + fun get( + implementation: OpenPGPImplementation = PGPainless.getInstance().implementation + ) = ConsumerOptions(implementation) } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt index f7238391..44321bb2 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageMetadata.kt @@ -4,10 +4,12 @@ package org.pgpainless.decryption_verification +import org.bouncycastle.bcpg.KeyIdentifier import java.util.* import javax.annotation.Nonnull import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPLiteralData +import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.SymmetricKeyAlgorithm @@ -47,9 +49,15 @@ class MessageMetadata(val message: Message) { if (encryptionAlgorithm == null) false else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL - fun isEncryptedFor(keys: PGPKeyRing): Boolean { + fun isEncryptedFor(cert: OpenPGPCertificate): Boolean { return encryptionLayers.asSequence().any { - it.recipients.any { keyId -> keys.getPublicKey(keyId) != null } + it.recipients.any { keyId -> cert.getKey(KeyIdentifier(keyId)) != null } + } + } + + fun isEncryptedFor(cert: PGPKeyRing): Boolean { + return encryptionLayers.asSequence().any { + it.recipients.any { keyId -> cert.getPublicKey(keyId) != null } } } @@ -270,6 +278,9 @@ class MessageMetadata(val message: Message) { fun isVerifiedSignedBy(keys: PGPKeyRing) = verifiedSignatures.any { keys.matches(it.signingKey) } + fun isVerifiedSignedBy(cert: OpenPGPCertificate) = + verifiedSignatures.any { cert.pgpKeyRing.matches(it.signingKey) } + fun isVerifiedDetachedSignedBy(fingerprint: OpenPgpFingerprint) = verifiedDetachedSignatures.any { it.signingKey.matches(fingerprint) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt index 06018e06..b4957a75 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.kt @@ -18,6 +18,7 @@ import org.bouncycastle.openpgp.PGPCompressedData import org.bouncycastle.openpgp.PGPEncryptedData import org.bouncycastle.openpgp.PGPEncryptedDataList import org.bouncycastle.openpgp.PGPException +import org.bouncycastle.openpgp.PGPKeyPair import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPPBEEncryptedData import org.bouncycastle.openpgp.PGPPrivateKey @@ -27,6 +28,10 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.api.OpenPGPCertificate +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPSecretKey +import org.bouncycastle.openpgp.api.OpenPGPSignature.OpenPGPDocumentSignature import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory import org.bouncycastle.util.io.TeeInputStream @@ -37,6 +42,7 @@ import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.SymmetricKeyAlgorithm import org.pgpainless.bouncycastle.extensions.getPublicKeyFor import org.pgpainless.bouncycastle.extensions.getSecretKeyFor +import org.pgpainless.bouncycastle.extensions.getSigningKeyFor import org.pgpainless.bouncycastle.extensions.issuerKeyId import org.pgpainless.bouncycastle.extensions.unlock import org.pgpainless.decryption_verification.MessageMetadata.CompressedData @@ -66,6 +72,7 @@ import org.pgpainless.signature.consumer.SignatureValidator import org.pgpainless.util.ArmoredInputStreamFactory import org.pgpainless.util.SessionKey import org.slf4j.LoggerFactory +import kotlin.math.sign class OpenPgpMessageInputStream( type: Type, @@ -400,30 +407,33 @@ class OpenPgpMessageInputStream( } val postponedDueToMissingPassphrase = - mutableListOf>() + mutableListOf>() // try (known) secret keys esks.pkesks.forEach { pkesk -> - LOGGER.debug("Encountered PKESK for recipient ${pkesk.keyID.openPgpKeyId()}") + LOGGER.debug("Encountered PKESK for recipient ${pkesk.keyIdentifier}") val decryptionKeyCandidates = getDecryptionKeys(pkesk) for (decryptionKeys in decryptionKeyCandidates) { val secretKey = decryptionKeys.getSecretKeyFor(pkesk)!! - val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID) - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + if (hasUnsupportedS2KSpecifier(secretKey)) { continue } - LOGGER.debug("Attempt decryption using secret key $decryptionKeyId") + LOGGER.debug("Attempt decryption using secret key ${decryptionKeys.keyIdentifier}") val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue - if (!protector.hasPassphraseFor(secretKey.keyID)) { + if (!protector.hasPassphraseFor(secretKey.keyIdentifier)) { LOGGER.debug( - "Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") + "Missing passphrase for key ${decryptionKeys.keyIdentifier}. Postponing decryption until all other keys were tried.") postponedDueToMissingPassphrase.add(secretKey to pkesk) continue } val privateKey = secretKey.unlock(protector) - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + if (decryptWithPrivateKey( + esks, + privateKey, + SubkeyIdentifier(secretKey.openPGPKey.pgpSecretKeyRing, secretKey.keyIdentifier), + pkesk)) { return true } } @@ -431,24 +441,27 @@ class OpenPgpMessageInputStream( // try anonymous secret keys for (pkesk in esks.anonPkesks) { - for ((decryptionKeys, secretKey) in findPotentialDecryptionKeys(pkesk)) { - val decryptionKeyId = SubkeyIdentifier(decryptionKeys, secretKey.keyID) - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + for (decryptionKeys in findPotentialDecryptionKeys(pkesk)) { + if (hasUnsupportedS2KSpecifier(decryptionKeys)) { continue } - LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeyId.") - val protector = options.getSecretKeyProtector(decryptionKeys) ?: continue + LOGGER.debug("Attempt decryption of anonymous PKESK with key $decryptionKeys.") + val protector = options.getSecretKeyProtector(decryptionKeys.openPGPKey) ?: continue - if (!protector.hasPassphraseFor(secretKey.keyID)) { + if (!protector.hasPassphraseFor(decryptionKeys.keyIdentifier)) { LOGGER.debug( - "Missing passphrase for key $decryptionKeyId. Postponing decryption until all other keys were tried.") - postponedDueToMissingPassphrase.add(secretKey to pkesk) + "Missing passphrase for key ${decryptionKeys.keyIdentifier}. Postponing decryption until all other keys were tried.") + postponedDueToMissingPassphrase.add(decryptionKeys to pkesk) continue } - val privateKey = secretKey.unlock(protector) - if (decryptWithPrivateKey(esks, privateKey, decryptionKeyId, pkesk)) { + val privateKey = decryptionKeys.unlock(protector) + if (decryptWithPrivateKey( + esks, + privateKey, + SubkeyIdentifier(decryptionKeys.openPGPKey.pgpSecretKeyRing, privateKey.keyIdentifier), + pkesk)) { return true } } @@ -463,10 +476,10 @@ class OpenPgpMessageInputStream( } else if (options.getMissingKeyPassphraseStrategy() == MissingKeyPassphraseStrategy.INTERACTIVE) { for ((secretKey, pkesk) in postponedDueToMissingPassphrase) { - val keyId = secretKey.keyID + val keyId = secretKey.keyIdentifier val decryptionKeys = getDecryptionKey(pkesk)!! - val decryptionKeyId = SubkeyIdentifier(decryptionKeys, keyId) - if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { + val decryptionKeyId = SubkeyIdentifier(decryptionKeys.pgpSecretKeyRing, keyId) + if (hasUnsupportedS2KSpecifier(secretKey)) { continue } @@ -489,24 +502,23 @@ class OpenPgpMessageInputStream( private fun decryptWithPrivateKey( esks: SortedESKs, - privateKey: PGPPrivateKey, + privateKey: PGPKeyPair, decryptionKeyId: SubkeyIdentifier, pkesk: PGPPublicKeyEncryptedData ): Boolean { val decryptorFactory = - ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey) + ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey.privateKey) return decryptPKESKAndStream(esks, decryptionKeyId, decryptorFactory, pkesk) } private fun hasUnsupportedS2KSpecifier( - secretKey: PGPSecretKey, - decryptionKeyId: SubkeyIdentifier + secretKey: OpenPGPSecretKey ): Boolean { - val s2k = secretKey.s2K + val s2k = secretKey.pgpSecretKey.s2K if (s2k != null) { if (s2k.type in 100..110) { LOGGER.debug( - "Skipping PKESK because key $decryptionKeyId has unsupported private S2K specifier ${s2k.type}") + "Skipping PKESK because key ${secretKey.keyIdentifier} has unsupported private S2K specifier ${s2k.type}") return true } } @@ -672,26 +684,26 @@ class OpenPgpMessageInputStream( return MessageMetadata((layerMetadata as Message)) } - private fun getDecryptionKey(keyId: Long): PGPSecretKeyRing? = + private fun getDecryptionKey(keyId: Long): OpenPGPKey? = options.getDecryptionKeys().firstOrNull { - it.any { k -> k.keyID == keyId } + it.pgpSecretKeyRing.any { k -> k.keyID == keyId } .and( PGPainless.inspectKeyRing(it).decryptionSubkeys.any { k -> k.keyIdentifier.keyId == keyId }) } - private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): PGPSecretKeyRing? = + private fun getDecryptionKey(pkesk: PGPPublicKeyEncryptedData): OpenPGPKey? = options.getDecryptionKeys().firstOrNull { - it.getSecretKeyFor(pkesk) != null && + it.pgpSecretKeyRing.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> pkesk.keyIdentifier.matches(subkey.keyIdentifier) } } - private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List = + private fun getDecryptionKeys(pkesk: PGPPublicKeyEncryptedData): List = options.getDecryptionKeys().filter { - it.getSecretKeyFor(pkesk) != null && + it.pgpSecretKeyRing.getSecretKeyFor(pkesk) != null && PGPainless.inspectKeyRing(it).decryptionSubkeys.any { subkey -> pkesk.keyIdentifier.matches(subkey.keyIdentifier) } @@ -699,15 +711,15 @@ class OpenPgpMessageInputStream( private fun findPotentialDecryptionKeys( pkesk: PGPPublicKeyEncryptedData - ): List> { + ): List { val algorithm = pkesk.algorithm - val candidates = mutableListOf>() + val candidates = mutableListOf() options.getDecryptionKeys().forEach { val info = PGPainless.inspectKeyRing(it) for (key in info.decryptionSubkeys) { if (key.pgpPublicKey.algorithm == algorithm && info.isSecretKeyAvailable(key.keyIdentifier)) { - candidates.add(it to it.getSecretKey(key.keyIdentifier)) + candidates.add(it.getSecretKey(key.keyIdentifier)) } } } @@ -753,8 +765,8 @@ class OpenPgpMessageInputStream( } private class Signatures(val options: ConsumerOptions) : OutputStream() { - val detachedSignatures = mutableListOf() - val prependedSignatures = mutableListOf() + val detachedSignatures = mutableListOf() + val prependedSignatures = mutableListOf() val onePassSignatures = mutableListOf() val opsUpdateStack = ArrayDeque>() var literalOPS = mutableListOf() @@ -798,22 +810,21 @@ class OpenPgpMessageInputStream( } } - fun initializeSignature(signature: PGPSignature): SignatureCheck? { + fun initializeSignature(signature: PGPSignature): OpenPGPDocumentSignature? { val certificate = findCertificate(signature) ?: return null - val publicKey = certificate.getPublicKeyFor(signature) ?: return null - val verifierKey = SubkeyIdentifier(certificate, publicKey.keyID) - initialize(signature, publicKey) - return SignatureCheck(signature, certificate, verifierKey) + val publicKey = certificate.getSigningKeyFor(signature) ?: return null + initialize(signature, publicKey.pgpPublicKey) + return OpenPGPDocumentSignature(signature, publicKey) } fun addOnePassSignature(signature: PGPOnePassSignature) { val certificate = findCertificate(signature) if (certificate != null) { - val publicKey = certificate.getPublicKeyFor(signature) + val publicKey = certificate.getSigningKeyFor(signature) if (publicKey != null) { val ops = OnePassSignatureCheck(signature, certificate) - initialize(signature, publicKey) + initialize(signature, publicKey.pgpPublicKey) onePassSignatures.add(ops) literalOPS.add(ops) } @@ -844,7 +855,7 @@ class OpenPgpMessageInputStream( val verification = SignatureVerification( signature, - SubkeyIdentifier(check.verificationKeys, check.onePassSignature.keyID)) + SubkeyIdentifier(check.verificationKeys.pgpPublicKeyRing, check.onePassSignature.keyIdentifier)) try { SignatureValidator.signatureWasCreatedInBounds( @@ -882,7 +893,7 @@ class OpenPgpMessageInputStream( opsUpdateStack.removeFirst() } - private fun findCertificate(signature: PGPSignature): PGPPublicKeyRing? { + private fun findCertificate(signature: PGPSignature): OpenPGPCertificate? { val cert = options.getCertificateSource().getCertificate(signature) if (cert != null) { return cert @@ -896,7 +907,7 @@ class OpenPgpMessageInputStream( return null // TODO: Missing cert for sig } - private fun findCertificate(signature: PGPOnePassSignature): PGPPublicKeyRing? { + private fun findCertificate(signature: PGPOnePassSignature): OpenPGPCertificate? { val cert = options.getCertificateSource().getCertificate(signature.keyID) if (cert != null) { return cert @@ -977,7 +988,7 @@ class OpenPgpMessageInputStream( for (prepended in prependedSignatures) { val verification = - SignatureVerification(prepended.signature, prepended.signingKeyIdentifier) + SignatureVerification(prepended.signature, prepended.keyIdentifier) try { SignatureValidator.signatureWasCreatedInBounds( options.getVerifyNotBefore(), options.getVerifyNotAfter()) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt index 5e86d950..373c964c 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt @@ -4,15 +4,17 @@ package org.pgpainless.key.protection -import kotlin.jvm.Throws +import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.PGPException import org.bouncycastle.openpgp.PGPSecretKey import org.bouncycastle.openpgp.PGPSecretKeyRing +import org.bouncycastle.openpgp.api.KeyPassphraseProvider import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider import org.pgpainless.util.Passphrase +import kotlin.Throws /** * Task of the [SecretKeyRingProtector] is to map encryptor/decryptor objects to key-ids. @@ -22,7 +24,7 @@ import org.pgpainless.util.Passphrase * While it is easy to create an implementation of this interface that fits your needs, there are a * bunch of implementations ready for use. */ -interface SecretKeyRingProtector { +interface SecretKeyRingProtector : KeyPassphraseProvider { /** * Returns true, if the protector has a passphrase for the key with the given key-id. @@ -30,7 +32,9 @@ interface SecretKeyRingProtector { * @param keyId key id * @return true if it has a passphrase, false otherwise */ - fun hasPassphraseFor(keyId: Long): Boolean + fun hasPassphraseFor(keyId: Long): Boolean = hasPassphraseFor(KeyIdentifier(keyId)) + + fun hasPassphraseFor(keyIdentifier: KeyIdentifier): Boolean /** * Return a decryptor for the key of id `keyId`. This method returns null if the key is @@ -39,7 +43,10 @@ interface SecretKeyRingProtector { * @param keyId id of the key * @return decryptor for the key */ - @Throws(PGPException::class) fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? + @Throws(PGPException::class) fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? = + getDecryptor(KeyIdentifier(keyId)) + + @Throws(PGPException::class) fun getDecryptor(keyIdentifier: KeyIdentifier): PBESecretKeyDecryptor? /** * Return an encryptor for the key of id `keyId`. This method returns null if the key is @@ -48,7 +55,10 @@ interface SecretKeyRingProtector { * @param keyId id of the key * @return encryptor for the key */ - @Throws(PGPException::class) fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? + @Throws(PGPException::class) fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? = + getEncryptor(KeyIdentifier(keyId)) + + @Throws(PGPException::class) fun getEncryptor(keyIdentifier: KeyIdentifier): PBESecretKeyEncryptor? companion object { diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt index a25bb31a..8d81973d 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/UnprotectedKeysProtector.kt @@ -3,14 +3,21 @@ // SPDX-License-Identifier: Apache-2.0 package org.pgpainless.key.protection +import org.bouncycastle.bcpg.KeyIdentifier +import org.bouncycastle.openpgp.api.OpenPGPKey +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor + /** * Implementation of the [SecretKeyRingProtector] which assumes that all handled keys are not * password protected. */ class UnprotectedKeysProtector : SecretKeyRingProtector { - override fun hasPassphraseFor(keyId: Long) = true + override fun hasPassphraseFor(keyIdentifier: KeyIdentifier): Boolean = true - override fun getDecryptor(keyId: Long) = null + override fun getDecryptor(keyIdentifier: KeyIdentifier): PBESecretKeyDecryptor? = null - override fun getEncryptor(keyId: Long) = null + override fun getEncryptor(keyIdentifier: KeyIdentifier): PBESecretKeyEncryptor? = null + + override fun getKeyPassword(p0: OpenPGPKey.OpenPGPSecretKey?): CharArray? = null } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt index 4a89e0b2..59d65cdb 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/OnePassSignatureCheck.kt @@ -7,6 +7,9 @@ package org.pgpainless.signature.consumer import org.bouncycastle.openpgp.PGPOnePassSignature import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPSignature +import org.bouncycastle.openpgp.api.OpenPGPCertificate +import org.bouncycastle.openpgp.api.OpenPGPCertificate.OpenPGPComponentKey +import org.pgpainless.bouncycastle.extensions.getSigningKeyFor import org.pgpainless.key.SubkeyIdentifier /** @@ -20,7 +23,7 @@ import org.pgpainless.key.SubkeyIdentifier */ data class OnePassSignatureCheck( val onePassSignature: PGPOnePassSignature, - val verificationKeys: PGPPublicKeyRing, + val verificationKeys: OpenPGPCertificate, var signature: PGPSignature? = null ) { @@ -30,5 +33,5 @@ data class OnePassSignatureCheck( * @return signing key fingerprint */ val signingKey: SubkeyIdentifier - get() = SubkeyIdentifier(verificationKeys, onePassSignature.keyID) + get() = SubkeyIdentifier(verificationKeys.pgpPublicKeyRing, onePassSignature.keyID) } diff --git a/pgpainless-core/src/test/java/org/gnupg/GnuPGDummyKeyUtilTest.java b/pgpainless-core/src/test/java/org/gnupg/GnuPGDummyKeyUtilTest.java index 87c5b02e..51a6c4c5 100644 --- a/pgpainless-core/src/test/java/org/gnupg/GnuPGDummyKeyUtilTest.java +++ b/pgpainless-core/src/test/java/org/gnupg/GnuPGDummyKeyUtilTest.java @@ -17,6 +17,8 @@ import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SecretKeyPacket; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.key.SubkeyIdentifier; @@ -178,8 +180,9 @@ public class GnuPGDummyKeyUtilTest { @Test public void testMoveAllKeysToCard() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_ON_CARD); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + OpenPGPKey expected = reader.parseKey(ALL_KEYS_ON_CARD); PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any(), cardSerial); @@ -190,46 +193,50 @@ public class GnuPGDummyKeyUtilTest { assertEquals(S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD, s2K.getProtectionMode()); } - assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded()); } @Test public void testMovePrimaryKeyToCard() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(PRIMARY_KEY_ON_CARD); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + OpenPGPKey expected = reader.parseKey(PRIMARY_KEY_ON_CARD); PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(primaryKeyId), cardSerial); - assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded()); } @Test public void testMoveEncryptionKeyToCard() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_KEY_ON_CARD); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + OpenPGPKey expected = reader.parseKey(ENCRYPTION_KEY_ON_CARD); PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId), cardSerial); - assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded()); } @Test public void testMoveSigningKeyToCard() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(SIGNATURE_KEY_ON_CARD); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + OpenPGPKey expected = reader.parseKey(SIGNATURE_KEY_ON_CARD); PGPSecretKeyRing onCard = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(signatureKeyId), cardSerial); - assertArrayEquals(expected.getEncoded(), onCard.getEncoded()); + assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), onCard.getEncoded()); } @Test public void testRemoveAllKeys() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - PGPSecretKeyRing expected = PGPainless.readKeyRing().secretKeyRing(ALL_KEYS_REMOVED); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + OpenPGPKey expected = reader.parseKey(ALL_KEYS_REMOVED); PGPSecretKeyRing removedSecretKeys = GnuPGDummyKeyUtil.modify(secretKeys) .removePrivateKeys(GnuPGDummyKeyUtil.KeyFilter.any()); @@ -240,33 +247,35 @@ public class GnuPGDummyKeyUtilTest { assertEquals(GnuPGDummyExtension.NO_PRIVATE_KEY.getId(), s2k.getProtectionMode()); } - assertArrayEquals(expected.getEncoded(), removedSecretKeys.getEncoded()); + assertArrayEquals(expected.getPGPSecretKeyRing().getEncoded(), removedSecretKeys.getEncoded()); } @Test public void testGetSingleIdOfHardwareBackedKey() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty()); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys.getPGPSecretKeyRing()).isEmpty()); PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.only(encryptionKeyId)); Set hardwareBackedKeys = GnuPGDummyKeyUtil .getIdsOfKeysWithGnuPGS2KDivertedToCard(withHardwareBackedEncryptionKey); - assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys, encryptionKeyId)), hardwareBackedKeys); + assertEquals(Collections.singleton(new SubkeyIdentifier(secretKeys.getPGPSecretKeyRing(), encryptionKeyId)), hardwareBackedKeys); } @Test public void testGetIdsOfFullyHardwareBackedKey() throws IOException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(FULL_KEY); - assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys).isEmpty()); + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); + OpenPGPKey secretKeys = reader.parseKey(FULL_KEY); + assertTrue(GnuPGDummyKeyUtil.getIdsOfKeysWithGnuPGS2KDivertedToCard(secretKeys.getPGPSecretKeyRing()).isEmpty()); PGPSecretKeyRing withHardwareBackedEncryptionKey = GnuPGDummyKeyUtil.modify(secretKeys) .divertPrivateKeysToCard(GnuPGDummyKeyUtil.KeyFilter.any()); Set expected = new HashSet<>(); - for (PGPSecretKey key : secretKeys) { - expected.add(new SubkeyIdentifier(secretKeys, key.getKeyID())); + for (PGPSecretKey key : secretKeys.getPGPSecretKeyRing()) { + expected.add(new SubkeyIdentifier(secretKeys.getPGPSecretKeyRing(), key.getKeyID())); } Set hardwareBackedKeys = GnuPGDummyKeyUtil diff --git a/pgpainless-core/src/test/java/org/pgpainless/example/DecryptOrVerify.java b/pgpainless-core/src/test/java/org/pgpainless/example/DecryptOrVerify.java index c35b3572..a7e9cb5a 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/example/DecryptOrVerify.java +++ b/pgpainless-core/src/test/java/org/pgpainless/example/DecryptOrVerify.java @@ -15,8 +15,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.openpgp.api.OpenPGPKeyReader; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -134,15 +135,16 @@ public class DecryptOrVerify { "=9PiO\n" + "-----END PGP MESSAGE-----"; - private static PGPSecretKeyRing secretKey; - private static PGPPublicKeyRing certificate; + private static OpenPGPKey secretKey; + private static OpenPGPCertificate certificate; @BeforeAll public static void prepare() throws IOException { + OpenPGPKeyReader reader = PGPainless.getInstance().readKey(); // read the secret key - secretKey = PGPainless.readKeyRing().secretKeyRing(KEY); + secretKey = reader.parseKey(KEY); // certificate is the public part of the key - certificate = PGPainless.extractCertificate(secretKey); + certificate = secretKey.toCertificate(); } /** @@ -153,7 +155,7 @@ public class DecryptOrVerify { */ @Test public void decryptMessage() throws PGPException, IOException { - ConsumerOptions consumerOptions = new ConsumerOptions() + ConsumerOptions consumerOptions = ConsumerOptions.get() .addDecryptionKey(secretKey, keyProtector); // add the decryption key ring ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); @@ -186,7 +188,7 @@ public class DecryptOrVerify { */ @Test public void decryptMessageAndVerifySignatures() throws PGPException, IOException { - ConsumerOptions consumerOptions = new ConsumerOptions() + ConsumerOptions consumerOptions = ConsumerOptions.get() .addDecryptionKey(secretKey, keyProtector) // provide the secret key of the recipient for decryption .addVerificationCert(certificate); // provide the signers public key for signature verification @@ -218,7 +220,7 @@ public class DecryptOrVerify { */ @Test public void verifySignatures() throws PGPException, IOException { - ConsumerOptions options = new ConsumerOptions() + ConsumerOptions options = ConsumerOptions.get() .addVerificationCert(certificate); // provide the signers certificate for verification of signatures for (String signed : new String[] {INBAND_SIGNED, CLEARTEXT_SIGNED}) { @@ -257,7 +259,7 @@ public class DecryptOrVerify { SigningOptions signingOptions = SigningOptions.get(); // for cleartext signed messages, we need to add a detached signature... - signingOptions.addDetachedSignature(keyProtector, secretKey, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT); + signingOptions.addDetachedSignature(keyProtector, secretKey.getPGPSecretKeyRing(), DocumentSignatureType.CANONICAL_TEXT_DOCUMENT); ProducerOptions producerOptions = ProducerOptions.sign(signingOptions) .setCleartextSigned(); // and declare that the message will be cleartext signed @@ -279,7 +281,7 @@ public class DecryptOrVerify { // and pass it to the decryption stream DecryptionStream verificationStream = PGPainless.decryptAndOrVerify() .onInputStream(signedIn) - .withOptions(new ConsumerOptions().addVerificationCert(certificate)); + .withOptions(ConsumerOptions.get().addVerificationCert(certificate)); // plain will receive the plaintext message ByteArrayOutputStream plain = new ByteArrayOutputStream();