From 858f8e00f3f38c32be10171bfbe2e04d0d49c364 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 9 Nov 2022 22:02:16 +0100 Subject: [PATCH] Rework CLI tests --- .../pgpainless/cli/commands/ArmorCmdTest.java | 99 +-- .../org/pgpainless/cli/commands/CLITest.java | 166 +++++ .../cli/commands/DearmorCmdTest.java | 89 ++- .../cli/commands/ExtractCertCmdTest.java | 76 ++- .../cli/commands/GenerateCertCmdTest.java | 106 --- .../cli/commands/GenerateKeyCmdTest.java | 96 +++ .../cli/commands/InlineDetachCmdTest.java | 143 ++--- .../RoundTripEncryptDecryptCmdTest.java | 606 +++++++++++++++--- ...oundTripInlineSignInlineVerifyCmdTest.java | 236 +++++++ .../commands/RoundTripSignVerifyCmdTest.java | 360 ++++++++--- .../cli/commands/VersionCmdTest.java | 44 ++ 11 files changed, 1548 insertions(+), 473 deletions(-) create mode 100644 pgpainless-cli/src/test/java/org/pgpainless/cli/commands/CLITest.java delete mode 100644 pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateCertCmdTest.java create mode 100644 pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateKeyCmdTest.java create mode 100644 pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java create mode 100644 pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorCmdTest.java index 711796e6..c1fb810f 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorCmdTest.java @@ -5,87 +5,96 @@ package org.pgpainless.cli.commands; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import com.ginsberg.junit.exit.FailOnSystemExit; -import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.cli.PGPainlessCLI; +import org.slf4j.LoggerFactory; +import sop.exception.SOPGPException; -public class ArmorCmdTest { +public class ArmorCmdTest extends CLITest { - private static PrintStream originalSout; - - @BeforeEach - public void saveSout() { - originalSout = System.out; + public ArmorCmdTest() { + super(LoggerFactory.getLogger(ArmorCmdTest.class)); } - @AfterEach - public void restoreSout() { - System.setOut(originalSout); - } + private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" + + "Comment: alice@pgpainless.org\n" + + "\n" + + "lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" + + "t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" + + "ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" + + "D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" + + "8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" + + "G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" + + "8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" + + "hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" + + "CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" + + "eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" + + "BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" + + "ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" + + "AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" + + "ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" + + "m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" + + "lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" + + "rsH85mUgCw==\n" + + "=EMKf\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; @Test - @FailOnSystemExit - public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org"); - byte[] bytes = secretKey.getEncoded(); + public void armorSecretKey() throws IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key); + byte[] binary = secretKeys.getEncoded(); - System.setIn(new ByteArrayInputStream(bytes)); - ByteArrayOutputStream armorOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(armorOut)); - PGPainlessCLI.execute("armor"); + pipeBytesToStdin(binary); + ByteArrayOutputStream armorOut = pipeStdoutToStream(); + assertSuccess(executeCommand("armor")); PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString()); - assertArrayEquals(secretKey.getEncoded(), armored.getEncoded()); + assertArrayEquals(secretKeys.getEncoded(), armored.getEncoded()); } @Test - @FailOnSystemExit - public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org"); + public void armorPublicKey() throws IOException { + PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key); PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey); byte[] bytes = publicKey.getEncoded(); - System.setIn(new ByteArrayInputStream(bytes)); - ByteArrayOutputStream armorOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(armorOut)); - PGPainlessCLI.execute("armor"); + pipeBytesToStdin(bytes); + ByteArrayOutputStream armorOut = pipeStdoutToStream(); + assertSuccess(executeCommand("armor")); PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString()); assertArrayEquals(publicKey.getEncoded(), armored.getEncoded()); } @Test - @FailOnSystemExit - public void armorMessage() { + public void armorMessage() throws IOException { String message = "Hello, World!\n"; - System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8))); - ByteArrayOutputStream armorOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(armorOut)); - PGPainlessCLI.execute("armor"); + pipeStringToStdin(message); + ByteArrayOutputStream armorOut = pipeStdoutToStream(); + assertSuccess(executeCommand("armor")); String armored = armorOut.toString(); - assertTrue(armored.startsWith("-----BEGIN PGP MESSAGE-----\n")); assertTrue(armored.contains("SGVsbG8sIFdvcmxkIQo=")); } + @Test + public void labelNotYetSupported() throws IOException { + pipeStringToStdin("Hello, World!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("armor", "--label", "Message"); + assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/CLITest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/CLITest.java new file mode 100644 index 00000000..d5d076cc --- /dev/null +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/CLITest.java @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.cli.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import javax.annotation.Nonnull; + +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.opentest4j.TestAbortedException; +import org.pgpainless.cli.TestUtils; +import org.pgpainless.sop.SOPImpl; +import org.slf4j.Logger; +import sop.cli.picocli.SopCLI; + +public abstract class CLITest { + + protected File testDirectory; + protected InputStream stdin; + protected PrintStream stdout; + + protected final Logger LOGGER; + + + public CLITest(@Nonnull Logger logger) { + LOGGER = logger; + SopCLI.setSopInstance(new SOPImpl()); + } + + @BeforeEach + public void setup() throws IOException { + testDirectory = TestUtils.createTempDirectory(); + testDirectory.deleteOnExit(); + LOGGER.debug(testDirectory.getAbsolutePath()); + stdin = System.in; + stdout = System.out; + } + + @AfterEach + public void cleanup() throws IOException { + resetStreams(); + } + + public File nonExistentFile(String name) { + File file = new File(testDirectory, name); + if (file.exists()) { + throw new TestAbortedException("File " + file.getAbsolutePath() + " already exists."); + } + return file; + } + + public File pipeStdoutToFile(String name) throws IOException { + File file = new File(testDirectory, name); + file.deleteOnExit(); + if (!file.createNewFile()) { + throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath()); + } + System.setOut(new PrintStream(Files.newOutputStream(file.toPath()))); + return file; + } + + public ByteArrayOutputStream pipeStdoutToStream() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + pipeStdoutToStream(out); + return out; + } + + public void pipeStdoutToStream(OutputStream stream) { + System.setOut(new PrintStream(stream)); + } + + public void pipeFileToStdin(File file) throws IOException { + System.setIn(Files.newInputStream(file.toPath())); + } + + public void pipeBytesToStdin(byte[] bytes) { + System.setIn(new ByteArrayInputStream(bytes)); + } + + public void pipeStringToStdin(String string) { + System.setIn(new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8))); + } + + public void resetStdout() { + if (System.out != stdout) { + System.out.flush(); + System.out.close(); + } + System.setOut(stdout); + } + + public void resetStdin() throws IOException { + if (System.in != stdin) { + System.in.close(); + } + System.setIn(stdin); + } + + public void resetStreams() throws IOException { + resetStdout(); + resetStdin(); + } + + public File writeFile(String name, String data) throws IOException { + return writeFile(name, data.getBytes(StandardCharsets.UTF_8)); + } + + public File writeFile(String name, byte[] bytes) throws IOException { + return writeFile(name, new ByteArrayInputStream(bytes)); + } + + public File writeFile(String name, InputStream data) throws IOException { + File file = new File(testDirectory, name); + if (!file.createNewFile()) { + throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath()); + } + file.deleteOnExit(); + try (FileOutputStream fileOut = new FileOutputStream(file)) { + Streams.pipeAll(data, fileOut); + fileOut.flush(); + } + return file; + } + + public byte[] readBytesFromFile(File file) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (FileInputStream fileIn = new FileInputStream(file)) { + Streams.pipeAll(fileIn, buffer); + } catch (FileNotFoundException e) { + throw new TestAbortedException("File " + file.getAbsolutePath() + " does not exist!", e); + } catch (IOException e) { + throw new TestAbortedException("Cannot read from file " + file.getAbsolutePath(), e); + } + return buffer.toByteArray(); + } + + public String readStringFromFile(File file) { + return new String(readBytesFromFile(file), StandardCharsets.UTF_8); + } + + public int executeCommand(String... command) throws IOException { + int exitCode = SopCLI.execute(command); + resetStreams(); + return exitCode; + } + + public void assertSuccess(int exitCode) { + assertEquals(0, exitCode, "Expected successful program execution"); + } +} diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorCmdTest.java index a49f4db1..4ebaf21d 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorCmdTest.java @@ -7,73 +7,73 @@ package org.pgpainless.cli.commands; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import com.ginsberg.junit.exit.FailOnSystemExit; -import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.cli.PGPainlessCLI; +import org.slf4j.LoggerFactory; -public class DearmorCmdTest { +public class DearmorCmdTest extends CLITest { - private PrintStream originalSout; - - @BeforeEach - public void saveSout() { - this.originalSout = System.out; + public DearmorCmdTest() { + super(LoggerFactory.getLogger(DearmorCmdTest.class)); } - @AfterEach - public void restoreSout() { - System.setOut(originalSout); - } + private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" + + "Comment: alice@pgpainless.org\n" + + "\n" + + "lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" + + "t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" + + "ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" + + "D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" + + "8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" + + "G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" + + "8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" + + "hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" + + "CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" + + "eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" + + "BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" + + "ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" + + "AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" + + "ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" + + "m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" + + "lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" + + "rsH85mUgCw==\n" + + "=EMKf\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; @Test - @FailOnSystemExit - public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org"); - String armored = PGPainless.asciiArmor(secretKey); + public void dearmorSecretKey() throws IOException { + PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key); - System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8))); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("dearmor"); + pipeStringToStdin(key); + ByteArrayOutputStream dearmored = pipeStdoutToStream(); + assertSuccess(executeCommand("dearmor")); - assertArrayEquals(secretKey.getEncoded(), out.toByteArray()); + assertArrayEquals(secretKey.getEncoded(), dearmored.toByteArray()); } @Test - @FailOnSystemExit - public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org"); + public void dearmorCertificate() throws IOException { + PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key); PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey); - String armored = PGPainless.asciiArmor(certificate); + String armoredCert = PGPainless.asciiArmor(certificate); - System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8))); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("dearmor"); + pipeStringToStdin(armoredCert); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("dearmor")); assertArrayEquals(certificate.getEncoded(), out.toByteArray()); } @Test - @FailOnSystemExit - public void dearmorMessage() { + public void dearmorMessage() throws IOException { String armored = "-----BEGIN PGP MESSAGE-----\n" + "Version: BCPG v1.69\n" + "\n" + @@ -81,10 +81,9 @@ public class DearmorCmdTest { "=fkLo\n" + "-----END PGP MESSAGE-----"; - System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8))); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("dearmor"); + pipeStringToStdin(armored); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("dearmor")); assertEquals("Hello, World\n", out.toString()); } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ExtractCertCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ExtractCertCmdTest.java index 1d20fb0e..3eebcb47 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ExtractCertCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ExtractCertCmdTest.java @@ -4,42 +4,96 @@ package org.pgpainless.cli.commands; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; -import java.io.PrintStream; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; -import com.ginsberg.junit.exit.FailOnSystemExit; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.cli.PGPainlessCLI; import org.pgpainless.key.info.KeyRingInfo; +import org.slf4j.LoggerFactory; +import sop.exception.SOPGPException; -public class ExtractCertCmdTest { +public class ExtractCertCmdTest extends CLITest { + + public ExtractCertCmdTest() { + super(LoggerFactory.getLogger(ExtractCertCmdTest.class)); + } @Test - @FailOnSystemExit public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing() .simpleEcKeyRing("Juliet Capulet "); - ByteArrayInputStream inputStream = new ByteArrayInputStream(secretKeys.getEncoded()); - System.setIn(inputStream); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); + pipeBytesToStdin(secretKeys.getEncoded()); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("extract-cert", "--armor")); + + assertTrue(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); - PGPainlessCLI.execute("extract-cert"); PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray()); KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys); assertFalse(info.isSecretKey()); assertTrue(info.isUserIdValid("Juliet Capulet ")); } + + @Test + public void testExtractCertFromCertFails() throws IOException { + // Generate key + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", "Alice ")); + + // extract cert from key (success) + pipeFileToStdin(keyFile); + File certFile = pipeStdoutToFile("cert.asc"); + assertSuccess(executeCommand("extract-cert")); + + // extract cert from cert (fail) + pipeFileToStdin(certFile); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("extract-cert"); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void extractCertFromGarbageFails() throws IOException { + pipeStringToStdin("This is a bunch of garbage!"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("extract-cert"); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testExtractCertUnarmored() throws IOException { + // Generate key + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", "Alice ")); + + // extract cert from key (success) + pipeFileToStdin(keyFile); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("extract-cert", "--no-armor")); + + assertFalse(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + + pipeBytesToStdin(out.toByteArray()); + ByteArrayOutputStream armored = pipeStdoutToStream(); + assertSuccess(executeCommand("armor")); + + assertTrue(armored.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + } + } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateCertCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateCertCmdTest.java deleted file mode 100644 index 63afc39f..00000000 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateCertCmdTest.java +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.cli.commands; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.pgpainless.cli.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -import com.ginsberg.junit.exit.FailOnSystemExit; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.junit.jupiter.api.Test; -import org.pgpainless.PGPainless; -import org.pgpainless.cli.PGPainlessCLI; -import org.pgpainless.cli.TestUtils; -import org.pgpainless.key.info.KeyInfo; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.util.Passphrase; - -public class GenerateCertCmdTest { - - @Test - @FailOnSystemExit - public void testKeyGeneration() throws IOException, PGPException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("generate-key", "--armor", "Juliet Capulet "); - - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray()); - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - assertTrue(info.isFullyDecrypted()); - assertTrue(info.isUserIdValid("Juliet Capulet ")); - - for (PGPSecretKey key : secretKeys) { - assertTrue(testPassphrase(key, null)); - } - - byte[] outBegin = new byte[37]; - System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37); - assertArrayEquals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES); - } - - @Test - @FailOnSystemExit - public void testGenerateKeyWithPassword() throws IOException, PGPException { - PrintStream orig = System.out; - try { - // Write password to file - File tempDir = TestUtils.createTempDirectory(); - File passwordFile = TestUtils.writeTempFile(tempDir, "sw0rdf1sh".getBytes(StandardCharsets.UTF_8)); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("generate-key", "Juliet Capulet ", - "--with-key-password", passwordFile.getAbsolutePath()); - - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray()); - KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - assertFalse(info.isFullyDecrypted()); - assertTrue(info.isFullyEncrypted()); - - for (PGPSecretKey key : secretKeys) { - assertTrue(testPassphrase(key, "sw0rdf1sh")); - } - } finally { - System.setOut(orig); - } - } - - private boolean testPassphrase(PGPSecretKey key, String passphrase) throws PGPException { - if (KeyInfo.isEncrypted(key)) { - UnlockSecretKey.unlockSecretKey(key, Passphrase.fromPassword(passphrase)); - } else { - if (passphrase != null) { - return false; - } - UnlockSecretKey.unlockSecretKey(key, (PBESecretKeyDecryptor) null); - } - return true; - } - - @Test - @FailOnSystemExit - public void testNoArmor() { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - System.setOut(new PrintStream(out)); - PGPainlessCLI.execute("generate-key", "--no-armor", "Test "); - - byte[] outBegin = new byte[37]; - System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37); - assertFalse(Arrays.equals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES)); - } -} diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateKeyCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateKeyCmdTest.java new file mode 100644 index 00000000..2e4e6e7f --- /dev/null +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/GenerateKeyCmdTest.java @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.cli.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.util.encoders.Hex; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.key.protection.UnlockSecretKey; +import org.pgpainless.util.Passphrase; +import org.slf4j.LoggerFactory; +import sop.exception.SOPGPException; + +public class GenerateKeyCmdTest extends CLITest { + + public GenerateKeyCmdTest() { + super(LoggerFactory.getLogger(GenerateKeyCmdTest.class)); + } + + @Test + public void testGenerateKey() throws IOException { + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", "Alice ")); + + String key = readStringFromFile(keyFile); + assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key); + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isFullyDecrypted()); + assertEquals(Collections.singletonList("Alice "), info.getUserIds()); + } + + @Test + public void testGenerateBinaryKey() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("generate-key", "--no-armor", + "Alice ")); + + byte[] key = out.toByteArray(); + String firstHexOctet = Hex.toHexString(key, 0, 1); + assertTrue(firstHexOctet.equals("c5") || firstHexOctet.equals("94")); + } + + @Test + public void testGenerateKeyWithMultipleUserIds() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("generate-key", + "Alice ", "Alice ")); + + String key = out.toString(); + assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key); + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isFullyDecrypted()); + assertEquals(Arrays.asList("Alice ", "Alice "), info.getUserIds()); + } + + @Test + public void testPasswordProtectedKey() throws IOException, PGPException { + File passwordFile = writeFile("password", "sw0rdf1sh"); + passwordFile.deleteOnExit(); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("generate-key", + "--with-key-password", passwordFile.getAbsolutePath(), "Alice ")); + + String key = out.toString(); + assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key); + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isFullyEncrypted()); + + assertNotNull(UnlockSecretKey.unlockSecretKey(secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh"))); + } + + @Test + public void testGeneratePasswordProtectedKey_missingPasswordFile() throws IOException { + int exit = executeCommand("generate-key", + "--with-key-password", "nonexistent", "Alice "); + + assertEquals(SOPGPException.MissingInput.EXIT_CODE, exit, "Expected MISSING_INPUT (" + SOPGPException.MissingInput.EXIT_CODE + ")"); + } +} diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java index aed4c581..eabdd6ad 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java @@ -6,33 +6,18 @@ 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 org.slf4j.LoggerFactory; import sop.exception.SOPGPException; -public class InlineDetachCmdTest { - - private PrintStream originalSout; - private static File tempDir; - private static File certFile; +public class InlineDetachCmdTest extends CLITest { private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "Version: BCPG v1.64\n" + @@ -49,28 +34,6 @@ public class InlineDetachCmdTest { "=a1W7\n" + "-----END PGP PUBLIC KEY BLOCK-----"; - @BeforeAll - public static void createTempDir() throws IOException { - tempDir = TestUtils.createTempDirectory(); - - 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" + @@ -103,91 +66,67 @@ public class InlineDetachCmdTest { "Unfold the imagined happiness that both\n" + "Receive in either by this dear encounter."; + public InlineDetachCmdTest() { + super(LoggerFactory.getLogger(InlineDetachCmdTest.class)); + } + @Test public void detachInbandSignatureAndMessage() throws IOException { - // Clearsigned In - ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)); - System.setIn(clearSignedIn); + pipeStringToStdin(CLEAR_SIGNED_MESSAGE); + ByteArrayOutputStream msgOut = pipeStdoutToStream(); + File sigFile = nonExistentFile("sig.out"); - // Plaintext Out - ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(msgOut)); - - // Detach - File tempSigFile = new File(tempDir, "sig.out"); - PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath()}); + assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath())); + assertTrue(sigFile.exists(), "Signature file must have been written."); // 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); - String sig = sigBytes.toString(); - TestUtils.assertSignatureIsArmored(sigBytes.toByteArray()); - TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig); - } catch (FileNotFoundException e) { - fail("Signature File must have been written.", e); - } + String sig = readStringFromFile(sigFile); + TestUtils.assertSignatureIsArmored(sig.getBytes()); + TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig); // 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()); + File certFile = writeFile("cert.asc", CERT); + pipeStringToStdin(msgOut.toString()); + ByteArrayOutputStream verifyOut = pipeStdoutToStream(); + assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath())); + assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", + verifyOut.toString()); } @Test public void detachInbandSignatureAndMessageNoArmor() throws IOException { - // Clearsigned In - ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)); - System.setIn(clearSignedIn); + pipeStringToStdin(CLEAR_SIGNED_MESSAGE); + ByteArrayOutputStream msgOut = pipeStdoutToStream(); + File sigFile = nonExistentFile("sig.out"); - // Plaintext Out - ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(msgOut)); - - // Detach - File tempSigFile = new File(tempDir, "sig.asc"); - PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath(), "--no-armor"}); + assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath(), "--no-armor")); // 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); - byte[] sig = sigBytes.toByteArray(); - TestUtils.assertSignatureIsNotArmored(sig); - TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig); - } catch (FileNotFoundException e) { - fail("Signature File must have been written.", e); - } + assertTrue(sigFile.exists(), "Signature file must have been written."); + byte[] sig = readBytesFromFile(sigFile); + + TestUtils.assertSignatureIsNotArmored(sig); + TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig); // 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()); + pipeBytesToStdin(msgOut.toByteArray()); + ByteArrayOutputStream verifyOut = pipeStdoutToStream(); + File certFile = writeFile("cert.asc", CERT); + assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath())); + assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", + verifyOut.toString()); } @Test - @ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE) public void existingSignatureOutCausesException() 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 - File existingSigFile = new File(tempDir, "sig.existing"); - assertTrue(existingSigFile.createNewFile()); - PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + existingSigFile.getAbsolutePath()}); + pipeStringToStdin(CLEAR_SIGNED_MESSAGE); + ByteArrayOutputStream msgOut = pipeStdoutToStream(); + File existingSigFile = writeFile("sig.asc", CLEAR_SIGNED_SIGNATURE); + int exit = executeCommand("inline-detach", "--signatures-out", existingSigFile.getAbsolutePath()); + assertEquals(SOPGPException.OutputExists.EXIT_CODE, exit); + assertEquals(0, msgOut.size()); } } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java index dba9f47b..5d183ea6 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java @@ -5,111 +5,557 @@ package org.pgpainless.cli.commands; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; import com.ginsberg.junit.exit.FailOnSystemExit; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; -import org.pgpainless.cli.PGPainlessCLI; -import org.pgpainless.cli.TestUtils; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.generation.type.eddsa.EdDSACurve; +import org.pgpainless.key.generation.type.xdh.XDHSpec; +import org.slf4j.LoggerFactory; +import sop.exception.SOPGPException; -public class RoundTripEncryptDecryptCmdTest { +public class RoundTripEncryptDecryptCmdTest extends CLITest { - private static File tempDir; - private static PrintStream originalSout; - - @BeforeAll - public static void prepare() throws IOException { - tempDir = TestUtils.createTempDirectory(); + public RoundTripEncryptDecryptCmdTest() { + super(LoggerFactory.getLogger(RoundTripEncryptDecryptCmdTest.class)); } + private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" + + "Comment: Alice \n" + + "\n" + + "lFgEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" + + "d1M7WwsAAP0R1ELnfdJhXcfjaYPLHzwy1i34FxP5g3tvdgg9Q7VmchActBxBbGlj\n" + + "ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmNr3kYJECwNPBI89RwI\n" + + "FiEEouwHf8l34V3Xme/5LA08Ejz1HAgCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" + + "AQAAe6YA/2sO483Vi2Fgs4ejv8FykyO96IVrMoYhw3Od4LyWEyDfAQDi15LxJJm6\n" + + "T2sXdENVigdwDJiELxjOtbmivuJutxkWCJxdBGNr3kYSCisGAQQBl1UBBQEBB0CS\n" + + "zXjySHqlicxG3QlrVeTIqwKitL1sWsx0MCDmT1C8dAMBCAcAAP9VNkfMQvYAlYSP\n" + + "aYEkwEOc8/XpiloVKtPzxwVCPlXFeBDCiHUEGBYKAB0FAmNr3kYCngECmwwFFgID\n" + + "AQAECwkIBwUVCgkICwAKCRAsDTwSPPUcCOT4AQDZcN5a/e8Qr+LNBIyXXLgJWGsL\n" + + "59nsKHBbDURnxbEnMQEAybS8u+Rsb82yW4CfaA4CLRTC3eDc5Y4QwYWzLogWNwic\n" + + "WARja95GFgkrBgEEAdpHDwEBB0DcdwQufWLq6ASku4JWBBd9JplRVhK0cXWuTE73\n" + + "uWltuwABAI0bVQXvgDnxTs6kUO7JIWtokM5lI/1bfG4L1YOfnXIgD7CI1QQYFgoA\n" + + "fQUCY2veRgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ\n" + + "7NC/hj9lyaWVAwEA3ze1LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGz\n" + + "ZMFF/iQ/rOcSAsHPp4ggezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxG\n" + + "u/s4maOFozcO4JoCZTsLHGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9\n" + + "aesZ2IHgpwA7jlyHSgwLDw==\n" + + "=H3HU\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" + + "Comment: Alice \n" + + "\n" + + "mDMEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" + + "d1M7Wwu0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2ve\n" + + "RgkQLA08Ejz1HAgWIQSi7Ad/yXfhXdeZ7/ksDTwSPPUcCAKeAQKbAQUWAgMBAAQL\n" + + "CQgHBRUKCQgLApkBAAB7pgD/aw7jzdWLYWCzh6O/wXKTI73ohWsyhiHDc53gvJYT\n" + + "IN8BAOLXkvEkmbpPaxd0Q1WKB3AMmIQvGM61uaK+4m63GRYIuDgEY2veRhIKKwYB\n" + + "BAGXVQEFAQEHQJLNePJIeqWJzEbdCWtV5MirAqK0vWxazHQwIOZPULx0AwEIB4h1\n" + + "BBgWCgAdBQJja95GAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQLA08Ejz1HAjk\n" + + "+AEA2XDeWv3vEK/izQSMl1y4CVhrC+fZ7ChwWw1EZ8WxJzEBAMm0vLvkbG/NsluA\n" + + "n2gOAi0Uwt3g3OWOEMGFsy6IFjcIuDMEY2veRhYJKwYBBAHaRw8BAQdA3HcELn1i\n" + + "6ugEpLuCVgQXfSaZUVYStHF1rkxO97lpbbuI1QQYFgoAfQUCY2veRgKeAQKbAgUW\n" + + "AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ7NC/hj9lyaWVAwEA3ze1\n" + + "LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGzZMFF/iQ/rOcSAsHPp4gg\n" + + "ezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxGu/s4maOFozcO4JoCZTsL\n" + + "HGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9aesZ2IHgpwA7jlyHSgwL\n" + + "Dw==\n" + + "=c1PZ\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + @Test @FailOnSystemExit public void encryptAndDecryptAMessage() throws IOException { - originalSout = System.out; - File julietKeyFile = new File(tempDir, "juliet.key"); - assertTrue(julietKeyFile.createNewFile()); + // Juliets key and cert + File julietKeyFile = pipeStdoutToFile("juliet.key"); + assertSuccess(executeCommand("generate-key", "Juliet ")); - File julietCertFile = new File(tempDir, "juliet.asc"); - assertTrue(julietCertFile.createNewFile()); + pipeFileToStdin(julietKeyFile); + File julietCertFile = pipeStdoutToFile("juliet.cert"); + assertSuccess(executeCommand("extract-cert")); - File romeoKeyFile = new File(tempDir, "romeo.key"); - assertTrue(romeoKeyFile.createNewFile()); + // Romeos key and cert + File romeoKeyFile = pipeStdoutToFile("romeo.key"); + assertSuccess(executeCommand("generate-key", "Romeo ")); - File romeoCertFile = new File(tempDir, "romeo.asc"); - assertTrue(romeoCertFile.createNewFile()); - - File msgAscFile = new File(tempDir, "msg.asc"); - assertTrue(msgAscFile.createNewFile()); - - OutputStream julietKeyOut = new FileOutputStream(julietKeyFile); - System.setOut(new PrintStream(julietKeyOut)); - PGPainlessCLI.execute("generate-key", "Juliet Capulet "); - julietKeyOut.close(); - - FileInputStream julietKeyIn = new FileInputStream(julietKeyFile); - System.setIn(julietKeyIn); - OutputStream julietCertOut = new FileOutputStream(julietCertFile); - System.setOut(new PrintStream(julietCertOut)); - PGPainlessCLI.execute("extract-cert"); - julietKeyIn.close(); - julietCertOut.close(); - - OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile); - System.setOut(new PrintStream(romeoKeyOut)); - PGPainlessCLI.execute("generate-key", "Romeo Montague "); - romeoKeyOut.close(); - - FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile); - System.setIn(romeoKeyIn); - OutputStream romeoCertOut = new FileOutputStream(romeoCertFile); - System.setOut(new PrintStream(romeoCertOut)); - PGPainlessCLI.execute("extract-cert"); - romeoKeyIn.close(); - romeoCertOut.close(); + File romeoCertFile = pipeStdoutToFile("romeo.cert"); + pipeFileToStdin(romeoKeyFile); + assertSuccess(executeCommand("extract-cert")); + // Romeo encrypts signs and encrypts for Juliet and himself String msg = "Hello World!\n"; - ByteArrayInputStream msgIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)); - System.setIn(msgIn); - OutputStream msgAscOut = new FileOutputStream(msgAscFile); - System.setOut(new PrintStream(msgAscOut)); - PGPainlessCLI.execute("encrypt", - "--sign-with", romeoKeyFile.getAbsolutePath(), - julietCertFile.getAbsolutePath()); - msgAscOut.close(); + File encryptedMessageFile = pipeStdoutToFile("msg.asc"); + pipeStringToStdin(msg); + assertSuccess(executeCommand("encrypt", "--sign-with", romeoKeyFile.getAbsolutePath(), + julietCertFile.getAbsolutePath(), romeoCertFile.getAbsolutePath())); - File verifyFile = new File(tempDir, "verify.txt"); - - FileInputStream msgAscIn = new FileInputStream(msgAscFile); - System.setIn(msgAscIn); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PrintStream pOut = new PrintStream(out); - System.setOut(pOut); - PGPainlessCLI.execute("decrypt", - "--verify-out", verifyFile.getAbsolutePath(), + // Juliet can decrypt and verify with Romeos cert + pipeFileToStdin(encryptedMessageFile); + File verificationsFile = nonExistentFile("verifications"); + ByteArrayOutputStream decrypted = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", romeoCertFile.getAbsolutePath(), - julietKeyFile.getAbsolutePath()); - msgAscIn.close(); + julietKeyFile.getAbsolutePath())); + assertEquals(msg, decrypted.toString()); + + // Romeo can decrypt and verify too + pipeFileToStdin(encryptedMessageFile); + File anotherVerificationsFile = nonExistentFile("anotherVerifications"); + decrypted = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--verifications-out", anotherVerificationsFile.getAbsolutePath(), + "--verify-with", romeoCertFile.getAbsolutePath(), + romeoKeyFile.getAbsolutePath())); + assertEquals(msg, decrypted.toString()); + + String julietsVerif = readStringFromFile(verificationsFile); + String romeosVerif = readStringFromFile(anotherVerificationsFile); + assertEquals(julietsVerif, romeosVerif); + assertFalse(julietsVerif.isEmpty()); + assertEquals(103, julietsVerif.length()); // 103 is number of symbols in [DATE, FINGER, FINGER] for V4 + } + + @Test + public void testMissingArgumentsIfNoArgsSupplied() throws IOException { + int exit = executeCommand("encrypt"); + assertEquals(SOPGPException.MissingArg.EXIT_CODE, exit); + } + + @Test + public void testEncryptingForKeyFails() throws IOException { + File notACert = writeFile("key.asc", KEY); + + pipeStringToStdin("Hello, World!"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", notACert.getAbsolutePath()); + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testEncrypt_SignWithCertFails() throws IOException { + File cert = writeFile("cert.asc", CERT); + // noinspection UnnecessaryLocalVariable + File notAKey = cert; + + pipeStringToStdin("Hello, World!"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", "--sign-with", notAKey.getAbsolutePath(), cert.getAbsolutePath()); + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testDecryptVerifyOut_withoutVerifyWithFails() throws IOException { + File key = writeFile("key.asc", KEY); + + File verifications = nonExistentFile("verifications"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", "--verifications-out", verifications.getAbsolutePath(), key.getAbsolutePath()); + + assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testVerificationsOutAlreadyExistFails() throws IOException { + File key = writeFile("key.asc", KEY); + File cert = writeFile("cert.asc", CERT); + + File verifications = writeFile("verifications", "this file is not empty"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", "--verify-with", cert.getAbsolutePath(), + "--verifications-out", verifications.getAbsolutePath(), + key.getAbsolutePath()); + + assertEquals(SOPGPException.OutputExists.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testSessionKeyOutWritesSessionKeyOut() throws IOException { + File key = writeFile("key.asc", KEY); + File sessionKeyFile = nonExistentFile("session.key"); + + String plaintext = "Hello, World!\n"; + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n"; + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream plaintextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), key.getAbsolutePath())); + + assertEquals(plaintext, plaintextOut.toString()); + String resultSessionKey = readStringFromFile(sessionKeyFile); + assertEquals(sessionKey, resultSessionKey); + } + + @Test + public void decryptMessageWithSessionKey() throws IOException { + String plaintext = "Hello, World!\n"; + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n"; + + File sessionKeyFile = writeFile("session.key", sessionKey); + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream plaintextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath())); + + assertEquals(plaintext, plaintextOut.toString()); + } + + @Test + public void testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications() throws IOException { + String plaintext = "Hello, World!\n"; + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n"; + + File cert = writeFile("cert.asc", CERT); + File sessionKeyFile = writeFile("session.key", sessionKey); + File verifications = nonExistentFile("verifications"); + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath(), + "--verify-with", cert.getAbsolutePath(), "--verifications-out", verifications.getAbsolutePath())); + + assertEquals(plaintext, out.toString()); + String verificationString = readStringFromFile(verifications); + assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08\n", + verificationString); + } + + @Test + public void encryptAndDecryptMessageWithPassphrase() throws IOException { + File passwordFile = writeFile("password", "c1tiz€n4"); + String message = "I cannot think of meaningful messages for test vectors rn"; + + pipeStringToStdin(message); + ByteArrayOutputStream ciphertext = pipeStdoutToStream(); + assertSuccess(executeCommand("encrypt", "--with-password", passwordFile.getAbsolutePath())); + + String ciphertextString = ciphertext.toString(); + assertTrue(ciphertextString.startsWith("-----BEGIN PGP MESSAGE-----\n")); + + pipeBytesToStdin(ciphertext.toByteArray()); + ByteArrayOutputStream plaintext = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--with-password", passwordFile.getAbsolutePath())); + + assertEquals(message, plaintext.toString()); + } + + @Test + public void testEncryptWithIncapableCert() throws PGPException, + InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing() + .addUserId("No Crypt ") + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) + .build(); + PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys); + File certFile = writeFile("cert.pgp", cert.getEncoded()); + + pipeStringToStdin("Hello, World!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", certFile.getAbsolutePath()); + + assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testSignWithIncapableKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing() + .addUserId("Cannot Sign ") + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) + .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + .build(); + File keyFile = writeFile("key.pgp", secretKeys.getEncoded()); + File certFile = writeFile("cert.pgp", PGPainless.extractCertificate(secretKeys).getEncoded()); + + pipeStringToStdin("Hello, World!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(), + certFile.getAbsolutePath()); + + assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testEncryptDecryptRoundTripWithPasswordProtectedKey() throws IOException { + // generate password protected key + File passwordFile = writeFile("password", "fooBarBaz420"); + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", + "--with-key-password", passwordFile.getAbsolutePath(), + "Pascal Password ")); + + // extract cert + File certFile = pipeStdoutToFile("cert.asc"); + pipeFileToStdin(keyFile); + assertSuccess(executeCommand("extract-cert")); + + // encrypt and sign message + String msg = "Hello, World!\n"; + pipeStringToStdin(msg); + File encryptedFile = pipeStdoutToFile("msg.asc"); + assertSuccess(executeCommand("encrypt", + "--sign-with", keyFile.getAbsolutePath(), + "--with-key-password", passwordFile.getAbsolutePath(), + "--no-armor", + "--as", "binary", + certFile.getAbsolutePath())); + + // Decrypt + File verificationsFile = nonExistentFile("verifications"); + pipeFileToStdin(encryptedFile); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", + "--verify-with", certFile.getAbsolutePath(), + "--verifications-out", verificationsFile.getAbsolutePath(), + "--with-key-password", passwordFile.getAbsolutePath(), + keyFile.getAbsolutePath())); assertEquals(msg, out.toString()); } - @AfterAll - public static void after() { - System.setOut(originalSout); - // CHECKSTYLE:OFF - System.out.println(tempDir.getAbsolutePath()); - // CHECKSTYLE:ON + @Test + public void decryptGarbageFails() throws IOException { + File keyFile = writeFile("key.asc", KEY); + pipeStringToStdin("Some Garbage!"); + int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath()); + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + } + + @Test + public void decryptMessageWithWrongKeyFails() throws IOException { + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", "Bob ")); + // message was *not* created with key above + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath()); + assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void encryptWithPasswordADecryptWithPasswordBFails() throws IOException { + File password1 = writeFile("password1", "swordfish"); + File password2 = writeFile("password2", "orange"); + + pipeStringToStdin("Bonjour, le monde!\n"); + ByteArrayOutputStream ciphertextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("encrypt", "--with-password", password1.getAbsolutePath())); + + pipeBytesToStdin(ciphertextOut.toByteArray()); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", "--with-password", password2.getAbsolutePath()); + assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void encryptWithGarbageCertFails() throws IOException { + File garbageCert = writeFile("cert.asc", "This is garbage!"); + + pipeStringToStdin("Hallo, Welt!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", garbageCert.getAbsolutePath()); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void encrypt_signWithGarbageKeyFails() throws IOException { + File cert = writeFile("cert.asc", CERT); + File garbageKey = writeFile("key.asc", "This is not a key!"); + + pipeStringToStdin("Salut!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", "--sign-with", garbageKey.getAbsolutePath(), + cert.getAbsolutePath()); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void decrypt_withGarbageKeyFails() throws IOException { + File key = writeFile("key.asc", "this is garbage"); + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", key.getAbsolutePath()); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void decrypt_verifyWithGarbageCertFails() throws IOException { + File key = writeFile("key.asc", KEY); + File cert = writeFile("cert.asc", "now this is garbage"); + String ciphertext = "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" + + "48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" + + "0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" + + "/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" + + "K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" + + "LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" + + "FpVU\n" + + "=edS5\n" + + "-----END PGP MESSAGE-----"; + File verificationsFile = nonExistentFile("verifications"); + + pipeStringToStdin(ciphertext); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("decrypt", key.getAbsolutePath(), + "--verify-with", cert.getAbsolutePath(), + "--verifications-out", verificationsFile.getAbsolutePath()); + + assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void encryptWithProtectedKey_wrongPassphraseFails() throws IOException { + File password = writeFile("passphrase1", "orange"); + File wrongPassword = writeFile("passphrase2", "blue"); + + File keyFile = pipeStdoutToFile("key.asc"); + assertSuccess(executeCommand("generate-key", "Pedro ", + "--with-key-password", password.getAbsolutePath())); + + File certFile = pipeStdoutToFile("cert.asc"); + pipeFileToStdin(keyFile); + assertSuccess(executeCommand("extract-cert")); + + // Use no passphrase to unlock the key + String msg = "Guten Tag, Welt!\n"; + pipeStringToStdin(msg); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(), + certFile.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + // use wrong passphrase to unlock key when signing message + pipeStringToStdin("Guten Tag, Welt!\n"); + out = pipeStdoutToStream(); + exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(), + "--with-key-password", wrongPassword.getAbsolutePath(), + certFile.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + // use correct passphrase and encrypt+sign message + pipeStringToStdin("Guten Tag, Welt!\n"); + out = pipeStdoutToStream(); + assertSuccess(executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath(), + certFile.getAbsolutePath())); + String ciphertext = out.toString(); + + // Use no passphrase to decrypt key when decrypting + pipeStringToStdin(ciphertext); + out = pipeStdoutToStream(); + exitCode = executeCommand("decrypt", keyFile.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + // Use wrong passphrase to decrypt key when decrypting + pipeStringToStdin(ciphertext); + out = pipeStdoutToStream(); + exitCode = executeCommand("decrypt", "--with-key-password", wrongPassword.getAbsolutePath(), + keyFile.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + // User correct passphrase to decrypt key when decrypting + pipeStringToStdin(ciphertext); + out = pipeStdoutToStream(); + assertSuccess(executeCommand("decrypt", "--with-key-password", password.getAbsolutePath(), + keyFile.getAbsolutePath())); + assertEquals(msg, out.toString()); } } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java new file mode 100644 index 00000000..c80019d1 --- /dev/null +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.cli.commands; + +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest { + + public RoundTripInlineSignInlineVerifyCmdTest() { + super(LoggerFactory.getLogger(RoundTripInlineSignInlineVerifyCmdTest.class)); + } + + private static final String KEY_1_PASSWORD = "takeDemHobbits2Isengard"; + private static final String KEY_1 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" + + "Comment: Legolas \n" + + "\n" + + "lIYEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" + + "2VpV1rf+CQMCVICwUO0SkvdgcPdvXO1cW4KIp6HCVVV6VgU5cvBlmrk9PNUQVBkb\n" + + "6S7oXQu0CgGwJ+QdbooBQqOjMy2MDy+UXaURTaVyWcmetsZJZzD2wrQhTGVnb2xh\n" + + "cyA8bGVnb2xhc0BmZWxsb3dzaGlwLnJpbmc+iI8EExYKAEEFAmNsCncJEJ/whzjf\n" + + "wAIkFiEEWfTsfUqHPmlwKY/en/CHON/AAiQCngECmwEFFgIDAQAECwkIBwUVCgkI\n" + + "CwKZAQAAE10BAN9tN4Le1p4giS6P/yFuKFlDBOeiq1S4EqwYG7qdcqemAP45O3w4\n" + + "3sXliOJBGDR/l/lOMHdPcTOb7VRwWbpIqx8LBJyLBGNsCncSCisGAQQBl1UBBQEB\n" + + "B0AMc+7s6uBqAQcDvfKkD5zYbmB9ZfwIjRWQq/XF+g8KQwMBCAf+CQMCVICwUO0S\n" + + "kvdgHLmKhKW1xxCNZAqQcIHa9F/cqb6Sq/oVFHj2bEYzmGVvFCVUpP7KJWGTeFT+\n" + + "BYK779quIqjxHOfzC3Jmo3BHkUPWYOa0rIh1BBgWCgAdBQJjbAp3Ap4BApsMBRYC\n" + + "AwEABAsJCAcFFQoJCAsACgkQn/CHON/AAiRUewD9HtKrCUf3S1yR28emzITWPgJS\n" + + "UA5mkzEMnYspV7zU4jgA/R6jj/5QqPszElCQNZGtvsDUwYo10iRlQkxPshcPNakJ\n" + + "nIYEY2wKdxYJKwYBBAHaRw8BAQdAYxpRGib/f/tu65gbsV22nmysVVmVgiQuDxyH\n" + + "rz7VCi/+CQMCVICwUO0SkvdgOYYbWltjQRDM3SW/Zw/DiZN9MYZYa0MTgs0SHoaM\n" + + "5LU7jMxNmPR1UtSqEO36QqW91q4fpEkGrdWE4gwjm1bth8pyYKiSFojVBBgWCgB9\n" + + "BQJjbAp3Ap4BApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCW\n" + + "K491s9xIMHwKAQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJ\n" + + "ouSKKuiH6Ck2OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZx\n" + + "NU+t4wQlWOkSsjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfs\n" + + "YgztJA1z/rmm3fmFgMMG\n" + + "=daDH\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + private static final String CERT_1 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" + + "Comment: Legolas \n" + + "\n" + + "mDMEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" + + "2VpV1re0IUxlZ29sYXMgPGxlZ29sYXNAZmVsbG93c2hpcC5yaW5nPoiPBBMWCgBB\n" + + "BQJjbAp3CRCf8Ic438ACJBYhBFn07H1Khz5pcCmP3p/whzjfwAIkAp4BApsBBRYC\n" + + "AwEABAsJCAcFFQoJCAsCmQEAABNdAQDfbTeC3taeIIkuj/8hbihZQwTnoqtUuBKs\n" + + "GBu6nXKnpgD+OTt8ON7F5YjiQRg0f5f5TjB3T3Ezm+1UcFm6SKsfCwS4OARjbAp3\n" + + "EgorBgEEAZdVAQUBAQdADHPu7OrgagEHA73ypA+c2G5gfWX8CI0VkKv1xfoPCkMD\n" + + "AQgHiHUEGBYKAB0FAmNsCncCngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCf8Ic4\n" + + "38ACJFR7AP0e0qsJR/dLXJHbx6bMhNY+AlJQDmaTMQydiylXvNTiOAD9HqOP/lCo\n" + + "+zMSUJA1ka2+wNTBijXSJGVCTE+yFw81qQm4MwRjbAp3FgkrBgEEAdpHDwEBB0Bj\n" + + "GlEaJv9/+27rmBuxXbaebKxVWZWCJC4PHIevPtUKL4jVBBgWCgB9BQJjbAp3Ap4B\n" + + "ApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCWK491s9xIMHwK\n" + + "AQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJouSKKuiH6Ck2\n" + + "OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZxNU+t4wQlWOkS\n" + + "sjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfsYgztJA1z/rmm\n" + + "3fmFgMMG\n" + + "=/lYl\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + private static final String CERT_1_SIGNING_KEY = + "D8906FEB9842569834FEDA9E962B8F75B3DC4830 59F4EC7D4A873E6970298FDE9FF08738DFC00224"; + + private static final String KEY_2 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" + + "Comment: Gollum \n" + + "\n" + + "lFgEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" + + "jpPdjaEAAP9Edg7yeIGEeNP0GrndUpNeZyFAXAlCHJObDbS80G6BBw9ktBlHb2xs\n" + + "dW0gPGdvbGx1bUBkZWVwLmNhdmU+iI8EExYKAEEFAmNsCqYJECZWDSrlPbhvFiEE\n" + + "rqD9LImdP8B3gV8AJlYNKuU9uG8CngECmwEFFgIDAQAECwkIBwUVCgkICwKZAQAA\n" + + "KSkBAOMq6ymNH83E5CBA/mn3DYLhnujzC9cVf/iX2zrsdXMvAQCWdfFy/PlGhP3K\n" + + "M+ej6WIRsx24Yy/NhNPcRJUzcv6dC5xdBGNsCqYSCisGAQQBl1UBBQEBB0DiN/5n\n" + + "AFQafWjnSkKhctFCNkfVRrnAea/2T/D8fYWeYwMBCAcAAP9HbxOhwxqz8I+pwk3e\n" + + "kZXNolWqagrYZkpNvqlBb/JJWBGViHUEGBYKAB0FAmNsCqYCngECmwwFFgIDAQAE\n" + + "CwkIBwUVCgkICwAKCRAmVg0q5T24bw2EAP4pUHVA2pkVspzEttIaQxdoHcnbwjae\n" + + "q12TmWqWDFFvwgD+O2EqHn0iXW49EOQrlP8g+bdWUlT0ZIW3C3Fv7nNA3AScWARj\n" + + "bAqmFgkrBgEEAdpHDwEBB0BHsmdF1Q0aU3YRVDeXGb904Nb7H/cxcasDhcbu2FTo\n" + + "HAAA/j1+WzozN/3lefo76eyENKkXl4f1rQlUreqytuaTsb0WEq6I1QQYFgoAfQUC\n" + + "Y2wKpgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73T\n" + + "bQGDFnN9OwD/QDDi1qq7DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwt\n" + + "L1M49UzQS7OIGP12/9cT66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/Iit\n" + + "ntMexrSK5jCd9JdCCNb2rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2\n" + + "cjK8WE1pfIwQ0ArPCQ==\n" + + "=SzrG\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + private static final String CERT_2 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" + + "Comment: Gollum \n" + + "\n" + + "mDMEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" + + "jpPdjaG0GUdvbGx1bSA8Z29sbHVtQGRlZXAuY2F2ZT6IjwQTFgoAQQUCY2wKpgkQ\n" + + "JlYNKuU9uG8WIQSuoP0siZ0/wHeBXwAmVg0q5T24bwKeAQKbAQUWAgMBAAQLCQgH\n" + + "BRUKCQgLApkBAAApKQEA4yrrKY0fzcTkIED+afcNguGe6PML1xV/+JfbOux1cy8B\n" + + "AJZ18XL8+UaE/coz56PpYhGzHbhjL82E09xElTNy/p0LuDgEY2wKphIKKwYBBAGX\n" + + "VQEFAQEHQOI3/mcAVBp9aOdKQqFy0UI2R9VGucB5r/ZP8Px9hZ5jAwEIB4h1BBgW\n" + + "CgAdBQJjbAqmAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQJlYNKuU9uG8NhAD+\n" + + "KVB1QNqZFbKcxLbSGkMXaB3J28I2nqtdk5lqlgxRb8IA/jthKh59Il1uPRDkK5T/\n" + + "IPm3VlJU9GSFtwtxb+5zQNwEuDMEY2wKphYJKwYBBAHaRw8BAQdAR7JnRdUNGlN2\n" + + "EVQ3lxm/dODW+x/3MXGrA4XG7thU6ByI1QQYFgoAfQUCY2wKpgKeAQKbAgUWAgMB\n" + + "AAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73TbQGDFnN9OwD/QDDi1qq7\n" + + "DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwtL1M49UzQS7OIGP12/9cT\n" + + "66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/IitntMexrSK5jCd9JdCCNb2\n" + + "rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2cjK8WE1pfIwQ0ArPCQ==\n" + + "=j1LR\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + private static final String CERT_2_SIGNING_KEY = + "7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F"; + + private static final String MESSAGE = "One does not simply use OpenPGP!\n" + + "\n" + + "There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power."; + + @Test + public void createCleartextSignedMessage() throws IOException { + File key = writeFile("key.asc", KEY_1); + File password = writeFile("password", KEY_1_PASSWORD); + + pipeStringToStdin(MESSAGE); + ByteArrayOutputStream ciphertextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-sign", + "--as", "cleartextsigned", + key.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath())); + + String cleartextSigned = ciphertextOut.toString(); + assertTrue(cleartextSigned.startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n" + + "Hash: ")); + assertTrue(cleartextSigned.contains(MESSAGE)); + assertTrue(cleartextSigned.contains("\n-----BEGIN PGP SIGNATURE-----\n")); + assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n")); + } + + @Test + public void createAndVerifyCleartextSignedMessage() throws IOException { + File key = writeFile("key.asc", KEY_1); + File password = writeFile("password", KEY_1_PASSWORD); + + pipeStringToStdin(MESSAGE); + ByteArrayOutputStream ciphertextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-sign", + "--as", "cleartextsigned", + key.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath())); + + File cert = writeFile("cert.asc", CERT_1); + File verifications = nonExistentFile("verifications"); + pipeStringToStdin(ciphertextOut.toString()); + ByteArrayOutputStream plaintextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-verify", + "--verifications-out", verifications.getAbsolutePath(), + cert.getAbsolutePath())); + + assertEquals(MESSAGE, plaintextOut.toString()); + String verificationString = readStringFromFile(verifications); + assertTrue(verificationString.contains(CERT_1_SIGNING_KEY)); + } + + @Test + public void createAndVerifyMultiKeyBinarySignedMessage() throws IOException { + File key1Pass = writeFile("password", KEY_1_PASSWORD); + File key1 = writeFile("key1.asc", KEY_1); + File key2 = writeFile("key2.asc", KEY_2); + + pipeStringToStdin(MESSAGE); + ByteArrayOutputStream ciphertextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-sign", + "--as", "binary", + "--no-armor", + key2.getAbsolutePath(), + "--with-key-password", key1Pass.getAbsolutePath(), + key1.getAbsolutePath())); + + assertFalse(ciphertextOut.toString().startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n")); + byte[] unarmoredMessage = ciphertextOut.toByteArray(); + + File cert1 = writeFile("cert1.asc", CERT_1); + File cert2 = writeFile("cert2.asc", CERT_2); + File verificationFile = nonExistentFile("verifications"); + pipeBytesToStdin(unarmoredMessage); + ByteArrayOutputStream plaintextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-verify", + "--verifications-out", verificationFile.getAbsolutePath(), + cert1.getAbsolutePath(), cert2.getAbsolutePath())); + + assertEquals(MESSAGE, plaintextOut.toString()); + String verification = readStringFromFile(verificationFile); + assertTrue(verification.contains(CERT_1_SIGNING_KEY)); + assertTrue(verification.contains(CERT_2_SIGNING_KEY)); + } + + @Test + public void createTextSignedMessageInlineDetachAndDetachedVerify() throws IOException { + File key = writeFile("key.asc", KEY_1); + File password = writeFile("password", KEY_1_PASSWORD); + + pipeStringToStdin(MESSAGE); + ByteArrayOutputStream ciphertextOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-sign", + "--as", "cleartextsigned", + key.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath())); + + File sigFile = nonExistentFile("sig.asc"); + pipeStringToStdin(ciphertextOut.toString()); + ByteArrayOutputStream msgOut = pipeStdoutToStream(); + assertSuccess(executeCommand("inline-detach", + "--signatures-out", sigFile.getAbsolutePath())); + assertEquals(MESSAGE, msgOut.toString()); + + File cert = writeFile("cert.asc", CERT_1); + pipeStringToStdin(msgOut.toString()); + ByteArrayOutputStream verificationsOut = pipeStdoutToStream(); + assertSuccess(executeCommand("verify", + sigFile.getAbsolutePath(), + cert.getAbsolutePath())); + + String verificationString = verificationsOut.toString(); + assertTrue(verificationString.contains(CERT_1_SIGNING_KEY)); + } +} diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java index 0fce1273..7f420054 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java @@ -5,129 +5,321 @@ package org.pgpainless.cli.commands; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.util.Date; -import com.ginsberg.junit.exit.FailOnSystemExit; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.util.io.Streams; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.cli.PGPainlessCLI; -import org.pgpainless.cli.TestUtils; +import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.generation.type.eddsa.EdDSACurve; +import org.pgpainless.key.generation.type.xdh.XDHSpec; import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.util.KeyRingUtils; +import org.slf4j.LoggerFactory; +import sop.exception.SOPGPException; +import sop.util.UTCUtil; -public class RoundTripSignVerifyCmdTest { +public class RoundTripSignVerifyCmdTest extends CLITest { - private static File tempDir; - private static PrintStream originalSout; + public RoundTripSignVerifyCmdTest() { + super(LoggerFactory.getLogger(RoundTripSignVerifyCmdTest.class)); + } - @BeforeAll - public static void prepare() throws IOException { - tempDir = TestUtils.createTempDirectory(); + private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" + + "Comment: Sigmund \n" + + "\n" + + "lFgEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" + + "kedJ3ZUAAQCZy5p1cvQvRIWUopHwhnrD/oVAa1dNT/nA3cihQ5gkZBHPtCBTaWdt\n" + + "dW5kIDxzaWdtdW5kQHBncGFpbmxlc3Mub3JnPoiPBBMWCgBBBQJja/OSCRAJmxG/\n" + + "KWo3PhYhBJ2glCPJ+UukzKMJUQmbEb8pajc+Ap4BApsBBRYCAwEABAsJCAcFFQoJ\n" + + "CAsCmQEAACM9AP9APloI2waD5gXsJqzenRVU4n/VmZUvcdUyhlbpab/0HQEAlaTw\n" + + "ZvxVyaf8EMFSJOY+LcgacHaZDHRPA1nS3bIfKwycXQRja/OSEgorBgEEAZdVAQUB\n" + + "AQdA1WL4QKgRxbvzW91ICM6PoICSNh2QHK6j0pIdN/cqXz0DAQgHAAD/bOk3WqbF\n" + + "QAE8xxm0w/KDZzL1N0yPcBQ5z4XKmu77FCgQ04h1BBgWCgAdBQJja/OSAp4BApsM\n" + + "BRYCAwEABAsJCAcFFQoJCAsACgkQCZsRvylqNz6rgQEAzoG6HnPCYi2i2c6/ufuy\n" + + "pBkLby2u1JjD0CWSbrM4dZ0A/j/pI4a9b8LcrZcuY2QwHqsXPAJp8QtOOQN6gTvN\n" + + "WcQNnFgEY2vzkhYJKwYBBAHaRw8BAQdAsxcDCvst/GbWxQvvOpChSvmbqWeuBgm3\n" + + "1vRoujFVFcYAAP9Ww46yfWipb8OivTSX+PvgdUhEeVgxENpsyOQLLhQP/RFziNUE\n" + + "GBYKAH0FAmNr85ICngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OS\n" + + "AAoJENqfQTmGIR3GtsMBAL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855\n" + + "AP4uAXDiaNxYTugqnG471jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOX\n" + + "AP45LPV6I4+D3h8etdiEA2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoR\n" + + "WxCFj+gDgqDNLzwoA4iNo1VMtQc=\n" + + "=/Np6\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" + + "Comment: Sigmund \n" + + "\n" + + "mDMEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" + + "kedJ3ZW0IFNpZ211bmQgPHNpZ211bmRAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEF\n" + + "AmNr85IJEAmbEb8pajc+FiEEnaCUI8n5S6TMowlRCZsRvylqNz4CngECmwEFFgID\n" + + "AQAECwkIBwUVCgkICwKZAQAAIz0A/0A+WgjbBoPmBewmrN6dFVTif9WZlS9x1TKG\n" + + "Vulpv/QdAQCVpPBm/FXJp/wQwVIk5j4tyBpwdpkMdE8DWdLdsh8rDLg4BGNr85IS\n" + + "CisGAQQBl1UBBQEBB0DVYvhAqBHFu/Nb3UgIzo+ggJI2HZAcrqPSkh039ypfPQMB\n" + + "CAeIdQQYFgoAHQUCY2vzkgKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJEAmbEb8p\n" + + "ajc+q4EBAM6Buh5zwmItotnOv7n7sqQZC28trtSYw9Alkm6zOHWdAP4/6SOGvW/C\n" + + "3K2XLmNkMB6rFzwCafELTjkDeoE7zVnEDbgzBGNr85IWCSsGAQQB2kcPAQEHQLMX\n" + + "Awr7Lfxm1sUL7zqQoUr5m6lnrgYJt9b0aLoxVRXGiNUEGBYKAH0FAmNr85ICngEC\n" + + "mwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OSAAoJENqfQTmGIR3GtsMB\n" + + "AL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855AP4uAXDiaNxYTugqnG47\n" + + "1jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOXAP45LPV6I4+D3h8etdiE\n" + + "A2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoRWxCFj+gDgqDNLzwoA4iN\n" + + "o1VMtQc=\n" + + "=KuJ4\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + private static final String PLAINTEXT = "Hello, World!\n"; + private static final String BINARY_SIG = "-----BEGIN PGP SIGNATURE-----\n" + + "Version: PGPainless\n" + + "\n" + + "iHUEABYKACcFAmNr9BgJENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" + + "AKocAP48P2C3TU33T3Zy73clw0eBa1oW9pwxTGuFxhgOBzmoSwEArj0781GlpTB0\n" + + "Vnr2PjPYEqzB+ZuOzOnGhsVGob4c3Ao=\n" + + "=VWAZ\n" + + "-----END PGP SIGNATURE-----"; + private static final String BINARY_SIG_VERIFICATION = + "2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n"; + private static final String TEXT_SIG = "-----BEGIN PGP SIGNATURE-----\n" + + "Version: PGPainless\n" + + "\n" + + "iHUEARYKACcFAmNr9E4JENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" + + "AG+CAQD1B3GAAlyxahSiGhvJv7YAI1m6qGcI7dIXcV7FkAFPSgEAlZ0UpCC8oGR+\n" + + "hi/mQlex4z0hDWSA4abAjclPTJ+qkAI=\n" + + "=s5xn\n" + + "-----END PGP SIGNATURE-----"; + private static final String TEXT_SIG_VERIFICATION = + "2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n"; + private static final Date TEXT_SIG_CREATION = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z"); + + @Test + public void createArmoredSignature() throws IOException { + File keyFile = writeFile("key.asc", KEY); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("sign", "--as", "text", keyFile.getAbsolutePath())); + assertTrue(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n")); } @Test - @FailOnSystemExit - public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - originalSout = System.out; - InputStream originalIn = System.in; + public void createUnarmoredSignature() throws IOException { + File keyFile = writeFile("key.asc", KEY); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("sign", "--no-armor", keyFile.getAbsolutePath())); + assertFalse(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n")); + } - // Write alice key to disc - File aliceKeyFile = new File(tempDir, "alice.key"); - assertTrue(aliceKeyFile.createNewFile()); - PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing() - .modernKeyRing("alice"); - OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile); - Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut); - aliceKeyOut.close(); + @Test + public void unarmorArmoredSigAndVerify() throws IOException { + File certFile = writeFile("cert.asc", CERT); - // Write alice pub key to disc - File aliceCertFile = new File(tempDir, "alice.pub"); - assertTrue(aliceCertFile.createNewFile()); - PGPPublicKeyRing alicePub = KeyRingUtils.publicKeyRingFrom(aliceKeys); - OutputStream aliceCertOut = new FileOutputStream(aliceCertFile); - Streams.pipeAll(new ByteArrayInputStream(alicePub.getEncoded()), aliceCertOut); - aliceCertOut.close(); + pipeStringToStdin(BINARY_SIG); + File unarmoredSigFile = pipeStdoutToFile("sig.pgp"); + assertSuccess(executeCommand("dearmor")); - // Write test data to disc - String data = "If privacy is outlawed, only outlaws will have privacy.\n"; - File dataFile = new File(tempDir, "data"); - assertTrue(dataFile.createNewFile()); - FileOutputStream dataOut = new FileOutputStream(dataFile); - Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut); - dataOut.close(); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("verify", unarmoredSigFile.getAbsolutePath(), certFile.getAbsolutePath())); - // Define micalg output file - File micalgOut = new File(tempDir, "micalg"); + assertEquals(BINARY_SIG_VERIFICATION, out.toString()); + } - // Sign test data - FileInputStream dataIn = new FileInputStream(dataFile); - System.setIn(dataIn); - File sigFile = new File(tempDir, "sig.asc"); - assertTrue(sigFile.createNewFile()); - FileOutputStream sigOut = new FileOutputStream(sigFile); - System.setOut(new PrintStream(sigOut)); - PGPainlessCLI.execute("sign", "--armor", "--micalg-out", micalgOut.getAbsolutePath(), aliceKeyFile.getAbsolutePath()); - sigOut.close(); + @Test + public void testNotBefore() throws IOException { + File cert = writeFile("cert.asc", CERT); + File sigFile = writeFile("sig.asc", TEXT_SIG); + Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60); + + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(), + "--not-before", UTCUtil.formatUTCDate(plus1Minute)); + + assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60); + pipeStringToStdin(PLAINTEXT); + out = pipeStdoutToStream(); + exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(), + "--not-before", UTCUtil.formatUTCDate(minus1Minute)); + + assertSuccess(exitCode); + assertEquals(TEXT_SIG_VERIFICATION, out.toString()); + } + + @Test + public void testNotAfter() throws IOException { + File cert = writeFile("cert.asc", CERT); + File sigFile = writeFile("sig.asc", TEXT_SIG); + + Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(), + "--not-after", UTCUtil.formatUTCDate(minus1Minute)); + + assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + + Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60); + pipeStringToStdin(PLAINTEXT); + out = pipeStdoutToStream(); + exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(), + "--not-after", UTCUtil.formatUTCDate(plus1Minute)); + + assertSuccess(exitCode); + assertEquals(TEXT_SIG_VERIFICATION, out.toString()); + } + + @Test + public void testSignWithIncapableKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing() + .addUserId("Cannot Sign ") + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) + .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + .build(); + File keyFile = writeFile("key.pgp", secretKeys.getEncoded()); + + pipeStringToStdin("Hello, World!\n"); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("sign", keyFile.getAbsolutePath()); + + assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void testSignatureCreationAndVerification() + throws IOException { + // Create key and cert + File aliceKeyFile = pipeStdoutToFile("alice.key"); + assertSuccess(executeCommand("generate-key", "Alice ")); + File aliceCertFile = pipeStdoutToFile("alice.cert"); + pipeFileToStdin(aliceKeyFile); + assertSuccess(executeCommand("extract-cert")); + + File micalgOut = nonExistentFile("micalg"); + String msg = "If privacy is outlawed, only outlaws will have privacy.\n"; + File dataFile = writeFile("data", msg); + + // sign data + File sigFile = pipeStdoutToFile("sig.asc"); + pipeFileToStdin(dataFile); + assertSuccess(executeCommand("sign", + "--armor", + "--as", "binary", + "--micalg-out", micalgOut.getAbsolutePath(), + aliceKeyFile.getAbsolutePath())); // verify test data signature - ByteArrayOutputStream verifyOut = new ByteArrayOutputStream(); - System.setOut(new PrintStream(verifyOut)); - dataIn = new FileInputStream(dataFile); - System.setIn(dataIn); - PGPainlessCLI.execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath()); - dataIn.close(); + pipeFileToStdin(dataFile); + ByteArrayOutputStream verificationsOut = pipeStdoutToStream(); + assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath())); // Test verification output + PGPPublicKeyRing cert = PGPainless.readKeyRing().publicKeyRing(readBytesFromFile(aliceCertFile)); + KeyRingInfo info = PGPainless.inspectKeyRing(cert); + // [date] [signing-key-fp] [primary-key-fp] signed by [key.pub] - String verification = verifyOut.toString(); + String verification = verificationsOut.toString(); String[] split = verification.split(" "); - OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys); - OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0)); + OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(cert); + OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(info.getSigningSubkeys().get(0)); assertEquals(signingKeyFingerprint.toString(), split[1].trim(), verification); assertEquals(primaryKeyFingerprint.toString(), split[2].trim()); // Test micalg output - assertTrue(micalgOut.exists()); - FileReader fileReader = new FileReader(micalgOut); - BufferedReader bufferedReader = new BufferedReader(fileReader); - String line = bufferedReader.readLine(); - assertNull(bufferedReader.readLine()); - bufferedReader.close(); - assertEquals("pgp-sha512", line); - - System.setIn(originalIn); + String content = readStringFromFile(micalgOut); + assertEquals("pgp-sha512", content); } - @AfterAll - public static void after() { - System.setOut(originalSout); - // CHECKSTYLE:OFF - System.out.println(tempDir.getAbsolutePath()); - // CHECKSTYLE:ON + private static final String PROTECTED_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: 738E EAB2 503D 322D 613A C42A B18E 8BF8 884F C050\n" + + "Comment: Axel \n" + + "\n" + + "lIYEY2v6aRYJKwYBBAHaRw8BAQdA3PXtH19zYpVQ9zTU3zlY+iXUptelAO3z4vK/\n" + + "M2FkmrP+CQMCYgVa6K+InVJguITSDIA+HQ6vhOZ5Dbanqx7GFbJbJLD2fWrxhTSr\n" + + "BUWGaUWTqN647auD/kUI8phH1cedVL6CzVR+YWvaWj9zZHr/CYXLobQaQXhlbCA8\n" + + "YXhlbEBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2v6aQkQsY6L+IhPwFAWIQRz\n" + + "juqyUD0yLWE6xCqxjov4iE/AUAKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAACq\n" + + "zgEAkxB+dUI7Jjcg5zRvT1EfE9DKCI1qTsxOAU/ZXLcSXLkBAJtWRsyptetZvjzB\n" + + "Ze2A7ArOl4q+IvKvun/d783YyRMInIsEY2v6aRIKKwYBBAGXVQEFAQEHQPFmlZ+o\n" + + "jCGEo2X0474vJfRG7blctuZXmCbC0sLO7MgzAwEIB/4JAwJiBVror4idUmDFhBq4\n" + + "lEhJxjCVc6aSD6+EWRT3YdplqCmNdynnrPombUFst6LfJFzns3H3d0rCeXHfQr93\n" + + "GrHTLkHfW8G3x0PJJPiqFkBviHUEGBYKAB0FAmNr+mkCngECmwwFFgIDAQAECwkI\n" + + "BwUVCgkICwAKCRCxjov4iE/AUNC2AP9WDx4lHt9oYFLSrM8vMLRFI31U8TkYrtCe\n" + + "pYICE76cIAEA5+wEbtE5vQrLxOqIRueVVdzwK9kTeMvSIQfc9PNoyQKchgRja/pp\n" + + "FgkrBgEEAdpHDwEBB0CyAEVlCUbFr3dBBG3MQ84hjCPfYqSx9kYsTN8j5Og6uP4J\n" + + "AwJiBVror4idUmCIFuAYXia0YpEhEpB/Lrn/D6/WAUPEgZjNLMvJzL//EmhkWfEa\n" + + "OfQz/fslj1erWNjLKNiW5C/TvGapDfjbn596AkNlcd1JiNUEGBYKAH0FAmNr+mkC\n" + + "ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/ppAAoJELRgil1uCuQj\n" + + "VUYBAJecbedwwqWQITVqucEBIraTRoc6ZGkN8jytDp8z9CsBAQDrb/W/J/kze6ln\n" + + "nRyJSriWF3SjcKOGIRkUslmdJEPPCQAKCRCxjov4iE/AUAvbAQDBBgQFG8acTT5L\n" + + "cyIi1Ix9/XBG7G23SSs6l7Beap8M+wEAmK13NYuq7Mv/mct8iIKZbBFH9aAiY+nX\n" + + "3Uct4Q5f0w0=\n" + + "=K65R\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + private static final String PASSPHRASE = "orange"; + private static final String SIGNING_KEY = "9846F3606EE875FB77EC8808B4608A5D6E0AE423 738EEAB2503D322D613AC42AB18E8BF8884FC050"; + + @Test + public void signWithProtectedKey_missingPassphraseFails() throws IOException { + File key = writeFile("key.asc", PROTECTED_KEY); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("sign", key.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); } + + @Test + public void signWithProtectedKey_wrongPassphraseFails() throws IOException { + File password = writeFile("password", "blue"); + File key = writeFile("key.asc", PROTECTED_KEY); + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream out = pipeStdoutToStream(); + int exitCode = executeCommand("sign", key.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath()); + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode); + assertEquals(0, out.size()); + } + + @Test + public void signWithProtectedKey() throws IOException { + File password = writeFile("password", PASSPHRASE); + File key = writeFile("key.asc", PROTECTED_KEY); + pipeStringToStdin(PROTECTED_KEY); + File cert = pipeStdoutToFile("cert.asc"); + assertSuccess(executeCommand("extract-cert")); + + pipeStringToStdin(PLAINTEXT); + File sigFile = pipeStdoutToFile("sig.asc"); + assertSuccess(executeCommand("sign", key.getAbsolutePath(), + "--with-key-password", password.getAbsolutePath())); + + pipeStringToStdin(PLAINTEXT); + ByteArrayOutputStream verificationOut = pipeStdoutToStream(); + assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath())); + assertTrue(verificationOut.toString().contains(SIGNING_KEY)); + } + } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java new file mode 100644 index 00000000..2e4aa7e4 --- /dev/null +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.cli.commands; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +public class VersionCmdTest extends CLITest { + + public VersionCmdTest() { + super(LoggerFactory.getLogger(VersionCmdTest.class)); + } + + @Test + public void testVersion() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("version")); + assertTrue(out.toString().startsWith("PGPainless-SOP ")); + } + + @Test + public void testGetBackendVersion() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("version", "--backend")); + assertTrue(out.toString().startsWith("Bouncy Castle ")); + } + + @Test + public void testExtendedVersion() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("version", "--extended")); + String info = out.toString(); + assertTrue(info.startsWith("PGPainless-SOP ")); + assertTrue(info.contains("Bouncy Castle")); + assertTrue(info.contains("Stateless OpenPGP Protocol")); + } +}