diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/TestUtils.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/TestUtils.java index ca525814..4f4c73a9 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/TestUtils.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/TestUtils.java @@ -15,12 +15,20 @@ */ package org.pgpainless.cli; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Random; +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.util.io.Streams; + public class TestUtils { public static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -44,4 +52,36 @@ public class TestUtils { } return sb.toString(); } + + public static void assertSignatureEquals(String sig1, String sig2) throws IOException { + assertSignatureEquals(sig1.getBytes(StandardCharsets.UTF_8), sig2.getBytes(StandardCharsets.UTF_8)); + } + + public static void assertSignatureEquals(byte[] sig1, byte[] sig2) throws IOException { + ByteArrayInputStream sigIn1 = new ByteArrayInputStream(sig1); + ByteArrayInputStream sigIn2 = new ByteArrayInputStream(sig2); + assertSignatureEquals(sigIn1, sigIn2); + } + + public static void assertSignatureEquals(InputStream sig1, InputStream sig2) throws IOException { + ArmoredInputStream armor1; + ArmoredInputStream armor2; + if (sig1 instanceof ArmoredInputStream) { + armor1 = (ArmoredInputStream) sig1; + } else { + armor1 = new ArmoredInputStream(sig1); + } + if (sig2 instanceof ArmoredInputStream) { + armor2 = (ArmoredInputStream) sig2; + } else { + armor2 = new ArmoredInputStream(sig2); + } + + ByteArrayOutputStream bout1 = new ByteArrayOutputStream(); + ByteArrayOutputStream bout2 = new ByteArrayOutputStream(); + Streams.pipeAll(armor1, bout1); + Streams.pipeAll(armor2, bout2); + + assertArrayEquals(bout1.toByteArray(), bout2.toByteArray()); + } } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java new file mode 100644 index 00000000..8114fde4 --- /dev/null +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.cli.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; + +import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pgpainless.cli.PGPainlessCLI; +import org.pgpainless.cli.TestUtils; +import sop.exception.SOPGPException; + +public class DetachInbandSignatureAndMessageTest { + + private PrintStream originalSout; + private static File tempDir; + private static File certFile; + private static File tempSigFile; + private static File existingSigFile; + + private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: BCPG v1.64\n" + + "\n" + + "mFIEXhtfCBMIKoZIzj0DAQcCAwTGSFMBUOSLusXS8hdNHbdK3gN8hS7jd4ky7Czl\n" + + "mSti+oVyRJUwQAFZJ1NMsg1H8flSJP1/9YbHd9FBU4bHKGKPtBE8ZW1pbEBlbWFp\n" + + "bC51c2VyPoh1BBMTCgAdBQJeG18IAhsjBRYCAwEABAsJCAcFFQoJCAsCHgEACgkQ\n" + + "VzbmkxrPNwz8rAD/S/VCQc5NJLArgTDkgrt3Q573HiYfrIQo1uk3dwV15WIBAMiq\n" + + "oDmRMb8jzOBv6FGW4P5WAubPdnAvDD7XmArD+TSeuFYEXhtfCBIIKoZIzj0DAQcC\n" + + "AwTgWDWmHJLQUQ35Qg/rINmUhkUhj1E4O5t6Y2PipbqlGfDufLmIKnX40BoJPS4G\n" + + "HW7U0QXfwSaTXa1BAaNsMUomAwEIB4h1BBgTCgAdBQJeG18IAhsMBRYCAwEABAsJ\n" + + "CAcFFQoJCAsCHgEACgkQVzbmkxrPNwxOcwEA19Fnhw7XwpQoT61Fqg54vroAwTZ3\n" + + "T5A+LOdevAtzNOUA/RWeKfOGk6D+vKYRNpMJyqsHi/vBeKwXoeN0n6HuExVF\n" + + "=a1W7\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + @BeforeAll + public static void createTempDir() throws IOException { + tempDir = TestUtils.createTempDirectory(); + + tempSigFile = new File(tempDir, "sig.out"); + + existingSigFile = new File(tempDir, "sig.existing"); + assertTrue(existingSigFile.getCanonicalFile().createNewFile()); + + certFile = new File(tempDir, "cert.asc"); + assertTrue(certFile.createNewFile()); + try (FileOutputStream out = new FileOutputStream(certFile)) { + ByteArrayInputStream in = new ByteArrayInputStream(CERT.getBytes(StandardCharsets.UTF_8)); + Streams.pipeAll(in, out); + } + } + + @BeforeEach + public void saveSout() { + this.originalSout = System.out; + } + + @AfterEach + public void restoreSout() { + System.setOut(originalSout); + } + + private static final String CLEAR_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n" + + "Hash: SHA512\n" + + "\n" + + "Ah, Juliet, if the measure of thy joy\n" + + "Be heaped like mine, and that thy skill be more\n" + + "To blazon it, then sweeten with thy breath\n" + + "This neighbor air, and let rich music’s tongue\n" + + "Unfold the imagined happiness that both\n" + + "Receive in either by this dear encounter.\n" + + "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" + + "DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" + + "ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" + + "=Z2SO\n" + + "-----END PGP SIGNATURE-----"; + + private static final String CLEAR_SIGNED_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" + + "DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" + + "ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" + + "=Z2SO\n" + + "-----END PGP SIGNATURE-----"; + + private static final String CLEAR_SIGNED_BODY = "Ah, Juliet, if the measure of thy joy\n" + + "Be heaped like mine, and that thy skill be more\n" + + "To blazon it, then sweeten with thy breath\n" + + "This neighbor air, and let rich music’s tongue\n" + + "Unfold the imagined happiness that both\n" + + "Receive in either by this dear encounter."; + + @Test + public void detachInbandSignatureAndMessage() throws IOException { + // Clearsigned In + ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)); + System.setIn(clearSignedIn); + + // Plaintext Out + ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(msgOut)); + + // Detach + PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + tempSigFile.getAbsolutePath()}); + + // Test equality with expected values + assertEquals(CLEAR_SIGNED_BODY, msgOut.toString()); + try (FileInputStream sigIn = new FileInputStream(tempSigFile)) { + ByteArrayOutputStream sigBytes = new ByteArrayOutputStream(); + Streams.pipeAll(sigIn, sigBytes); + TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sigBytes.toString()); + } catch (FileNotFoundException e) { + fail("Signature File must have been written.", e); + } + + // Check if produced signature still checks out + System.setIn(new ByteArrayInputStream(msgOut.toByteArray())); + ByteArrayOutputStream verifyOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(verifyOut)); + PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()}); + + assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString()); + } + + @Test + @ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE) + public void existingSignatureOutCausesException() { + // Clearsigned In + ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)); + System.setIn(clearSignedIn); + + // Plaintext Out + ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(msgOut)); + + // Detach + PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + existingSigFile.getAbsolutePath()}); + } + +}