From a471a9eda81a96de00a5284d95358530fcc8a39d Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Thu, 9 Feb 2023 09:16:06 +0200 Subject: [PATCH] Streams issue --- pgpainless-core/build.gradle | 3 + .../java/investigations/StreamsIssueTest.java | 213 ++++++++++++++++++ pgpainless-core/src/test/resources/image.png | Bin 0 -> 2936 bytes pgpainless-core/src/test/resources/text.txt | 3 + 4 files changed, 219 insertions(+) create mode 100644 pgpainless-core/src/test/java/investigations/StreamsIssueTest.java create mode 100644 pgpainless-core/src/test/resources/image.png create mode 100644 pgpainless-core/src/test/resources/text.txt diff --git a/pgpainless-core/build.gradle b/pgpainless-core/build.gradle index 3c73121f..b97af4dd 100644 --- a/pgpainless-core/build.gradle +++ b/pgpainless-core/build.gradle @@ -12,6 +12,9 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + //JavaMail + testImplementation "com.sun.mail:jakarta.mail:2.0.1" + // Mocking Components testImplementation "org.mockito:mockito-core:$mockitoVersion" diff --git a/pgpainless-core/src/test/java/investigations/StreamsIssueTest.java b/pgpainless-core/src/test/java/investigations/StreamsIssueTest.java new file mode 100644 index 00000000..e4699133 --- /dev/null +++ b/pgpainless-core/src/test/java/investigations/StreamsIssueTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2023. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. + * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. + * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. + * Vestibulum commodo. Ut rhoncus gravida arcu. + */ + +package investigations; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Properties; +import java.util.UUID; + +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; +import jakarta.mail.BodyPart; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Session; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; +import org.pgpainless.PGPainless; +import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.MissingKeyPassphraseStrategy; +import org.pgpainless.encryption_signing.EncryptionOptions; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class StreamsIssueTest { + private static final String SENDER_PRIVATE_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "\n" + + "lIYEYIq7phYJKwYBBAHaRw8BAQdAat45rrh+gvQwWwJw5eScq3Pdxt/8d+lWNVSm\n" + + "kImXcRP+CQMCvWfx3mzDdd5g6c59LcPqADK0p70/7ZmTkp3ZC1YViTprg4tQt/PF\n" + + "QJL+VPCG+BF9bWyFcfxKe+KAnXRTWml5O6xrv6ZkiNmAxoYyO1shzLQWZGVmYXVs\n" + + "dEBmbG93Y3J5cHQudGVzdIh4BBMWCgAgBQJgirumAhsDBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCHgECGQEACgkQIl+AI8INCVcysgD/cu23M07rImuV5gIl98uOnSIR+QnHUD/M\n" + + "I34b7iY/iTQBALMIsqO1PwYl2qKwmXb5lSoMj5SmnzRRE2RwAFW3AiMCnIsEYIq7\n" + + "phIKKwYBBAGXVQEFAQEHQA8q7iPr+0OXqBGBSAL6WNDjzHuBsG7uiu5w8l/A6v8l\n" + + "AwEIB/4JAwK9Z/HebMN13mCOF6Wy/9oZK4d0DW9cNLuQDeRVZejxT8oFMm7G8iGw\n" + + "CGNjIWWcQSvctBZtHwgcMeplCW7tmzkD3Nq/ty50lCwQQd6gZSXMiHUEGBYKAB0F\n" + + "AmCKu6YCGwwFFgIDAQAECwkIBwUVCgkICwIeAQAKCRAiX4Ajwg0JV+sbAQCv4LVM\n" + + "0+AN54ivWa4vPRyYOfSQ1FqsipkYLJce+xwUeAD+LZpEVCypFtGWQVdeSJVxIHx3\n" + + "k40IfHsK0fGgR+NrRAw=\n" + + "=osuI\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + private static final String SENDER_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "\n" + + "mDMEYIq7phYJKwYBBAHaRw8BAQdAat45rrh+gvQwWwJw5eScq3Pdxt/8d+lWNVSm\n" + + "kImXcRO0FmRlZmF1bHRAZmxvd2NyeXB0LnRlc3SIeAQTFgoAIAUCYIq7pgIbAwUW\n" + + "AgMBAAQLCQgHBRUKCQgLAh4BAhkBAAoJECJfgCPCDQlXMrIA/3LttzNO6yJrleYC\n" + + "JffLjp0iEfkJx1A/zCN+G+4mP4k0AQCzCLKjtT8GJdqisJl2+ZUqDI+Upp80URNk\n" + + "cABVtwIjArg4BGCKu6YSCisGAQQBl1UBBQEBB0APKu4j6/tDl6gRgUgC+ljQ48x7\n" + + "gbBu7orucPJfwOr/JQMBCAeIdQQYFgoAHQUCYIq7pgIbDAUWAgMBAAQLCQgHBRUK\n" + + "CQgLAh4BAAoJECJfgCPCDQlX6xsBAK/gtUzT4A3niK9Zri89HJg59JDUWqyKmRgs\n" + + "lx77HBR4AP4tmkRULKkW0ZZBV15IlXEgfHeTjQh8ewrR8aBH42tEDA==\n" + + "=kdDK\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + private static final String RECEIVER_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "\n" + + "mDMEYIucWBYJKwYBBAHaRw8BAQdAew+8mzMWyf3+Pfy49qa60uKV6e5os7de4TdZ\n" + + "ceAWUq+0F2RlbmJvbmQ3QGZsb3djcnlwdC50ZXN0iHgEExYKACAFAmCLnFgCGwMF\n" + + "FgIDAQAECwkIBwUVCgkICwIeAQIZAQAKCRDDIInNavjWzm3JAQCgFgCEyD58iEa/\n" + + "Rw/DYNoQNoZC1lhw1bxBiOcIbtkdBgEAsDFZu3TBavOMKI7KW+vfMBHtRVbkMNpv\n" + + "unaAldoabgO4OARgi5xYEgorBgEEAZdVAQUBAQdAB1/Mrq5JGYim4KqGTSK4OESQ\n" + + "UwPgK56q0yrkiU9WgyYDAQgHiHUEGBYKAB0FAmCLnFgCGwwFFgIDAQAECwkIBwUV\n" + + "CgkICwIeAQAKCRDDIInNavjWzjMgAQCU+R1fItqdY6lt9jXUqipmXuqVaEFPwNA8\n" + + "YJ1rIwDwVQEAyUc8162KWzA2iQB5akwLwNr/pLDDtOWwhLUkrBb3mAc=\n" + + "=pXF6\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + private static final String PASSPHRASE = "android"; + + @Test + public void successStreamsUsageTest() throws MessagingException, PGPException, IOException { + MimeMessage mimeMessage = prepareMimeMessage("text.txt", "text/plain"); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + assertDoesNotThrow(() -> mimeMessage.writeTo(byteArrayOutputStream)); + String rawMimeMessage = byteArrayOutputStream.toString(); + assertNotNull(rawMimeMessage); + } + + @Test + public void failedStreamsUsageTest() throws MessagingException, PGPException, IOException { + MimeMessage mimeMessage = prepareMimeMessage("image.png", "image/png"); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + assertThrows(Exception.class, () -> mimeMessage.writeTo(byteArrayOutputStream)); + String rawMimeMessage = byteArrayOutputStream.toString(); + assertEquals("", rawMimeMessage); + } + + private MimeMessage prepareMimeMessage(String fileName, String contentType) + throws IOException, PGPException, MessagingException { + String someText = "some text"; + String subject = "some subject"; + String sender = "default@flowcrypt.test"; + String receiver = "denbond7@flowcrypt.test"; + + MimeMessage mimeMessage = new MimeMessage(Session.getDefaultInstance(new Properties())); + mimeMessage.setFrom(sender); + mimeMessage.setRecipients(Message.RecipientType.TO, receiver); + mimeMessage.setSubject(subject); + + Multipart multipart = new MimeMultipart(); + multipart.addBodyPart(prepareTextPart(someText)); + multipart.addBodyPart(prepareAttachmentBodyPart(fileName, contentType)); + mimeMessage.setContent(multipart); + return mimeMessage; + } + + private MimeBodyPart prepareAttachmentBodyPart( + String fileName, + String contentType) + throws IOException, MessagingException, PGPException { + //prepare keys and SecretKeyRingProtector + SecretKeyRingProtector secretKeyRingProtector = + SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(PASSPHRASE)); + PGPPublicKeyRingCollection pgpPublicKeyRingCollection = PGPainless.readKeyRing().publicKeyRingCollection( + SENDER_PUBLIC_KEY + "\n" + RECEIVER_PUBLIC_KEY); + PGPSecretKeyRingCollection secretKeyRingCollection = + PGPainless.readKeyRing().secretKeyRingCollection(SENDER_PRIVATE_KEY); + + ByteArrayOutputStream outputStreamForEncryptedBytes = new ByteArrayOutputStream(); + //open a file from resources and encrypt it + try (InputStream inputStream = requireResource(fileName)) { + EncryptionOptions encryptionOptions = new EncryptionOptions(); + encryptionOptions.addRecipients(pgpPublicKeyRingCollection); + ProducerOptions producerOptions = ProducerOptions.encrypt(encryptionOptions); + producerOptions.setAsciiArmor(false); + + try (EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() + .onOutputStream(outputStreamForEncryptedBytes) + .withOptions(producerOptions)) { + Streams.pipeAll(inputStream, encryptionStream); + } + } + MimeBodyPart attachmentBodyPart = new MimeBodyPart(); + attachmentBodyPart.setDataHandler(new DataHandler(new DataSource() { + @Override + public InputStream getInputStream() throws IOException { + try { + return PGPainless.decryptAndOrVerify() + .onInputStream(new ByteArrayInputStream(outputStreamForEncryptedBytes.toByteArray())) + .withOptions( + new ConsumerOptions() + .addDecryptionKeys(secretKeyRingCollection, secretKeyRingProtector) + .setMissingKeyPassphraseStrategy(MissingKeyPassphraseStrategy.THROW_EXCEPTION) + ); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + + @Override + public OutputStream getOutputStream() { + return null; + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getName() { + return fileName; + } + })); + attachmentBodyPart.setFileName(fileName); + attachmentBodyPart.setContentID(UUID.randomUUID().toString()); + return attachmentBodyPart; + } + + private BodyPart prepareTextPart(String someText) throws MessagingException { + BodyPart bodyPart = new MimeBodyPart(); + bodyPart.setText(someText); + return bodyPart; + } + + private InputStream requireResource(String resourceName) { + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourceName); + if (inputStream == null) { + throw new TestAbortedException("Cannot read resource " + resourceName + ": InputStream is null."); + } + return inputStream; + } +} diff --git a/pgpainless-core/src/test/resources/image.png b/pgpainless-core/src/test/resources/image.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd8f6f25b8e1ac39c98e3af9434e84e3f695da2 GIT binary patch literal 2936 zcmV-;3y1WHP)3@?wui}Q=f|k8dR;mj zx2@dX7L32b_^H(oaqm7zcUeE+pw*x?g*H1?T2tNkk+cPvl+1Uly zUd{OX(?I}Sba@ffdzzry-3bD(%06H2*_cx(I)HDn?<0=ft^? zFD%dmLvT9fYrm$$RE5yj`-HS&o8OmPsus|I8t{@u$q*y~@fS;d|%*)0LQA#jRKT ziogrvoSVDwu=A}Mj*8t|H|gHn+*rM2E4TiLd2RE92tNS4OCpb=JZwe_n4fM8J%k;M ze?<87$|b&Z;so4zk@tx5`v%{W&(7KGxeFrBaUAU4zDaj;BOrVQ<0(J^RHbrWojNb# z9U>l4hd2)+m>EL}fpT{z#;R0<=gvIKZ=DDMw?4Iy-Q+U}o&BU8lcw&lH|qewM$!Zv@RPu51I`Hmpo5Xh$`9aoa=3E z{)ibOf*pwfdkw#FgNp_~0X&T{qj?cp3rpnli^=f&fvVR7GxBW2cmza2$K|NdECZu` z{fo4L-G&4tkDpt=#upKuQ0GE6H}8tMMSQ#MxX-Ac5LMU@8x;dMPFvl%@C==%DUdwB zew{A}^1U->&r&QbIYc=rq(Mo9G8pYq%PkCrSV454j;4395a@5;iwLhAG*&w%@X_~i z5#gh=t7oK`pC1WH;e@c)wy4Sq1H1>RNkBjthsdEM#89z);|+^tX;Jyer(WiI;|PfG z0Tm6!?2NH^Q z5+X51EXGL-*MR3%FY$-XYJlMd5iAa`fyAVM6Cq*Rc10o4Vjgui^#A}6zb_-Mh(ZK*z&jF*_ zPftk^G|Q6EhGJns_J*wf&ekW_KFjCNeVBhcY#(3%SY%iOY*Hfk6QS$_P-HOMu8kLP zS#A8pRG2ldM+h@#&I97S=jRSj0VX2Am;$-4kx&ixfUSHiFF@TukA?iPW(}9+G4y+n zH=6{F+RFzKaNk;F==Wf{Pw#j8l|vUI3TD`p@Q*_oevV=EXU7iz5J~{bzz|r3xF=y$ zj+K5E0-@{+#y>Fue{c<$2IW~y+dSq(57Ct#8Ut3y+2GNyh$5N7muw$ltS%KTObvJ6^j7z$YIrnYAEG4&XV zi6YJ+>Jt&-vcY`dQ18bG>u#AM>eJiap)3ug_IjD5DMSPWvuO2Azp&%^#b)~en@j;6p9f^pXaf;28P@YL)_mVNr;qb_u}#uxFq5CsKvvk$$H`Z+n|i zR)`3Bdxm0e7N6(bU0Xvi7G_5F9?sVTP_2)=6>>$3nRDme_VqX3dFMLs1zv5`1*Ye} zjOuCLpZb69t=%GqfXlOFv|q}}ifouZF_eUU$xJa~yaA#=q=o6Q;Xeq5&ph3-8tw(LdLuM32uV2dC|vGr4~XFJ8f}C=_S0Ne;oFJ~ zVFH~_Mds@bnSsHW`)sU_4wTiOCtB-3o8dD@zuTu*lHe7j?k>nc8#S7eU?J?S+auWD zp1I7YniBBiM82by+kOJN0ad|!88dVYW|O}gF?`hcAsMI$dEto`>GdMrK2$Lo02Yxw zp&q|SMsEQ`Vfu)@_o5|Qs45kH@`abV*=!*ac|F37c5p6A(pI5mBf&L&I7Uu@I@szG3VD7gsLv^~O6v z&3Mr&p5N`^=UPbZr5FW(s!Cosy#L!kC&grnj8BCju(j1A1|##(&LH!G_?#+wf(bD> zKQdf@1;eaLXK^|R1_v0S3Sd}(i|3x@FPiKGC-;KCZty#KhRqg;Nb*LS(*YLe9nR@U zevWvb@tI*dY%E>w-rF-iGT#R8NWkTg72rq2;S7MQ&SuUuIUAkck02n)+m}*^$f^0a#4O3tk z4eFXlz}gM^_cr^>o|%Kba48%E|Nh6c77KpX;8%gi0GtxM!+8a#g7Y;Oa-i|`al4aJ z45uj-$a9a3)quQl8l)QVUwf~{!R7B#U_>ESPZ%_?k0* icj+?U;847Ux9~rI0WPOBC)F(g0000