1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-22 12:22:06 +01:00

Rework CLI tests

This commit is contained in:
Paul Schaub 2022-11-09 22:02:16 +01:00
parent fd55ce3657
commit 858f8e00f3
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
11 changed files with 1548 additions and 473 deletions

View file

@ -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());
}
}

View file

@ -0,0 +1,166 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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");
}
}

View file

@ -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());
}

View file

@ -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 <juliet@capulet.lit>");
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 <juliet@capulet.lit>"));
}
@Test
public void testExtractCertFromCertFails() throws IOException {
// Generate key
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
// 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 <alice@pgpainless.org>"));
// 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"));
}
}

View file

@ -1,106 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 <juliet@capulet.lit>");
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isFullyDecrypted());
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
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 <juliet@capulet.lit>",
"--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 <test@test.test>");
byte[] outBegin = new byte[37];
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
assertFalse(Arrays.equals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES));
}
}

View file

@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 <alice@pgpainless.org>"));
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 <alice@pgpainless.org>"), info.getUserIds());
}
@Test
public void testGenerateBinaryKey() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("generate-key", "--no-armor",
"Alice <alice@pgpainless.org>"));
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@pgpainless.org>", "Alice <alice@openpgp.org>"));
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@pgpainless.org>", "Alice <alice@openpgp.org>"), 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 <alice@pgpainless.org>"));
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 <alice@pgpainless.org>");
assertEquals(SOPGPException.MissingInput.EXIT_CODE, exit, "Expected MISSING_INPUT (" + SOPGPException.MissingInput.EXIT_CODE + ")");
}
}

View file

@ -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());
String sig = readStringFromFile(sigFile);
TestUtils.assertSignatureIsArmored(sig.getBytes());
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig);
} catch (FileNotFoundException e) {
fail("Signature File must have been written.", e);
}
// Check if produced signature still checks out
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(verifyOut));
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
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();
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);
} catch (FileNotFoundException e) {
fail("Signature File must have been written.", e);
}
// Check if produced signature still checks out
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(verifyOut));
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
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());
}
}

View file

@ -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 <alice@pgpainless.org>\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 <alice@pgpainless.org>\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 <juliet@capulet.lit>"));
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 <romeo@montague.lit>"));
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 <juliet@capulet.lit>");
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 <romeo@montague.lit>");
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 <no@crypt.key>")
.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 <cannot@sign.key>")
.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 <pascal@password.protected>"));
// 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 <bob@pgpainless.org>"));
// 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 <pedro@pgpainless.org>",
"--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());
}
}

View file

@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 <legolas@fellowship.ring>\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 <legolas@fellowship.ring>\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 <gollum@deep.cave>\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 <gollum@deep.cave>\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));
}
}

View file

@ -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 <sigmund@pgpainless.org>\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 <sigmund@pgpainless.org>\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 <cannot@sign.key>")
.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 <alice@pgpainless.org>"));
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 <axel@pgpainless.org>\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));
}
}

View file

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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"));
}
}