diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java index f040eac8..ec911082 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/commands/Decrypt.java @@ -15,6 +15,20 @@ */ package org.pgpainless.sop.commands; +import static org.pgpainless.sop.Print.err_ln; +import static org.pgpainless.sop.SopKeyUtil.loadKeysFromFiles; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -28,21 +42,6 @@ import org.pgpainless.decryption_verification.OpenPgpMetadata; import org.pgpainless.key.OpenPgpV4Fingerprint; import picocli.CommandLine; -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.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import static org.pgpainless.sop.Print.err_ln; -import static org.pgpainless.sop.SopKeyUtil.loadKeysFromFiles; - @CommandLine.Command(name = "decrypt", description = "Decrypt a message from standard input") public class Decrypt implements Runnable { @@ -125,7 +124,7 @@ public class Decrypt implements Runnable { try { verifyWith.add(PGPainless.readKeyRing().publicKeyRing(new FileInputStream(f))); } catch (IOException e) { - e.printStackTrace(); + } } } @@ -152,7 +151,6 @@ public class Decrypt implements Runnable { Streams.pipeAll(decryptionStream, System.out); decryptionStream.close(); } catch (IOException e) { - e.printStackTrace(); return; } if (verifyOut == null) { @@ -178,10 +176,11 @@ public class Decrypt implements Runnable { try { verifyOut.createNewFile(); PrintStream verifyPrinter = new PrintStream(new FileOutputStream(verifyOut)); + // CHECKSTYLE:OFF verifyPrinter.println(sb.toString()); + // CHECKSTYLE:ON verifyPrinter.close(); } catch (IOException e) { - e.printStackTrace(); } } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java new file mode 100644 index 00000000..8a023dd9 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +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.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 org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class EncryptDecryptTest { + + private static File tempDir; + private static PrintStream originalSout; + + @BeforeAll + public static void prepare() throws IOException { + tempDir = TestUtils.createTempDirectory(); + } + + @Test + public void test() throws IOException { + originalSout = System.out; + File julietKeyFile = new File(tempDir, "juliet.key"); + assertTrue(julietKeyFile.createNewFile()); + + File julietCertFile = new File(tempDir, "juliet.asc"); + assertTrue(julietCertFile.createNewFile()); + + File romeoKeyFile = new File(tempDir, "romeo.key"); + assertTrue(romeoKeyFile.createNewFile()); + + 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.main(new String[] {"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.main(new String[] {"extract-cert"}); + julietKeyIn.close(); + julietCertOut.close(); + + OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile); + System.setOut(new PrintStream(romeoKeyOut)); + PGPainlessCLI.main(new String[] {"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.main(new String[] {"extract-cert"}); + romeoKeyIn.close(); + romeoCertOut.close(); + + 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.main(new String[] {"encrypt", + "--sign-with", romeoKeyFile.getAbsolutePath(), + julietCertFile.getAbsolutePath()}); + msgAscOut.close(); + + File verifyFile = new File(tempDir, "verify.txt"); + assertTrue(verifyFile.createNewFile()); + + FileInputStream msgAscIn = new FileInputStream(msgAscFile); + System.setIn(msgAscIn); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + PGPainlessCLI.main(new String[] {"decrypt", + "--verify-out", verifyFile.getAbsolutePath(), + "--verify-with", romeoCertFile.getAbsolutePath(), + julietKeyFile.getAbsolutePath()}); + msgAscIn.close(); + + assertEquals(msg, out.toString()); + } + + @AfterAll + public static void after() { + System.setOut(originalSout); + // CHECKSTYLE:OFF + System.out.println(tempDir.getAbsolutePath()); + // CHECKSTYLE:ON + } +} diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/ExitException.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExitException.java new file mode 100644 index 00000000..64d85042 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExitException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +public class ExitException extends SecurityException { + + private final int status; + + public ExitException(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } +} diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java new file mode 100644 index 00000000..a2035a86 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/ExtractCertTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +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.IOException; +import java.io.PrintStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +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.key.info.KeyRingInfo; + +public class ExtractCertTest { + + @Test + 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)); + + PGPainlessCLI.main(new String[] {"extract-cert"}); + PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray()); + KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys); + assertFalse(info.isSecretKey()); + assertTrue(info.isUserIdValid("Juliet Capulet ")); + } +} diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateCertTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateCertTest.java new file mode 100644 index 00000000..22cae771 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateCertTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +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.sop.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.key.info.KeyRingInfo; + +public class GenerateCertTest { + + private static File tempDir; + + + @BeforeAll + public static void setup() throws IOException { + tempDir = TestUtils.createTempDirectory(); + } + + @Test + public void testKeyGeneration() throws IOException, PGPException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + PGPainlessCLI.main(new String[] {"generate-key", "--armor", "Juliet Capulet "}); + + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray()); + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + assertTrue(info.isUserIdValid("Juliet Capulet ")); + + byte[] outBegin = new byte[37]; + System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37); + assertArrayEquals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES); + } + + @Test + public void testNoArmor() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + System.setOut(new PrintStream(out)); + PGPainlessCLI.main(new String[] {"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-sop/src/test/java/org/pgpainless/sop/NoExitSecurityManager.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/NoExitSecurityManager.java new file mode 100644 index 00000000..f582e653 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/NoExitSecurityManager.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +import java.security.Permission; + +public class NoExitSecurityManager extends SecurityManager { + + @Override + public void checkPermission(Permission perm) { + // allow anything + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow anything + } + + @Override + public void checkExit(int status) { + super.checkExit(status); + throw new ExitException(status); + } +} diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/TestUtils.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/TestUtils.java new file mode 100644 index 00000000..9f5e74a2 --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/TestUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.sop; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Random; + +public class TestUtils { + + public static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + private static final Random RANDOM = new Random(); + + public static final String ARMOR_PRIVATE_KEY_HEADER = "-----BEGIN PGP PRIVATE KEY BLOCK-----"; + public static final byte[] ARMOR_PRIVATE_KEY_HEADER_BYTES = + ARMOR_PRIVATE_KEY_HEADER.getBytes(StandardCharsets.UTF_8); + + public static File createTempDirectory() throws IOException { + String name = randomString(10); + File dir = Files.createTempDirectory(name).toFile(); + // dir.deleteOnExit(); + return dir; + } + + private static String randomString(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(ALPHABET.charAt(RANDOM.nextInt(ALPHABET.length()))); + } + return sb.toString(); + } +}