From cb440776f2269ac4bd3d1b3b9741834fa1115228 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Feb 2025 15:14:04 +0100 Subject: [PATCH] Add workaround for decryption with non-encryption subkey --- .../ConsumerOptions.kt | 9 ++++++++ .../OpenPgpMessageInputStream.kt | 5 +++++ ...ntDecryptionUsingNonEncryptionKeyTest.java | 22 +++++++++++++++---- 3 files changed, 32 insertions(+), 4 deletions(-) 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 a11e3536..1ab218e5 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 @@ -43,6 +43,7 @@ class ConsumerOptions { private val decryptionPassphrases = mutableSetOf() private var missingKeyPassphraseStrategy = MissingKeyPassphraseStrategy.INTERACTIVE private var multiPassStrategy: MultiPassStrategy = InMemoryMultiPassStrategy() + private var allowDecryptionWithNonEncryptionKey: Boolean = false /** * Consider signatures on the message made before the given timestamp invalid. Null means no @@ -328,6 +329,14 @@ class ConsumerOptions { fun isIgnoreMDCErrors(): Boolean = ignoreMDCErrors + fun setAllowDecryptionWithNonEncryptionKey(allow: Boolean): ConsumerOptions = apply { + allowDecryptionWithNonEncryptionKey = allow + } + + fun getAllowDecryptionWithNonEncryptionKey(): Boolean { + return allowDecryptionWithNonEncryptionKey + } + /** * Force PGPainless to handle the data provided by the [InputStream] as non-OpenPGP data. This * workaround might come in handy if PGPainless accidentally mistakes the data for binary 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 2512c89c..10d60c75 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 @@ -409,6 +409,11 @@ class OpenPgpMessageInputStream( val decryptionKeyCandidates = getDecryptionKeys(pkesk) for (decryptionKeys in decryptionKeyCandidates) { val secretKey = decryptionKeys.getSecretKeyFor(pkesk)!! + if (!secretKey.isEncryptionKey && !options.getAllowDecryptionWithNonEncryptionKey()) { + LOGGER.debug( + "Message is encrypted for ${secretKey.keyIdentifier}, but the key is not encryption capable.") + continue + } if (hasUnsupportedS2KSpecifier(secretKey)) { continue } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java index ea54f2a4..9a80667d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java @@ -14,7 +14,6 @@ import java.nio.charset.StandardCharsets; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.util.io.Streams; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.exception.MissingDecryptionMethodException; @@ -206,15 +205,30 @@ public class PreventDecryptionUsingNonEncryptionKeyTest { } @Test - @Disabled public void nonEncryptionKeyCannotDecrypt() throws IOException { PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY); ByteArrayInputStream msgIn = new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8)); assertThrows(MissingDecryptionMethodException.class, () -> - PGPainless.decryptAndOrVerify() + PGPainless.decryptAndOrVerify() + .onInputStream(msgIn) + .withOptions(ConsumerOptions.get().addDecryptionKey(secretKeys))); + } + + @Test + public void nonEncryptionKeyCanDecryptIfAllowed() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY); + + ByteArrayInputStream msgIn = new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8)); + + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(msgIn) - .withOptions(ConsumerOptions.get().addDecryptionKey(secretKeys))); + .withOptions(ConsumerOptions.get() + .setAllowDecryptionWithNonEncryptionKey(true) + .addDecryptionKey(secretKeys)); + + byte[] decrypted = Streams.readAll(decryptionStream); + decryptionStream.close(); } }