From 7db10432fef91a31cdd83c6f22d71766f98a696e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 28 Mar 2025 16:02:10 +0100 Subject: [PATCH] Port MessageInspector --- .../MessageInspector.kt | 135 +++++++++--------- .../MessageInspectorTest.java | 28 ++-- 2 files changed, 83 insertions(+), 80 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt index e6d08dae..0a2fb971 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/MessageInspector.kt @@ -6,104 +6,103 @@ package org.pgpainless.decryption_verification import java.io.IOException import java.io.InputStream +import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.openpgp.* import org.bouncycastle.openpgp.api.OpenPGPImplementation +import org.pgpainless.PGPainless import org.pgpainless.util.ArmorUtils /** * Inspect an OpenPGP message to determine IDs of its encryption keys or whether it is passphrase * protected. */ -class MessageInspector { +class MessageInspector(val api: PGPainless = PGPainless.getInstance()) { /** * Info about an OpenPGP message. * - * @param keyIds List of recipient key ids for whom the message is encrypted. + * @param keyIdentifiers List of recipient [KeyIdentifiers][KeyIdentifier] for whom the message + * is encrypted. * @param isPassphraseEncrypted true, if the message is encrypted for a passphrase * @param isSignedOnly true, if the message is not encrypted, but signed using OnePassSignatures */ data class EncryptionInfo( - val keyIds: List, + val keyIdentifiers: List, val isPassphraseEncrypted: Boolean, val isSignedOnly: Boolean ) { val isEncrypted: Boolean get() = isPassphraseEncrypted || keyIds.isNotEmpty() + + val keyIds: List = keyIdentifiers.map { it.keyId } } - companion object { + /** + * Parses parts of the provided OpenPGP message in order to determine which keys were used to + * encrypt it. + * + * @param message OpenPGP message + * @return encryption info + * @throws PGPException in case the message is broken + * @throws IOException in case of an IO error + */ + @Throws(PGPException::class, IOException::class) + fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = + determineEncryptionInfoForMessage(message.byteInputStream()) - /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used - * to encrypt it. - * - * @param message OpenPGP message - * @return encryption info - * @throws PGPException in case the message is broken - * @throws IOException in case of an IO error - */ - @JvmStatic - @Throws(PGPException::class, IOException::class) - fun determineEncryptionInfoForMessage(message: String): EncryptionInfo = - determineEncryptionInfoForMessage(message.byteInputStream()) + /** + * Parses parts of the provided OpenPGP message in order to determine which keys were used to + * encrypt it. Note: This method does not rewind the passed in Stream, so you might need to take + * care of that yourselves. + * + * @param inputStream openpgp message + * @return encryption information + * @throws IOException in case of an IO error + * @throws PGPException if the message is broken + */ + @Throws(PGPException::class, IOException::class) + fun determineEncryptionInfoForMessage(inputStream: InputStream): EncryptionInfo { + return processMessage(ArmorUtils.getDecoderStream(inputStream)) + } - /** - * Parses parts of the provided OpenPGP message in order to determine which keys were used - * to encrypt it. Note: This method does not rewind the passed in Stream, so you might need - * to take care of that yourselves. - * - * @param inputStream openpgp message - * @return encryption information - * @throws IOException in case of an IO error - * @throws PGPException if the message is broken - */ - @JvmStatic - @Throws(PGPException::class, IOException::class) - fun determineEncryptionInfoForMessage(inputStream: InputStream): EncryptionInfo { - return processMessage(ArmorUtils.getDecoderStream(inputStream)) - } + @Throws(PGPException::class, IOException::class) + private fun processMessage(inputStream: InputStream): EncryptionInfo { + var objectFactory = api.implementation.pgpObjectFactory(inputStream) - @JvmStatic - @Throws(PGPException::class, IOException::class) - private fun processMessage(inputStream: InputStream): EncryptionInfo { - var objectFactory = OpenPGPImplementation.getInstance().pgpObjectFactory(inputStream) - - var n: Any? - while (objectFactory.nextObject().also { n = it } != null) { - when (val next = n!!) { - is PGPOnePassSignatureList -> { - if (!next.isEmpty) { - return EncryptionInfo( - listOf(), isPassphraseEncrypted = false, isSignedOnly = true) - } - } - is PGPEncryptedDataList -> { - var isPassphraseEncrypted = false - val keyIds = mutableListOf() - for (encryptedData in next) { - if (encryptedData is PGPPublicKeyEncryptedData) { - keyIds.add(encryptedData.keyID) - } else if (encryptedData is PGPPBEEncryptedData) { - isPassphraseEncrypted = true - } - } - // Data is encrypted, we cannot go deeper - return EncryptionInfo(keyIds, isPassphraseEncrypted, false) - } - is PGPCompressedData -> { - objectFactory = - OpenPGPImplementation.getInstance() - .pgpObjectFactory(PGPUtil.getDecoderStream(next.dataStream)) - continue - } - is PGPLiteralData -> { - break + var n: Any? + while (objectFactory.nextObject().also { n = it } != null) { + when (val next = n!!) { + is PGPOnePassSignatureList -> { + if (!next.isEmpty) { + return EncryptionInfo( + listOf(), isPassphraseEncrypted = false, isSignedOnly = true) } } + is PGPEncryptedDataList -> { + var isPassphraseEncrypted = false + val keyIdentifiers = mutableListOf() + for (encryptedData in next) { + if (encryptedData is PGPPublicKeyEncryptedData) { + keyIdentifiers.add(encryptedData.keyIdentifier) + } else if (encryptedData is PGPPBEEncryptedData) { + isPassphraseEncrypted = true + } + } + // Data is encrypted, we cannot go deeper + return EncryptionInfo(keyIdentifiers, isPassphraseEncrypted, false) + } + is PGPCompressedData -> { + objectFactory = + OpenPGPImplementation.getInstance() + .pgpObjectFactory(PGPUtil.getDecoderStream(next.dataStream)) + continue + } + is PGPLiteralData -> { + break + } } - return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false) } + return EncryptionInfo(listOf(), isPassphraseEncrypted = false, isSignedOnly = false) } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageInspectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageInspectorTest.java index b7382360..fce4186d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageInspectorTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MessageInspectorTest.java @@ -10,9 +10,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import org.bouncycastle.bcpg.KeyIdentifier; import org.bouncycastle.openpgp.PGPException; import org.junit.jupiter.api.Test; -import org.pgpainless.key.util.KeyIdUtil; public class MessageInspectorTest { @@ -26,14 +26,15 @@ public class MessageInspectorTest { "Z1/i3TYsmy8B0mMKkNYtpMk=\n" + "=IICf\n" + "-----END PGP MESSAGE-----\n"; + MessageInspector inspector = new MessageInspector(); - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); assertFalse(info.isPassphraseEncrypted()); assertFalse(info.isSignedOnly()); assertTrue(info.isEncrypted()); assertEquals(1, info.getKeyIds().size()); - assertEquals(KeyIdUtil.fromLongKeyId("4766F6B9D5F21EB6"), (long) info.getKeyIds().get(0)); + assertEquals(new KeyIdentifier("4766F6B9D5F21EB6"), info.getKeyIdentifiers().get(0)); } @Test @@ -51,15 +52,16 @@ public class MessageInspectorTest { "nxVuXey3iyihCFAfD8ZK1Rnh\n" + "=z6e0\n" + "-----END PGP MESSAGE-----"; + MessageInspector inspector = new MessageInspector(); - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); assertTrue(info.isEncrypted()); assertTrue(info.isPassphraseEncrypted()); assertEquals(2, info.getKeyIds().size()); assertFalse(info.isSignedOnly()); - assertTrue(info.getKeyIds().contains(KeyIdUtil.fromLongKeyId("4C6E8F99F6E47184"))); - assertTrue(info.getKeyIds().contains(KeyIdUtil.fromLongKeyId("1839079A640B2FAC"))); + assertTrue(info.getKeyIdentifiers().contains(new KeyIdentifier("4C6E8F99F6E47184"))); + assertTrue(info.getKeyIdentifiers().contains(new KeyIdentifier("1839079A640B2FAC"))); } @Test @@ -73,8 +75,8 @@ public class MessageInspectorTest { "Dvxwv8UPAA==\n" + "=nt5n\n" + "-----END PGP MESSAGE-----"; - - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector inspector = new MessageInspector(); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); assertTrue(info.isSignedOnly()); @@ -97,7 +99,8 @@ public class MessageInspectorTest { "KK0Ymg5GrsBTEGFm4jb1p+V85PPhsIioX3np/N3fkIfxFguTGZza33/GHy61+DTy\n" + "=SZU6\n" + "-----END PGP MESSAGE-----"; - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector inspector = new MessageInspector(); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); // Message is encrypted, so we cannot determine if it is signed or not. // It is not signed only @@ -117,8 +120,8 @@ public class MessageInspectorTest { "yyl0CF9DT05TT0xFYXlXgUp1c3Qgc29tZSB1bmVuY3J5cHRlZCBkYXRhLg==\n" + "=jVNT\n" + "-----END PGP MESSAGE-----"; - - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector inspector = new MessageInspector(); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); assertFalse(info.isEncrypted()); assertFalse(info.isSignedOnly()); assertFalse(info.isPassphraseEncrypted()); @@ -136,7 +139,8 @@ public class MessageInspectorTest { "=jw3E\n" + "-----END PGP MESSAGE-----"; - MessageInspector.EncryptionInfo info = MessageInspector.determineEncryptionInfoForMessage(message); + MessageInspector inspector = new MessageInspector(); + MessageInspector.EncryptionInfo info = inspector.determineEncryptionInfoForMessage(message); assertFalse(info.isEncrypted()); assertFalse(info.isSignedOnly()); assertFalse(info.isPassphraseEncrypted());