From 084923c82848b4c9b6a207b73c25ee49d5727763 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 19 Jun 2022 18:24:54 +0200 Subject: [PATCH 001/444] SOP-Java 4.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index ff0c7f7..9aa5316 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.0' - isSnapshot = false + shortVersion = '4.0.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 77c76c57d09aed0ab06d6423087716eb476ba213 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 20 Jun 2022 19:50:50 +0200 Subject: [PATCH 002/444] Add InlineDetachCmdTest --- .../java/sop/cli/picocli/TestFileUtil.java | 9 ++- .../picocli/commands/InlineDetachCmdTest.java | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/TestFileUtil.java b/sop-java-picocli/src/test/java/sop/cli/picocli/TestFileUtil.java index 385fe1a..d1dc5d1 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/TestFileUtil.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/TestFileUtil.java @@ -12,10 +12,15 @@ import java.nio.file.Files; public class TestFileUtil { - public static File writeTempStringFile(String string) throws IOException { - File tempDir = Files.createTempDirectory("tmpDir").toFile(); + public static File createTempDir() throws IOException { + File tempDir = Files.createTempDirectory("tmpFir").toFile(); tempDir.deleteOnExit(); tempDir.mkdirs(); + return tempDir; + } + + public static File writeTempStringFile(String string) throws IOException { + File tempDir = createTempDir(); File passwordFile = new File(tempDir, "file"); passwordFile.createNewFile(); diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java new file mode 100644 index 0000000..3a16c61 --- /dev/null +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sop.ReadyWithResult; +import sop.SOP; +import sop.Signatures; +import sop.cli.picocli.SopCLI; +import sop.cli.picocli.TestFileUtil; +import sop.exception.SOPGPException; +import sop.operation.InlineDetach; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class InlineDetachCmdTest { + + InlineDetach inlineDetach; + + @BeforeEach + public void mockComponents() { + inlineDetach = mock(InlineDetach.class); + + SOP sop = mock(SOP.class); + when(sop.inlineDetach()).thenReturn(inlineDetach); + SopCLI.setSopInstance(sop); + } + + @Test + @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) + public void testMissingSignaturesOutResultsInExit19() { + SopCLI.main(new String[] {"inline-detach"}); + } + + @Test + public void testNoArmorIsCalled() throws IOException { + // Create temp dir and allocate non-existing tempfile for sigout + File tempDir = TestFileUtil.createTempDir(); + File tempFile = new File(tempDir, "sigs.out"); + tempFile.deleteOnExit(); + + // mock inline-detach + when(inlineDetach.message((InputStream) any())) + .thenReturn(new ReadyWithResult() { + @Override + public Signatures writeTo(OutputStream outputStream) throws SOPGPException.NoSignature { + return new Signatures() { + @Override + public void writeTo(OutputStream signatureOutputStream) throws IOException { + signatureOutputStream.write("Signatures!\n".getBytes(StandardCharsets.UTF_8)); + } + }; + } + }); + + SopCLI.main(new String[] {"inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"}); + verify(inlineDetach, times(1)).noArmor(); + verify(inlineDetach, times(1)).message((InputStream) any()); + } +} From 86e39809ae66ffe47597ac38b3ee044b16b05151 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 23 Jul 2022 01:11:40 +0200 Subject: [PATCH 003/444] Fix sop.properties sop command header --- sop-java-picocli/src/main/resources/sop.properties | 4 ++-- sop-java-picocli/src/main/resources/sop_de.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sop-java-picocli/src/main/resources/sop.properties b/sop-java-picocli/src/main/resources/sop.properties index 68f5a45..78b9225 100644 --- a/sop-java-picocli/src/main/resources/sop.properties +++ b/sop-java-picocli/src/main/resources/sop.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop -usage.header=Stateless OpenPGP Protocol +sop.usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by picocli%n sop.locale=Locale for description texts @@ -11,7 +11,7 @@ usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n -usage.exitCodeList.0=\u00200:Successful program execution. +usage.exitCodeList.0=\u00200:Successful program execution usage.exitCodeList.1=\u00201:Generic program error usage.exitCodeList.2=\u00203:Verification requested but no verifiable signature found usage.exitCodeList.3=13:Unsupported asymmetric algorithm diff --git a/sop-java-picocli/src/main/resources/sop_de.properties b/sop-java-picocli/src/main/resources/sop_de.properties index 6026f9a..1b59021 100644 --- a/sop-java-picocli/src/main/resources/sop_de.properties +++ b/sop-java-picocli/src/main/resources/sop_de.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop -usage.header=Stateless OpenPGP Protocol +sop.usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by Picocli%n sop.locale=Gebietsschema für Beschreibungstexte # Generic From 3801a644ef15fc52d4f3c424ea4269b683a2db97 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 23 Jul 2022 01:21:14 +0200 Subject: [PATCH 004/444] Do not overwrite command name. Doing so would break resolution of command.usage.header strings --- sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 750cfa8..2ce2e65 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -72,8 +72,7 @@ public class SopCLI { CommandLine gen = cmd.getSubcommands().get("generate-completion"); gen.getCommandSpec().usageMessage().hidden(true); - cmd.setCommandName(EXECUTABLE_NAME) - .setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) + cmd.setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) .setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper()) .setCaseInsensitiveEnumValuesAllowed(true); From fa52df385e3f9c0254ee5d9178deafbb264db37e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 25 Jul 2022 19:15:47 +0200 Subject: [PATCH 005/444] Split message resources into separate per-command resource files. Since picocli 4.7.0, subcommands inherit resources from their parent commands, so we can store shared stuff like error msgs etc. in the parent (sop) resources file. This enables us to rename the parent command downstream (e.g. in pgpainless-cli). Only the help command breaks when renaming the parent command. TODO: Fix --- .../sop/cli/picocli/commands/ArmorCmd.java | 4 +- .../sop/cli/picocli/commands/DearmorCmd.java | 2 +- .../sop/cli/picocli/commands/DecryptCmd.java | 20 ++-- .../sop/cli/picocli/commands/EncryptCmd.java | 14 +-- .../cli/picocli/commands/ExtractCertCmd.java | 4 +- .../cli/picocli/commands/GenerateKeyCmd.java | 8 +- .../cli/picocli/commands/InlineDetachCmd.java | 6 +- .../cli/picocli/commands/InlineSignCmd.java | 10 +- .../cli/picocli/commands/InlineVerifyCmd.java | 10 +- .../sop/cli/picocli/commands/SignCmd.java | 12 +-- .../sop/cli/picocli/commands/VerifyCmd.java | 10 +- .../sop/cli/picocli/commands/VersionCmd.java | 6 +- .../src/main/resources/armor.properties | 5 + .../src/main/resources/armor_de.properties | 5 + .../src/main/resources/dearmor.properties | 4 + .../src/main/resources/dearmor_de.properties | 4 + .../src/main/resources/decrypt.properties | 23 +++++ .../src/main/resources/decrypt_de.properties | 23 +++++ .../main/resources/detached-sign.properties | 12 +++ .../resources/detached-sign_de.properties | 12 +++ .../main/resources/detached-verify.properties | 13 +++ .../resources/detached-verify_de.properties | 13 +++ .../src/main/resources/encrypt.properties | 12 +++ .../src/main/resources/encrypt_de.properties | 12 +++ .../main/resources/extract-cert.properties | 5 + .../main/resources/extract-cert_de.properties | 5 + .../main/resources/generate-key.properties | 8 ++ .../main/resources/generate-key_de.properties | 8 ++ .../main/resources/inline-detach.properties | 6 ++ .../resources/inline-detach_de.properties | 6 ++ .../src/main/resources/inline-sign.properties | 14 +++ .../main/resources/inline-sign_de.properties | 14 +++ .../main/resources/inline-verify.properties | 13 +++ .../resources/inline-verify_de.properties | 13 +++ .../src/main/resources/sop.properties | 95 ++----------------- .../src/main/resources/sop_de.properties | 92 +----------------- .../src/main/resources/version.properties | 6 ++ .../src/main/resources/version_de.properties | 6 ++ version.gradle | 2 +- 39 files changed, 307 insertions(+), 230 deletions(-) create mode 100644 sop-java-picocli/src/main/resources/armor.properties create mode 100644 sop-java-picocli/src/main/resources/armor_de.properties create mode 100644 sop-java-picocli/src/main/resources/dearmor.properties create mode 100644 sop-java-picocli/src/main/resources/dearmor_de.properties create mode 100644 sop-java-picocli/src/main/resources/decrypt.properties create mode 100644 sop-java-picocli/src/main/resources/decrypt_de.properties create mode 100644 sop-java-picocli/src/main/resources/detached-sign.properties create mode 100644 sop-java-picocli/src/main/resources/detached-sign_de.properties create mode 100644 sop-java-picocli/src/main/resources/detached-verify.properties create mode 100644 sop-java-picocli/src/main/resources/detached-verify_de.properties create mode 100644 sop-java-picocli/src/main/resources/encrypt.properties create mode 100644 sop-java-picocli/src/main/resources/encrypt_de.properties create mode 100644 sop-java-picocli/src/main/resources/extract-cert.properties create mode 100644 sop-java-picocli/src/main/resources/extract-cert_de.properties create mode 100644 sop-java-picocli/src/main/resources/generate-key.properties create mode 100644 sop-java-picocli/src/main/resources/generate-key_de.properties create mode 100644 sop-java-picocli/src/main/resources/inline-detach.properties create mode 100644 sop-java-picocli/src/main/resources/inline-detach_de.properties create mode 100644 sop-java-picocli/src/main/resources/inline-sign.properties create mode 100644 sop-java-picocli/src/main/resources/inline-sign_de.properties create mode 100644 sop-java-picocli/src/main/resources/inline-verify.properties create mode 100644 sop-java-picocli/src/main/resources/inline-verify_de.properties create mode 100644 sop-java-picocli/src/main/resources/version.properties create mode 100644 sop-java-picocli/src/main/resources/version_de.properties diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java index 891b974..9d626e5 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java @@ -14,12 +14,12 @@ import sop.operation.Armor; import java.io.IOException; @CommandLine.Command(name = "armor", - resourceBundle = "sop", + resourceBundle = "armor", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ArmorCmd extends AbstractSopCmd { @CommandLine.Option(names = {"--label"}, - descriptionKey = "sop.armor.usage.option.label", + descriptionKey = "usage.option.label", paramLabel = "{auto|sig|key|cert|message}") ArmorLabel label; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java index 1bca4d7..76f1b31 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java @@ -12,7 +12,7 @@ import sop.operation.Dearmor; import java.io.IOException; @CommandLine.Command(name = "dearmor", - resourceBundle = "sop", + resourceBundle = "dearmor", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DearmorCmd extends AbstractSopCmd { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index 5a9b4f5..9f02bb8 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.regex.Pattern; @CommandLine.Command(name = "decrypt", - resourceBundle = "sop", + resourceBundle = "decrypt", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DecryptCmd extends AbstractSopCmd { @@ -40,49 +40,49 @@ public class DecryptCmd extends AbstractSopCmd { @CommandLine.Option( names = {OPT_SESSION_KEY_OUT}, - descriptionKey = "sop.decrypt.usage.option.session_key_out", + descriptionKey = "usage.option.session_key_out", paramLabel = "SESSIONKEY") String sessionKeyOut; @CommandLine.Option( names = {OPT_WITH_SESSION_KEY}, - descriptionKey = "sop.decrypt.usage.option.with_session_key", + descriptionKey = "usage.option.with_session_key", paramLabel = "SESSIONKEY") List withSessionKey = new ArrayList<>(); @CommandLine.Option( names = {OPT_WITH_PASSWORD}, - descriptionKey = "sop.decrypt.usage.option.with_password", + descriptionKey = "usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = {OPT_VERIFY_OUT}, - descriptionKey = "sop.decrypt.usage.option.verify_out", + descriptionKey = "usage.option.verify_out", paramLabel = "VERIFICATIONS") String verifyOut; @CommandLine.Option(names = {OPT_VERIFY_WITH}, - descriptionKey = "sop.decrypt.usage.option.certs", + descriptionKey = "usage.option.certs", paramLabel = "CERT") List certs = new ArrayList<>(); @CommandLine.Option(names = {OPT_NOT_BEFORE}, - descriptionKey = "sop.decrypt.usage.option.not_before", + descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {OPT_NOT_AFTER}, - descriptionKey = "sop.decrypt.usage.option.not_after", + descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @CommandLine.Parameters(index = "0..*", - descriptionKey = "sop.decrypt.usage.param.keys", + descriptionKey = "usage.param.keys", paramLabel = "KEY") List keys = new ArrayList<>(); @CommandLine.Option(names = {OPT_WITH_KEY_PASSWORD}, - descriptionKey = "sop.decrypt.usage.option.with_key_password", + descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index 3af1bb1..f87aba2 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java @@ -17,36 +17,36 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "encrypt", - resourceBundle = "sop", + resourceBundle = "encrypt", exitCodeOnInvalidInput = 37) public class EncryptCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.encrypt.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = {"--as"}, - descriptionKey = "sop.encrypt.usage.option.type", + descriptionKey = "usage.option.type", paramLabel = "{binary|text}") EncryptAs type; @CommandLine.Option(names = "--with-password", - descriptionKey = "sop.encrypt.usage.option.with_password", + descriptionKey = "usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = "--sign-with", - descriptionKey = "sop.encrypt.usage.option.sign_with", + descriptionKey = "usage.option.sign_with", paramLabel = "KEY") List signWith = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "sop.encrypt.usage.option.with_key_password", + descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); - @CommandLine.Parameters(descriptionKey = "sop.encrypt.usage.param.certs", + @CommandLine.Parameters(descriptionKey = "usage.param.certs", index = "0..*", paramLabel = "CERTS") List certs = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java index 960a263..4bb1c18 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java @@ -13,12 +13,12 @@ import sop.exception.SOPGPException; import sop.operation.ExtractCert; @CommandLine.Command(name = "extract-cert", - resourceBundle = "sop", + resourceBundle = "extract-cert", exitCodeOnInvalidInput = 37) public class ExtractCertCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.extract-cert.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index fbf415c..82c0a37 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -15,20 +15,20 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "generate-key", - resourceBundle = "sop", + resourceBundle = "generate-key", exitCodeOnInvalidInput = 37) public class GenerateKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.generate-key.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; - @CommandLine.Parameters(descriptionKey = "sop.generate-key.usage.option.user_id") + @CommandLine.Parameters(descriptionKey = "usage.option.user_id") List userId = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "sop.generate-key.usage.option.with_key_password", + descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") String withKeyPassword; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java index bcd269d..3ed6f7a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java @@ -14,18 +14,18 @@ import java.io.IOException; import java.io.OutputStream; @CommandLine.Command(name = "inline-detach", - resourceBundle = "sop", + resourceBundle = "inline-detach", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class InlineDetachCmd extends AbstractSopCmd { @CommandLine.Option( names = {"--signatures-out"}, - descriptionKey = "sop.inline-detach.usage.option.signatures_out", + descriptionKey = "usage.option.signatures_out", paramLabel = "SIGNATURES") String signaturesOut; @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.inline-detach.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 2cf5ebd..823e332 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -17,26 +17,26 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "inline-sign", - resourceBundle = "sop", + resourceBundle = "inline-sign", exitCodeOnInvalidInput = 37) public class InlineSignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.inline-sign.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = "--as", - descriptionKey = "sop.inline-sign.usage.option.as", + descriptionKey = "usage.option.as", paramLabel = "{binary|text|cleartextsigned}") InlineSignAs type; - @CommandLine.Parameters(descriptionKey = "sop.inline-sign.usage.parameter.keys", + @CommandLine.Parameters(descriptionKey = "usage.parameter.keys", paramLabel = "KEYS") List secretKeyFile = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "sop.inline-sign.usage.option.with_key_password", + descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index 249d8a1..df1f805 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -19,27 +19,27 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "inline-verify", - resourceBundle = "sop", + resourceBundle = "inline-verify", exitCodeOnInvalidInput = 37) public class InlineVerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(arity = "1..*", - descriptionKey = "sop.inline-verify.usage.parameter.certs", + descriptionKey = "usage.parameter.certs", paramLabel = "CERT") List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - descriptionKey = "sop.inline-verify.usage.option.not_before", + descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - descriptionKey = "sop.inline-verify.usage.option.not_after", + descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @CommandLine.Option(names = "--verifications-out", - descriptionKey = "sop.inline-verify.usage.option.verifications_out") + descriptionKey = "usage.option.verifications_out") String verificationsOut; @Override diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java index b6661af..d441e1a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java @@ -20,31 +20,31 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "sign", - resourceBundle = "sop", + resourceBundle = "detached-sign", exitCodeOnInvalidInput = 37) public class SignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "sop.sign.usage.option.armor", + descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = "--as", - descriptionKey = "sop.sign.usage.option.as", + descriptionKey = "usage.option.as", paramLabel = "{binary|text}") SignAs type; - @CommandLine.Parameters(descriptionKey = "sop.sign.usage.parameter.keys", + @CommandLine.Parameters(descriptionKey = "usage.parameter.keys", paramLabel = "KEYS") List secretKeyFile = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "sop.sign.usage.option.with_key_password", + descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); @CommandLine.Option(names = "--micalg-out", - descriptionKey = "sop.sign.usage.option.micalg_out", + descriptionKey = "usage.option.micalg_out", paramLabel = "MICALG") String micAlgOut; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java index e0953b3..d029c52 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java @@ -17,28 +17,28 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "verify", - resourceBundle = "sop", + resourceBundle = "detached-verify", exitCodeOnInvalidInput = 37) public class VerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(index = "0", - descriptionKey = "sop.verify.usage.parameter.signature", + descriptionKey = "usage.parameter.signature", paramLabel = "SIGNATURE") String signature; @CommandLine.Parameters(index = "0..*", arity = "1..*", - descriptionKey = "sop.verify.usage.parameter.certs", + descriptionKey = "usage.parameter.certs", paramLabel = "CERT") List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - descriptionKey = "sop.verify.usage.option.not_before", + descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - descriptionKey = "sop.verify.usage.option.not_after", + descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java index 2dc08db..2cf97e9 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java @@ -9,7 +9,7 @@ import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.operation.Version; -@CommandLine.Command(name = "version", resourceBundle = "sop", +@CommandLine.Command(name = "version", resourceBundle = "version", exitCodeOnInvalidInput = 37) public class VersionCmd extends AbstractSopCmd { @@ -18,11 +18,11 @@ public class VersionCmd extends AbstractSopCmd { static class Exclusive { @CommandLine.Option(names = "--extended", - descriptionKey = "sop.version.usage.option.extended") + descriptionKey = "usage.option.extended") boolean extended; @CommandLine.Option(names = "--backend", - descriptionKey = "sop.version.usage.option.backend") + descriptionKey = "usage.option.backend") boolean backend; } diff --git a/sop-java-picocli/src/main/resources/armor.properties b/sop-java-picocli/src/main/resources/armor.properties new file mode 100644 index 0000000..b963ceb --- /dev/null +++ b/sop-java-picocli/src/main/resources/armor.properties @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Add ASCII Armor to standard input +usage.option.label=Label to be used in the header and tail of the armoring diff --git a/sop-java-picocli/src/main/resources/armor_de.properties b/sop-java-picocli/src/main/resources/armor_de.properties new file mode 100644 index 0000000..1023244 --- /dev/null +++ b/sop-java-picocli/src/main/resources/armor_de.properties @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Schütze Standard-Eingabe mit ASCII Armor +usage.option.label=Label für Kopf- und Fußzeile der ASCII Armor diff --git a/sop-java-picocli/src/main/resources/dearmor.properties b/sop-java-picocli/src/main/resources/dearmor.properties new file mode 100644 index 0000000..fc2f119 --- /dev/null +++ b/sop-java-picocli/src/main/resources/dearmor.properties @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Remove ASCII Armor from standard input diff --git a/sop-java-picocli/src/main/resources/dearmor_de.properties b/sop-java-picocli/src/main/resources/dearmor_de.properties new file mode 100644 index 0000000..186540e --- /dev/null +++ b/sop-java-picocli/src/main/resources/dearmor_de.properties @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Entferne ASCII Armor von Standard-Eingabe diff --git a/sop-java-picocli/src/main/resources/decrypt.properties b/sop-java-picocli/src/main/resources/decrypt.properties new file mode 100644 index 0000000..ddf20eb --- /dev/null +++ b/sop-java-picocli/src/main/resources/decrypt.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Decrypt a message from standard input +usage.option.session_key_out=Can be used to learn the session key on successful decryption +usage.option.with_session_key.0=Symmetric message key (session key). +usage.option.with_session_key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. +usage.option.with_session_key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +usage.option.with_password.0=Symmetric passphrase to decrypt the message with. +usage.option.with_password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". +usage.option.with_password_2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +usage.option.verify_out=Emits signature verification status to the designated output +usage.option.certs=Certificates for signature verification +usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_before.1=Reject signatures with a creation date not in range. +usage.option.not_before.2=Defaults to beginning of time ('-'). +usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_after.1=Reject signatures with a creation date not in range. +usage.option.not_after.2=Defaults to current system time ('now'). +usage.option.not_after.3=Accepts special value '-' for end of time. +usage.option.with_key_password.0=Passphrase to unlock the secret key(s). +usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +usage.param.keys=Secret keys to attempt decryption with diff --git a/sop-java-picocli/src/main/resources/decrypt_de.properties b/sop-java-picocli/src/main/resources/decrypt_de.properties new file mode 100644 index 0000000..42d55e6 --- /dev/null +++ b/sop-java-picocli/src/main/resources/decrypt_de.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Entschlüssle eine Nachricht von Standard-Eingabe +usage.option.session_key_out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung +usage.option.with_session_key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). +usage.option.with_session_key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. +usage.option.with_session_key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.option.with_password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. +usage.option.with_password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. +usage.option.with_password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.option.verify_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe +usage.option.certs=Zertifikate zur Signaturprüfung +usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). +usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel +usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.param.keys=Private Schlüssel zum Entschlüsseln der Nachricht diff --git a/sop-java-picocli/src/main/resources/detached-sign.properties b/sop-java-picocli/src/main/resources/detached-sign.properties new file mode 100644 index 0000000..9238b35 --- /dev/null +++ b/sop-java-picocli/src/main/resources/detached-sign.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Create a detached signature on the data from standard input +usage.option.armor=ASCII armor the output +usage.option.as.0=Specify the output format of the signed message +usage.option.as.1=Defaults to 'binary'. +usage-option.as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. +usage.option.with_key_password.0=Passphrase to unlock the secret key(s). +usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +usage.option.micalg_out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +usage.parameter.keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/detached-sign_de.properties b/sop-java-picocli/src/main/resources/detached-sign_de.properties new file mode 100644 index 0000000..abc70b4 --- /dev/null +++ b/sop-java-picocli/src/main/resources/detached-sign_de.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe +usage.option.armor=Schütze Ausgabe mit ASCII Armor +usage.option.as.0=Bestimme Signaturformat der Nachricht. +usage.option.as.1=Standardmäßig: 'binary'. +usage-option.as.2=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. +usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels +usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.option.micalg_out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. +usage.parameter.keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/detached-verify.properties b/sop-java-picocli/src/main/resources/detached-verify.properties new file mode 100644 index 0000000..a4ddd33 --- /dev/null +++ b/sop-java-picocli/src/main/resources/detached-verify.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verify a detached signature over the data from standard input +usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_before.1=Reject signatures with a creation date not in range. +usage.option.not_before.2=Defaults to beginning of time ("-"). +usage.option.not_after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_after.2=Reject signatures with a creation date not in range. +usage.option.not_after.3=Defaults to current system time ("now").\ +usage.option.not_after.4 = Accepts special value "-" for end of time. +usage.parameter.signature=Detached signature +usage.parameter.certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/detached-verify_de.properties b/sop-java-picocli/src/main/resources/detached-verify_de.properties new file mode 100644 index 0000000..f378ad5 --- /dev/null +++ b/sop-java-picocli/src/main/resources/detached-verify_de.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe +usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). +usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +usage.parameter.signature=Abgetrennte Signatur +usage.parameter.certs=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/encrypt.properties b/sop-java-picocli/src/main/resources/encrypt.properties new file mode 100644 index 0000000..f9b57e9 --- /dev/null +++ b/sop-java-picocli/src/main/resources/encrypt.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Encrypt a message from standard input +usage.option.armor=ASCII armor the output +usage.option.type=Type of the input data. Defaults to 'binary' +usage.option.with_password.0=Encrypt the message with a password. +usage.option.with_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +usage.option.sign_with=Sign the output with a private key +usage.option.with_key_password.0=Passphrase to unlock the secret key(s). +usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +usage.param.certs=Certificates the message gets encrypted to diff --git a/sop-java-picocli/src/main/resources/encrypt_de.properties b/sop-java-picocli/src/main/resources/encrypt_de.properties new file mode 100644 index 0000000..555f7c1 --- /dev/null +++ b/sop-java-picocli/src/main/resources/encrypt_de.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verschlüssle eine Nachricht von Standard-Eingabe +usage.option.armor=Schütze Ausgabe mit ASCII Armor +usage.option.type=Format der Nachricht. Standardmäßig 'binary' +usage.option.with_password.0=Verschlüssle die Nachricht mit einem Passwort +usage.option.with_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.option.sign_with=Signiere die Nachricht mit einem privaten Schlüssel +usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel +usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.param.certs=Zertifikate für die die Nachricht verschlüsselt werden soll diff --git a/sop-java-picocli/src/main/resources/extract-cert.properties b/sop-java-picocli/src/main/resources/extract-cert.properties new file mode 100644 index 0000000..50f090e --- /dev/null +++ b/sop-java-picocli/src/main/resources/extract-cert.properties @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Extract a public key certificate from a secret key from standard input +usage.option.armor=ASCII armor the output diff --git a/sop-java-picocli/src/main/resources/extract-cert_de.properties b/sop-java-picocli/src/main/resources/extract-cert_de.properties new file mode 100644 index 0000000..f1607c3 --- /dev/null +++ b/sop-java-picocli/src/main/resources/extract-cert_de.properties @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe +usage.option.armor=Schütze Ausgabe mit ASCII Armor diff --git a/sop-java-picocli/src/main/resources/generate-key.properties b/sop-java-picocli/src/main/resources/generate-key.properties new file mode 100644 index 0000000..822a23e --- /dev/null +++ b/sop-java-picocli/src/main/resources/generate-key.properties @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generate a secret key +usage.option.armor=ASCII armor the output +usage.option.user_id=User-ID, e.g. "Alice " +usage.option.with_key_password.0=Password to protect the private key with +usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/generate-key_de.properties b/sop-java-picocli/src/main/resources/generate-key_de.properties new file mode 100644 index 0000000..a698593 --- /dev/null +++ b/sop-java-picocli/src/main/resources/generate-key_de.properties @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generiere einen privaten Schlüssel +usage.option.armor=Schütze Ausgabe mit ASCII Armor +usage.option.user_id=Nutzer-ID, z.B.. "Alice " +usage.option.with_key_password.0=Passwort zum Schutz des privaten Schlüssels +usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/inline-detach.properties b/sop-java-picocli/src/main/resources/inline-detach.properties new file mode 100644 index 0000000..0f03363 --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-detach.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Split signatures from a clearsigned message +usage.option.armor=ASCII armor the output +usage.option.signatures_out=Destination to which a detached signatures block will be written diff --git a/sop-java-picocli/src/main/resources/inline-detach_de.properties b/sop-java-picocli/src/main/resources/inline-detach_de.properties new file mode 100644 index 0000000..3be5f5a --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-detach_de.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Trenne Signaturen von Klartext-signierter Nachricht +usage.option.armor=Schütze Ausgabe mit ASCII Armor +usage.option.signatures_out=Schreibe abgetrennte Signaturen in Ausgabe diff --git a/sop-java-picocli/src/main/resources/inline-sign.properties b/sop-java-picocli/src/main/resources/inline-sign.properties new file mode 100644 index 0000000..79a2dab --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-sign.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Create an inline-signed message from data on standard input +usage.option.armor=ASCII armor the output +usage.option.as.0=Specify the signature format of the signed message +usage.option.as.1='text' and 'binary' will produce inline-signed messages. +usage.option.as.2='cleartextsigned' will make use of the cleartext signature framework. +usage.option.as.3=Defaults to 'binary'. +usage.option.as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53. +usage.option.with_key_password.0=Passphrase to unlock the secret key(s). +usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +usage.option.micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +usage.parameter.keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/inline-sign_de.properties b/sop-java-picocli/src/main/resources/inline-sign_de.properties new file mode 100644 index 0000000..dc85ecd --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-sign_de.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen +usage.option.armor=Schütze Ausgabe mit ASCII Armor +usage.option.as.0=Bestimme Signaturformat der Nachricht. +usage.option.as.1='text' und 'binary' resultieren in eingebettete Signaturen. +usage.option.as.2='cleartextsigned' wird die Nachricht Klartext-signieren. +usage.option.as.3=Standardmäßig: 'binary'. +usage.option.as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. +usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels +usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +usage.option.micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. +usage.parameter.keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/inline-verify.properties b/sop-java-picocli/src/main/resources/inline-verify.properties new file mode 100644 index 0000000..8552671 --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-verify.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verify inline-signed data from standard input +usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_before.1=Reject signatures with a creation date not in range. +usage.option.not_before.2=Defaults to beginning of time ("-"). +usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +usage.option.not_after.1=Reject signatures with a creation date not in range. +usage.option.not_after.2=Defaults to current system time ("now"). +usage.option.not_after.3=Accepts special value "-" for end of time. +usage.option.verifications_out=File to write details over successful verifications to +usage.parameter.certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/inline-verify_de.properties b/sop-java-picocli/src/main/resources/inline-verify_de.properties new file mode 100644 index 0000000..ecc4a8e --- /dev/null +++ b/sop-java-picocli/src/main/resources/inline-verify_de.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Prüfe eingebettete Signaturen einer Nachricht von Standard-Eingabe +usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). +usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +usage.option.verifications_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe +usage.parameter.certs=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/sop.properties b/sop-java-picocli/src/main/resources/sop.properties index 78b9225..3962861 100644 --- a/sop-java-picocli/src/main/resources/sop.properties +++ b/sop-java-picocli/src/main/resources/sop.properties @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop -sop.usage.header=Stateless OpenPGP Protocol - +usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by picocli%n + sop.locale=Locale for description texts # Generic usage.synopsisHeading=Usage:\u0020 @@ -30,93 +30,10 @@ usage.exitCodeList.15=69:Unsupported subcommand usage.exitCodeList.16=71:Unsupported special prefix (e.g. \"@env/@fd\") of indirect parameter usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator already exists) usage.exitCodeList.18=79:Key is not signing capable -# Subcommands -sop.armor.usage.header=Add ASCII Armor to standard input -sop.armor.usage.option.label=Label to be used in the header and tail of the armoring -sop.dearmor.usage.header=Remove ASCII Armor from standard input -sop.decrypt.usage.header=Decrypt a message from standard input -sop.decrypt.usage.option.session_key_out=Can be used to learn the session key on successful decryption -sop.decrypt.usage.option.with_session_key.0=Symmetric message key (session key). -sop.decrypt.usage.option.with_session_key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. -sop.decrypt.usage.option.with_session_key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -sop.decrypt.usage.option.with_password.0=Symmetric passphrase to decrypt the message with. -sop.decrypt.usage.option.with_password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". -sop.decrypt.usage.option.with_password_2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -sop.decrypt.usage.option.verify_out=Emits signature verification status to the designated output -sop.decrypt.usage.option.certs=Certificates for signature verification -sop.decrypt.usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.decrypt.usage.option.not_before.1=Reject signatures with a creation date not in range. -sop.decrypt.usage.option.not_before.2=Defaults to beginning of time ('-'). -sop.decrypt.usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.decrypt.usage.option.not_after.1=Reject signatures with a creation date not in range. -sop.decrypt.usage.option.not_after.2=Defaults to current system time ('now'). -sop.decrypt.usage.option.not_after.3=Accepts special value '-' for end of time. -sop.decrypt.usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -sop.decrypt.usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -sop.decrypt.usage.param.keys=Secret keys to attempt decryption with -sop.encrypt.usage.header=Encrypt a message from standard input -sop.encrypt.usage.option.armor=ASCII armor the output -sop.encrypt.usage.option.type=Type of the input data. Defaults to 'binary' -sop.encrypt.usage.option.with_password.0=Encrypt the message with a password. -sop.encrypt.usage.option.with_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -sop.encrypt.usage.option.sign_with=Sign the output with a private key -sop.encrypt.usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -sop.encrypt.usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -sop.encrypt.usage.param.certs=Certificates the message gets encrypted to -sop.extract-cert.usage.header=Extract a public key certificate from a secret key from standard input -sop.extract-cert.usage.option.armor=ASCII armor the output -sop.generate-key.usage.header=Generate a secret key -sop.generate-key.usage.option.armor=ASCII armor the output -sop.generate-key.usage.option.user_id=User-ID, e.g. "Alice " -sop.generate-key.usage.option.with_key_password.0=Password to protect the private key with -sop.generate-key.usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -sop.inline-detach.usage.header=Split signatures from a clearsigned message -sop.inline-detach.usage.option.armor=ASCII armor the output -sop.inline-detach.usage.option.signatures_out=Destination to which a detached signatures block will be written -sop.inline-sign.usage.header=Create an inline-signed message from data on standard input -sop.inline-sign.usage.option.armor=ASCII armor the output -sop.inline-sign.usage.option.as.0=Specify the signature format of the signed message -sop.inline-sign.usage.option.as.1='text' and 'binary' will produce inline-signed messages. -sop.inline-sign.usage.option.as.2='cleartextsigned' will make use of the cleartext signature framework. -sop.inline-sign.usage.option.as.3=Defaults to 'binary'. -sop.inline-sign.usage.option.as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53. -sop.inline-sign.usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -sop.inline-sign.usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -sop.inline-sign.usage.option.micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -sop.inline-sign.usage.parameter.keys=Secret keys used for signing -sop.inline-verify.usage.header=Verify inline-signed data from standard input -sop.inline-verify.usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.inline-verify.usage.option.not_before.1=Reject signatures with a creation date not in range. -sop.inline-verify.usage.option.not_before.2=Defaults to beginning of time ("-"). -sop.inline-verify.usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.inline-verify.usage.option.not_after.1=Reject signatures with a creation date not in range. -sop.inline-verify.usage.option.not_after.2=Defaults to current system time ("now"). -sop.inline-verify.usage.option.not_after.3=Accepts special value "-" for end of time. -sop.inline-verify.usage.option.verifications_out=File to write details over successful verifications to -sop.inline-verify.usage.parameter.certs=Public key certificates for signature verification -sop.sign.usage.header=Create a detached signature on the data from standard input -sop.sign.usage.option.armor=ASCII armor the output -sop.sign.usage.option.as.0=Specify the output format of the signed message -sop.sign.usage.option.as.1=Defaults to 'binary'. -sop.sign.usage-option.as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. -sop.sign.usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -sop.sign.usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -sop.sign.usage.option.micalg_out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -sop.sign.usage.parameter.keys=Secret keys used for signing -sop.verify.usage.header=Verify a detached signature over the data from standard input -sop.verify.usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.verify.usage.option.not_before.1=Reject signatures with a creation date not in range. -sop.verify.usage.option.not_before.2=Defaults to beginning of time ("-"). -sop.verify.usage.option.not_after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -sop.verify.usage.option.not_after.2=Reject signatures with a creation date not in range. -sop.verify.usage.option.not_after.3=Defaults to current system time ("now").\ -sop.verify.usage.option.not_after.4 = Accepts special value "-" for end of time. -sop.verify.usage.parameter.signature=Detached signature -sop.verify.usage.parameter.certs=Public key certificates for signature verification -sop.version.usage.header=Display version information about the tool -sop.version.usage.option.extended=Print an extended version string -sop.version.usage.option.backend=Print information about the cryptographic backend -sop.help.usage.header=Display usage information for the specified subcommand + +help.usage.header=Display usage information for the specified subcommand + +## SHARED RESOURCES ## Malformed Input sop.error.input.malformed_session_key=Session keys are expected in the format 'ALGONUM:HEXKEY'. sop.error.input.not_a_private_key=Input '%s' does not contain an OpenPGP private key. diff --git a/sop-java-picocli/src/main/resources/sop_de.properties b/sop-java-picocli/src/main/resources/sop_de.properties index 1b59021..78ccc39 100644 --- a/sop-java-picocli/src/main/resources/sop_de.properties +++ b/sop-java-picocli/src/main/resources/sop_de.properties @@ -2,8 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop -sop.usage.header=Stateless OpenPGP Protocol +usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by Picocli%n + sop.locale=Gebietsschema für Beschreibungstexte # Generic usage.synopsisHeading=Aufruf:\u0020 @@ -29,93 +30,10 @@ usage.exitCodeList.15=69:Nicht unterst usage.exitCodeList.16=71:Nicht unterstützter Spezialprefix (z.B.. "@env/@fd") von indirektem Parameter usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner entspricht, existiert bereits) usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren -# Subcommands -sop.armor.usage.header=Schütze Standard-Eingabe mit ASCII Armor -sop.armor.usage.option.label=Label für Kopf- und Fußzeile der ASCII Armor -sop.dearmor.usage.header=Entferne ASCII Armor von Standard-Eingabe -sop.decrypt.usage.header=Entschlüssle eine Nachricht von Standard-Eingabe -sop.decrypt.usage.option.session_key_out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung -sop.decrypt.usage.option.with_session_key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). -sop.decrypt.usage.option.with_session_key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. -sop.decrypt.usage.option.with_session_key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.decrypt.usage.option.with_password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. -sop.decrypt.usage.option.with_password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. -sop.decrypt.usage.option.with_password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.decrypt.usage.option.verify_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -sop.decrypt.usage.option.certs=Zertifikate zur Signaturprüfung -sop.decrypt.usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.decrypt.usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.decrypt.usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -sop.decrypt.usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.decrypt.usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.decrypt.usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -sop.decrypt.usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -sop.decrypt.usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel -sop.decrypt.usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.decrypt.usage.param.keys=Private Schlüssel zum Entschlüsseln der Nachricht -sop.encrypt.usage.header=Verschlüssle eine Nachricht von Standard-Eingabe -sop.encrypt.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.encrypt.usage.option.type=Format der Nachricht. Standardmäßig 'binary' -sop.encrypt.usage.option.with_password.0=Verschlüssle die Nachricht mit einem Passwort -sop.encrypt.usage.option.with_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.encrypt.usage.option.sign_with=Signiere die Nachricht mit einem privaten Schlüssel -sop.encrypt.usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel -sop.encrypt.usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.encrypt.usage.param.certs=Zertifikate für die die Nachricht verschlüsselt werden soll -sop.extract-cert.usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe -sop.extract-cert.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.generate-key.usage.header=Generiere einen privaten Schlüssel -sop.generate-key.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.generate-key.usage.option.user_id=Nutzer-ID, z.B.. "Alice " -sop.generate-key.usage.option.with_key_password.0=Passwort zum Schutz des privaten Schlüssels -sop.generate-key.usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.inline-detach.usage.header=Trenne Signaturen von Klartext-signierter Nachricht -sop.inline-detach.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.inline-detach.usage.option.signatures_out=Schreibe abgetrennte Signaturen in Ausgabe -sop.inline-sign.usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen -sop.inline-sign.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.inline-sign.usage.option.as.0=Bestimme Signaturformat der Nachricht. -sop.inline-sign.usage.option.as.1='text' und 'binary' resultieren in eingebettete Signaturen. -sop.inline-sign.usage.option.as.2='cleartextsigned' wird die Nachricht Klartext-signieren. -sop.inline-sign.usage.option.as.3=Standardmäßig: 'binary'. -sop.inline-sign.usage.option.as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. -sop.inline-sign.usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels -sop.inline-sign.usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.inline-sign.usage.option.micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -sop.inline-sign.usage.parameter.keys=Private Signaturschlüssel -sop.inline-verify.usage.header=Prüfe eingebettete Signaturen einer Nachricht von Standard-Eingabe -sop.inline-verify.usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.inline-verify.usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.inline-verify.usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -sop.inline-verify.usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.inline-verify.usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.inline-verify.usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -sop.inline-verify.usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -sop.inline-verify.usage.option.verifications_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -sop.inline-verify.usage.parameter.certs=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung -sop.sign.usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe -sop.sign.usage.option.armor=Schütze Ausgabe mit ASCII Armor -sop.sign.usage.option.as.0=Bestimme Signaturformat der Nachricht. -sop.sign.usage.option.as.1=Standardmäßig: 'binary'. -sop.sign.usage-option.as.2=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. -sop.sign.usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels -sop.sign.usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -sop.sign.usage.option.micalg_out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -sop.sign.usage.parameter.keys=Private Signaturschlüssel -sop.verify.usage.header=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe -sop.verify.usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.verify.usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.verify.usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -sop.verify.usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -sop.verify.usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -sop.verify.usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -sop.verify.usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -sop.verify.usage.parameter.signature=Abgetrennte Signatur -sop.verify.usage.parameter.certs=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung -sop.version.usage.header=Zeige Versionsinformationen über das Programm -sop.version.usage.option.extended=Gebe erweiterte Versionsinformationen aus -sop.version.usage.option.backend=Gebe Informationen über das kryptografische Backend aus + sop.help.usage.header=Zeige Nutzungshilfen für den angegebenen Unterbefehl an + +## SHARED RESOURCES ## Malformed Input sop.error.input.malformed_session_key=Nachrichtenschlüssel werden im folgenden Format erwartet: 'ALGONUM:HEXKEY' sop.error.input.not_a_private_key=Eingabe '%s' enthält keinen privaten OpenPGP Schlüssel. diff --git a/sop-java-picocli/src/main/resources/version.properties b/sop-java-picocli/src/main/resources/version.properties new file mode 100644 index 0000000..4bf6457 --- /dev/null +++ b/sop-java-picocli/src/main/resources/version.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Display version information about the tool +usage.option.extended=Print an extended version string +usage.option.backend=Print information about the cryptographic backend diff --git a/sop-java-picocli/src/main/resources/version_de.properties b/sop-java-picocli/src/main/resources/version_de.properties new file mode 100644 index 0000000..18250ea --- /dev/null +++ b/sop-java-picocli/src/main/resources/version_de.properties @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Zeige Versionsinformationen über das Programm +usage.option.extended=Gebe erweiterte Versionsinformationen aus +usage.option.backend=Gebe Informationen über das kryptografische Backend aus diff --git a/version.gradle b/version.gradle index 9aa5316..e94f79d 100644 --- a/version.gradle +++ b/version.gradle @@ -10,7 +10,7 @@ allprojects { javaSourceCompatibility = 1.8 junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - picocliVersion = '4.6.3' + picocliVersion = '4.7.0' mockitoVersion = '4.5.1' jsrVersion = '3.0.2' } From 7de94f1815684a7f5f081af09daf3647994e5958 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Jul 2022 14:30:48 +0200 Subject: [PATCH 006/444] Set resource bundle for help command --- .../src/main/java/sop/cli/picocli/SopCLI.java | 13 +++++++++---- sop-java-picocli/src/main/resources/help.properties | 4 ++++ .../src/main/resources/help_de.properties | 4 ++++ sop-java-picocli/src/main/resources/sop.properties | 2 -- .../src/main/resources/sop_de.properties | 2 -- 5 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 sop-java-picocli/src/main/resources/help.properties create mode 100644 sop-java-picocli/src/main/resources/help_de.properties diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 2ce2e65..b823a8f 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -64,15 +64,20 @@ public class SopCLI { // Set locale new CommandLine(new InitLocale()).parseArgs(args); + // get error message bundle cliMsg = ResourceBundle.getBundle("sop"); // Prepare CLI CommandLine cmd = new CommandLine(SopCLI.class); - // Hide generate-completion command - CommandLine gen = cmd.getSubcommands().get("generate-completion"); - gen.getCommandSpec().usageMessage().hidden(true); - cmd.setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) + // explicitly set help command resource bundle + cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("help")); + + // Hide generate-completion command + cmd.getSubcommands().get("generate-completion").getCommandSpec().usageMessage().hidden(true); + + cmd.setCommandName(EXECUTABLE_NAME) + .setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) .setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper()) .setCaseInsensitiveEnumValuesAllowed(true); diff --git a/sop-java-picocli/src/main/resources/help.properties b/sop-java-picocli/src/main/resources/help.properties new file mode 100644 index 0000000..518f793 --- /dev/null +++ b/sop-java-picocli/src/main/resources/help.properties @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Display usage information for the specified subcommand diff --git a/sop-java-picocli/src/main/resources/help_de.properties b/sop-java-picocli/src/main/resources/help_de.properties new file mode 100644 index 0000000..d933a4a --- /dev/null +++ b/sop-java-picocli/src/main/resources/help_de.properties @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Zeige Nutzungshilfen für den angegebenen Unterbefehl an diff --git a/sop-java-picocli/src/main/resources/sop.properties b/sop-java-picocli/src/main/resources/sop.properties index 3962861..4fd31a2 100644 --- a/sop-java-picocli/src/main/resources/sop.properties +++ b/sop-java-picocli/src/main/resources/sop.properties @@ -31,8 +31,6 @@ usage.exitCodeList.16=71:Unsupported special prefix (e.g. \"@env/@fd\") of indir usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator already exists) usage.exitCodeList.18=79:Key is not signing capable -help.usage.header=Display usage information for the specified subcommand - ## SHARED RESOURCES ## Malformed Input sop.error.input.malformed_session_key=Session keys are expected in the format 'ALGONUM:HEXKEY'. diff --git a/sop-java-picocli/src/main/resources/sop_de.properties b/sop-java-picocli/src/main/resources/sop_de.properties index 78ccc39..d2542de 100644 --- a/sop-java-picocli/src/main/resources/sop_de.properties +++ b/sop-java-picocli/src/main/resources/sop_de.properties @@ -31,8 +31,6 @@ usage.exitCodeList.16=71:Nicht unterst usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner entspricht, existiert bereits) usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren -sop.help.usage.header=Zeige Nutzungshilfen für den angegebenen Unterbefehl an - ## SHARED RESOURCES ## Malformed Input sop.error.input.malformed_session_key=Nachrichtenschlüssel werden im folgenden Format erwartet: 'ALGONUM:HEXKEY' From 8c581d459a2427507c225ff6148dae11bcc35527 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Jul 2022 21:40:24 +0200 Subject: [PATCH 007/444] WIP: Add task for generating man pages --- sop-java-picocli/build.gradle | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index e78bca9..9cbd1ef 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -4,6 +4,7 @@ plugins { id 'application' + id 'org.asciidoctor.jvm.convert' version '3.1.0' } dependencies { @@ -23,6 +24,7 @@ dependencies { // CLI implementation "info.picocli:picocli:$picocliVersion" + annotationProcessor "info.picocli:picocli-codegen:$picocliVersion" // @Nonnull, @Nullable... implementation "com.google.code.findbugs:jsr305:$jsrVersion" @@ -49,3 +51,24 @@ jar { exclude "META-INF/*.RSA" } } + +task generateManpageAsciiDoc(type: JavaExec) { + dependsOn(classes) + group = "Documentation" + description = "Generate AsciiDoc manpage" + classpath(configurations.annotationProcessor, sourceSets.main.runtimeClasspath) + systemProperty("user.language", "en") + main 'picocli.codegen.docgen.manpage.ManPageGenerator' + args mainClassName, "--outdir=${project.buildDir}/generated-picocli-docs", "-v" //, "--template-dir=src/docs/mantemplates" +} + +apply plugin: 'org.asciidoctor.jvm.convert' +asciidoctor { + dependsOn(generateManpageAsciiDoc) + sourceDir = file("${project.buildDir}/generated-picocli-docs") + outputDir = file("${project.buildDir}/docs") + logDocuments = true + outputOptions { + backends = ['manpage', 'html5'] + } +} \ No newline at end of file From 5fb1146dfbc02e65b45b468950fe6e50e626e6c3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 16:03:46 +0200 Subject: [PATCH 008/444] Add .woodpecker/ for codeberg-ci --- .reuse/dep5 | 5 +++++ .woodpecker/.build.yml | 7 +++++++ .woodpecker/.reuse.yml | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 .woodpecker/.build.yml create mode 100644 .woodpecker/.reuse.yml diff --git a/.reuse/dep5 b/.reuse/dep5 index b8bb6be..9dee06a 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -13,3 +13,8 @@ Source: https://pgpainless.org Files: gradle* Copyright: 2015 the original author or authors. License: Apache-2.0 + +# Woodpecker build files +Files: .woodpecker/* +Copyright: 2022 the original author or authors. +License: Apache-2.0 \ No newline at end of file diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml new file mode 100644 index 0000000..a59acd5 --- /dev/null +++ b/.woodpecker/.build.yml @@ -0,0 +1,7 @@ +pipeline: + + run: + image: gradle:7.5-jdk8 + commands: + - gradle --no-daemon assemble test check javadocAll jacocoRootReport coveralls + secrets: [COVERALLS_REPO_TOKEN] diff --git a/.woodpecker/.reuse.yml b/.woodpecker/.reuse.yml new file mode 100644 index 0000000..fb6a421 --- /dev/null +++ b/.woodpecker/.reuse.yml @@ -0,0 +1,5 @@ +pipeline: + reuse: + image: fsfe/reuse:latest + commands: + - reuse lint \ No newline at end of file From 1e9b1c77d3940dcc40eb2326a223757f9ac50081 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 16:09:41 +0200 Subject: [PATCH 009/444] Clean up woodpecker scripts --- .woodpecker/.build.yml | 8 ++++++-- .woodpecker/.reuse.yml | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index a59acd5..4f10968 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -1,7 +1,11 @@ pipeline: - run: image: gradle:7.5-jdk8 commands: - - gradle --no-daemon assemble test check javadocAll jacocoRootReport coveralls + # Code works + - gradle test + # Code is clean + - gradle check javadocAll + # Code has coverage + - gradle jacocoRootReport coveralls secrets: [COVERALLS_REPO_TOKEN] diff --git a/.woodpecker/.reuse.yml b/.woodpecker/.reuse.yml index fb6a421..58f17e6 100644 --- a/.woodpecker/.reuse.yml +++ b/.woodpecker/.reuse.yml @@ -1,3 +1,5 @@ +# Code is licensed properly +# See https://reuse.software/ pipeline: reuse: image: fsfe/reuse:latest From ed150e51a878538214f0feeddd2eb2a988cd2dc1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 16:40:18 +0200 Subject: [PATCH 010/444] Woodpecker: Attempt to fix branch name --- .woodpecker/.build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index 4f10968..f504b44 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -2,6 +2,7 @@ pipeline: run: image: gradle:7.5-jdk8 commands: + - git checkout $CI_COMMIT_BRANCH # Code works - gradle test # Code is clean From a651abb44e087c1470636314a802fe69c51eec10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:09:39 +0200 Subject: [PATCH 011/444] Replace badges for Travis-CI with Codeberg-CI and fix coverage badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d94bfd1..bdb0b72 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java -[![Travis (.com)](https://travis-ci.com/pgpainless/sop-java.svg?branch=master)](https://travis-ci.com/pgpainless/sop-java) +[![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) [![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/sop-java)](https://search.maven.org/artifact/org.pgpainless/sop-java) [![Spec Revision: 4](https://img.shields.io/badge/Spec%20Revision-4-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/04/) -[![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=master)](https://coveralls.io/github/pgpainless/sop-java?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) The [Stateless OpenPGP Protocol](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) specification From 01dbaab598abdbccdc2559838eee6438f5f02508 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:41:03 +0200 Subject: [PATCH 012/444] Delete .travis.yml --- .travis.yml | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9deccee..0000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 - -language: java -dist: bionic -jdk: - - openjdk8 - - openjdk11 - -services: - - docker - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.m2 - -before_install: - - export GRADLE_VERSION=6.2 - - wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip - - unzip -q gradle-${GRADLE_VERSION}-all.zip - - rm gradle-${GRADLE_VERSION}-all.zip - - sudo mv gradle-${GRADLE_VERSION} /usr/local/bin/ - - export PATH="/usr/local/bin/gradle-${GRADLE_VERSION}/bin:$PATH" - - docker pull fsfe/reuse:latest - - docker run -v ${TRAVIS_BUILD_DIR}:/data fsfe/reuse:latest lint - -install: gradle assemble --stacktrace - -# Run the test suite and also install the artifacts in the local maven -# archive to additionaly test if artifact creation is -# functional. Which hasn't always be the case in the past, see -# 90cbcaebc7a89f4f771f733a33ac9f389df85be2 -# Also run javadocAll to ensure it works. -script: - - | - JAVAC_MAJOR_VERSION=$(javac -version | sed -E 's/javac ([[:digit:]]+).*/\1/') - GRADLE_TASKS=() - GRADLE_TASKS+=(check) - if [[ ${JAVAC_MAJOR_VERSION} -ge 11 ]]; then - GRADLE_TASKS+=(javadocAll) - fi - gradle ${GRADLE_TASKS[@]} --stacktrace - -after_success: - - JAVAC_VERSION=$((javac -version) 2>&1) - # Only run jacocoRootReport in the Java 8 build - - if [[ "$JAVAC_VERSION" = javac\ 1.8.* ]]; then gradle jacocoRootReport coveralls; fi From b7c1b4f1a1dcc921e95c9ab90b27136617f48e38 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 17:44:22 +0200 Subject: [PATCH 013/444] Remove unused methods from Print class --- .../src/main/java/sop/cli/picocli/Print.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java b/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java index d6474e1..9e81b66 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java @@ -6,18 +6,6 @@ package sop.cli.picocli; public class Print { - public static void errln(String string) { - // CHECKSTYLE:OFF - System.err.println(string); - // CHECKSTYLE:ON - } - - public static void trace(Throwable e) { - // CHECKSTYLE:OFF - e.printStackTrace(); - // CHECKSTYLE:ON - } - public static void outln(String string) { // CHECKSTYLE:OFF System.out.println(string); From fe729c4eb8788bd84f0f78d78a887b3453e4e3fc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 18:21:41 +0200 Subject: [PATCH 014/444] Add AbstractSopCmdTest to test getInput method --- .../picocli/commands/AbstractSopCmdTest.java | 126 ++++++++++++++++++ .../TestEnvironmentVariableResolver.java | 22 +++ 2 files changed, 148 insertions(+) create mode 100644 sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java create mode 100644 sop-java-picocli/src/test/java/sop/cli/picocli/commands/TestEnvironmentVariableResolver.java diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java new file mode 100644 index 0000000..7038ad7 --- /dev/null +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; +import sop.cli.picocli.TestFileUtil; +import sop.exception.SOPGPException; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AbstractSopCmdTest { + + private static AbstractSopCmd abstractCmd; + private static final TestEnvironmentVariableResolver resolver = new TestEnvironmentVariableResolver(); + + @BeforeAll + public static void setup() { + abstractCmd = new VersionCmd(); // Use Version as representative command + abstractCmd.setEnvironmentVariableResolver(resolver); + } + + @Test + public void setEnvironmentVariableResolver_nullNPE() { + assertThrows(NullPointerException.class, () -> abstractCmd.setEnvironmentVariableResolver(null)); + } + + @Test + public void getInput_NullInvalid() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput(null)); + } + + @Test + public void getInput_EmptyInvalid() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput("")); + } + + @Test + public void getInput_BlankInvalid() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput(" ")); + } + + @Test + public void getInput_envNotSetIllegalArg() { + String envName = "@ENV:IS_NOT_SET"; + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput(envName)); + } + + @Test + public void getInput_envEmptyIllegalArg() { + String envName = "@ENV:IS_EMPTY"; + resolver.addEnvironmentVariable("IS_EMPTY", ""); + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput(envName)); + } + + @Test + public void getInput_fromEnv() throws IOException { + resolver.addEnvironmentVariable("FOO", "BAR"); + InputStream input = abstractCmd.getInput("@ENV:FOO"); + String string = readStringFromInputStream(input); + assertEquals("BAR", string); + } + + private static String readStringFromInputStream(InputStream input) + throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + byte[] buf = new byte[512]; + int read; + while ((read = input.read(buf)) > 0) { + output.write(buf, 0, read); + } + return output.toString(); + } + + @Test + public void getInput_envClashesWithExistingFile() throws IOException { + String env = "@ENV:existing.file"; + File tempFile = new File(env); + if (!tempFile.createNewFile()) { + throw new TestAbortedException("Cannot create temporary file " + tempFile.getAbsolutePath()); + } + tempFile.deleteOnExit(); + + resolver.addEnvironmentVariable("existing.file", "foo_bar"); + + assertThrows(SOPGPException.AmbiguousInput.class, () -> abstractCmd.getInput(env)); + } + + @Test + public void getInput_fdClashesWithExistingFile() throws IOException { + String env = "@FD:existing.file"; + File tempFile = new File(env); + if (!tempFile.createNewFile()) { + throw new TestAbortedException("Cannot create temporary file " + tempFile.getAbsolutePath()); + } + tempFile.deleteOnExit(); + + resolver.addEnvironmentVariable("existing.file", "foo_bar"); + + assertThrows(SOPGPException.AmbiguousInput.class, () -> abstractCmd.getInput(env)); + } + + @Test + public void getInput_missingFile() { + String missingFile = "missing.file"; + assertThrows(SOPGPException.MissingInput.class, () -> abstractCmd.getInput(missingFile)); + } + + @Test + public void getInput_notAFile() throws IOException { + File directory = TestFileUtil.createTempDir(); + directory.deleteOnExit(); + + assertThrows(SOPGPException.MissingInput.class, () -> abstractCmd.getInput(directory.getAbsolutePath())); + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/TestEnvironmentVariableResolver.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/TestEnvironmentVariableResolver.java new file mode 100644 index 0000000..6665ada --- /dev/null +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/TestEnvironmentVariableResolver.java @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import java.util.HashMap; +import java.util.Map; + +public class TestEnvironmentVariableResolver implements AbstractSopCmd.EnvironmentVariableResolver { + + private final Map environment = new HashMap<>(); + + public void addEnvironmentVariable(String name, String value) { + this.environment.put(name, value); + } + + @Override + public String resolveEnvironmentVariable(String name) { + return environment.get(name); + } +} From c4cbf8ff69a0a4a8a4e5ca0d8924411e53d8f518 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 18:31:56 +0200 Subject: [PATCH 015/444] Add tests for getOutput --- .../picocli/commands/AbstractSopCmdTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java index 7038ad7..9f383f5 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java @@ -123,4 +123,37 @@ public class AbstractSopCmdTest { assertThrows(SOPGPException.MissingInput.class, () -> abstractCmd.getInput(directory.getAbsolutePath())); } + + @Test + public void getOutput_NullIllegalArg() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getOutput(null)); + } + + @Test + public void getOutput_EmptyIllegalArg() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getOutput("")); + } + + @Test + public void getOutput_BlankIllegalArg() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getOutput(" ")); + } + + @Test + public void getOutput_envUnsupportedSpecialPrefix() { + assertThrows(SOPGPException.UnsupportedSpecialPrefix.class, () -> abstractCmd.getOutput("@ENV:IS_ILLEGAL")); + } + + @Test + public void getOutput_fdUnsupportedSpecialPrefix() { + assertThrows(SOPGPException.UnsupportedSpecialPrefix.class, () -> abstractCmd.getOutput("@FD:IS_ILLEGAL")); + } + + @Test + public void getOutput_fileExists() throws IOException { + File testFile = TestFileUtil.createTempDir(); + testFile.deleteOnExit(); + + assertThrows(SOPGPException.OutputExists.class, () -> abstractCmd.getOutput(testFile.getAbsolutePath())); + } } From d80a0a067fb327b848ea329ca34512f31ddc066e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 Aug 2022 18:46:11 +0200 Subject: [PATCH 016/444] Sign: Test that key passwords are passed down from CLI --- .../test/java/sop/cli/picocli/commands/SignCmdTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java index 3b96012..c3d6b59 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java @@ -23,6 +23,7 @@ import sop.ReadyWithResult; import sop.SOP; import sop.SigningResult; import sop.cli.picocli.SopCLI; +import sop.cli.picocli.TestFileUtil; import sop.exception.SOPGPException; import sop.operation.DetachedSign; @@ -30,6 +31,7 @@ public class SignCmdTest { DetachedSign detachedSign; File keyFile; + File passFile; @BeforeEach public void mockComponents() throws IOException, SOPGPException.ExpectedText { @@ -47,6 +49,7 @@ public class SignCmdTest { SopCLI.setSopInstance(sop); keyFile = File.createTempFile("sign-", ".asc"); + passFile = TestFileUtil.writeTempStringFile("sw0rdf1sh"); } @Test @@ -107,6 +110,12 @@ public class SignCmdTest { verify(detachedSign, times(1)).noArmor(); } + @Test + public void withKeyPassword_passedDown() { + SopCLI.main(new String[] {"sign", "--with-key-password", passFile.getAbsolutePath(), keyFile.getAbsolutePath()}); + verify(detachedSign, times(1)).withKeyPassword("sw0rdf1sh"); + } + @Test @ExpectSystemExitWithStatus(1) public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText { From dc5f11469f847a114061fa4f71fda3cdab9bc205 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 12:15:53 +0200 Subject: [PATCH 017/444] Rename resource bundles and name resources after options/parameters --- .../src/main/java/sop/cli/picocli/SopCLI.java | 8 +++---- .../cli/picocli/commands/AbstractSopCmd.java | 2 +- .../sop/cli/picocli/commands/ArmorCmd.java | 3 +-- .../sop/cli/picocli/commands/DearmorCmd.java | 2 +- .../sop/cli/picocli/commands/DecryptCmd.java | 13 ++--------- .../sop/cli/picocli/commands/EncryptCmd.java | 10 ++------ .../cli/picocli/commands/ExtractCertCmd.java | 3 +-- .../cli/picocli/commands/GenerateKeyCmd.java | 6 ++--- .../cli/picocli/commands/InlineDetachCmd.java | 4 +--- .../cli/picocli/commands/InlineSignCmd.java | 8 ++----- .../cli/picocli/commands/InlineVerifyCmd.java | 8 ++----- .../sop/cli/picocli/commands/SignCmd.java | 9 ++------ .../sop/cli/picocli/commands/VerifyCmd.java | 6 +---- .../sop/cli/picocli/commands/VersionCmd.java | 8 +++---- .../src/main/resources/decrypt.properties | 23 ------------------- .../src/main/resources/decrypt_de.properties | 23 ------------------- .../main/resources/detached-sign.properties | 12 ---------- .../resources/detached-sign_de.properties | 12 ---------- .../main/resources/detached-verify.properties | 13 ----------- .../resources/detached-verify_de.properties | 13 ----------- .../src/main/resources/encrypt.properties | 12 ---------- .../src/main/resources/encrypt_de.properties | 12 ---------- .../main/resources/generate-key.properties | 8 ------- .../main/resources/generate-key_de.properties | 8 ------- .../src/main/resources/inline-sign.properties | 14 ----------- .../main/resources/inline-sign_de.properties | 14 ----------- .../main/resources/inline-verify.properties | 13 ----------- .../resources/inline-verify_de.properties | 13 ----------- ...{armor.properties => msg_armor.properties} | 2 +- ..._de.properties => msg_armor_de.properties} | 2 +- ...rmor.properties => msg_dearmor.properties} | 0 ...e.properties => msg_dearmor_de.properties} | 0 .../src/main/resources/msg_decrypt.properties | 23 +++++++++++++++++++ .../main/resources/msg_decrypt_de.properties | 23 +++++++++++++++++++ .../resources/msg_detached-sign.properties | 12 ++++++++++ .../resources/msg_detached-sign_de.properties | 12 ++++++++++ .../resources/msg_detached-verify.properties | 13 +++++++++++ .../msg_detached-verify_de.properties | 13 +++++++++++ .../src/main/resources/msg_encrypt.properties | 12 ++++++++++ .../main/resources/msg_encrypt_de.properties | 12 ++++++++++ ...properties => msg_extract-cert.properties} | 2 +- ...perties => msg_extract-cert_de.properties} | 2 +- .../resources/msg_generate-key.properties | 8 +++++++ .../resources/msg_generate-key_de.properties | 8 +++++++ .../{help.properties => msg_help.properties} | 0 ...p_de.properties => msg_help_de.properties} | 0 ...roperties => msg_inline-detach.properties} | 4 ++-- ...erties => msg_inline-detach_de.properties} | 4 ++-- .../main/resources/msg_inline-sign.properties | 14 +++++++++++ .../resources/msg_inline-sign_de.properties | 14 +++++++++++ .../resources/msg_inline-verify.properties | 13 +++++++++++ .../resources/msg_inline-verify_de.properties | 13 +++++++++++ .../{sop.properties => msg_sop.properties} | 2 +- ...op_de.properties => msg_sop_de.properties} | 2 +- ...sion.properties => msg_version.properties} | 4 ++-- ...e.properties => msg_version_de.properties} | 4 ++-- 56 files changed, 229 insertions(+), 269 deletions(-) delete mode 100644 sop-java-picocli/src/main/resources/decrypt.properties delete mode 100644 sop-java-picocli/src/main/resources/decrypt_de.properties delete mode 100644 sop-java-picocli/src/main/resources/detached-sign.properties delete mode 100644 sop-java-picocli/src/main/resources/detached-sign_de.properties delete mode 100644 sop-java-picocli/src/main/resources/detached-verify.properties delete mode 100644 sop-java-picocli/src/main/resources/detached-verify_de.properties delete mode 100644 sop-java-picocli/src/main/resources/encrypt.properties delete mode 100644 sop-java-picocli/src/main/resources/encrypt_de.properties delete mode 100644 sop-java-picocli/src/main/resources/generate-key.properties delete mode 100644 sop-java-picocli/src/main/resources/generate-key_de.properties delete mode 100644 sop-java-picocli/src/main/resources/inline-sign.properties delete mode 100644 sop-java-picocli/src/main/resources/inline-sign_de.properties delete mode 100644 sop-java-picocli/src/main/resources/inline-verify.properties delete mode 100644 sop-java-picocli/src/main/resources/inline-verify_de.properties rename sop-java-picocli/src/main/resources/{armor.properties => msg_armor.properties} (67%) rename sop-java-picocli/src/main/resources/{armor_de.properties => msg_armor_de.properties} (71%) rename sop-java-picocli/src/main/resources/{dearmor.properties => msg_dearmor.properties} (100%) rename sop-java-picocli/src/main/resources/{dearmor_de.properties => msg_dearmor_de.properties} (100%) create mode 100644 sop-java-picocli/src/main/resources/msg_decrypt.properties create mode 100644 sop-java-picocli/src/main/resources/msg_decrypt_de.properties create mode 100644 sop-java-picocli/src/main/resources/msg_detached-sign.properties create mode 100644 sop-java-picocli/src/main/resources/msg_detached-sign_de.properties create mode 100644 sop-java-picocli/src/main/resources/msg_detached-verify.properties create mode 100644 sop-java-picocli/src/main/resources/msg_detached-verify_de.properties create mode 100644 sop-java-picocli/src/main/resources/msg_encrypt.properties create mode 100644 sop-java-picocli/src/main/resources/msg_encrypt_de.properties rename sop-java-picocli/src/main/resources/{extract-cert.properties => msg_extract-cert.properties} (81%) rename sop-java-picocli/src/main/resources/{extract-cert_de.properties => msg_extract-cert_de.properties} (80%) create mode 100644 sop-java-picocli/src/main/resources/msg_generate-key.properties create mode 100644 sop-java-picocli/src/main/resources/msg_generate-key_de.properties rename sop-java-picocli/src/main/resources/{help.properties => msg_help.properties} (100%) rename sop-java-picocli/src/main/resources/{help_de.properties => msg_help_de.properties} (100%) rename sop-java-picocli/src/main/resources/{inline-detach.properties => msg_inline-detach.properties} (54%) rename sop-java-picocli/src/main/resources/{inline-detach_de.properties => msg_inline-detach_de.properties} (58%) create mode 100644 sop-java-picocli/src/main/resources/msg_inline-sign.properties create mode 100644 sop-java-picocli/src/main/resources/msg_inline-sign_de.properties create mode 100644 sop-java-picocli/src/main/resources/msg_inline-verify.properties create mode 100644 sop-java-picocli/src/main/resources/msg_inline-verify_de.properties rename sop-java-picocli/src/main/resources/{sop.properties => msg_sop.properties} (99%) rename sop-java-picocli/src/main/resources/{sop_de.properties => msg_sop_de.properties} (99%) rename sop-java-picocli/src/main/resources/{version.properties => msg_version.properties} (56%) rename sop-java-picocli/src/main/resources/{version_de.properties => msg_version_de.properties} (54%) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index b823a8f..4e3d587 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -26,7 +26,7 @@ import java.util.ResourceBundle; @CommandLine.Command( name = "sop", - resourceBundle = "sop", + resourceBundle = "msg_sop", exitCodeOnInvalidInput = 69, subcommands = { CommandLine.HelpCommand.class, @@ -48,7 +48,7 @@ import java.util.ResourceBundle; public class SopCLI { // Singleton static SOP SOP_INSTANCE; - static ResourceBundle cliMsg = ResourceBundle.getBundle("sop"); + static ResourceBundle cliMsg = ResourceBundle.getBundle("msg_sop"); public static String EXECUTABLE_NAME = "sop"; @@ -65,13 +65,13 @@ public class SopCLI { new CommandLine(new InitLocale()).parseArgs(args); // get error message bundle - cliMsg = ResourceBundle.getBundle("sop"); + cliMsg = ResourceBundle.getBundle("msg_sop"); // Prepare CLI CommandLine cmd = new CommandLine(SopCLI.class); // explicitly set help command resource bundle - cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("help")); + cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("msg_help")); // Hide generate-completion command cmd.getSubcommands().get("generate-completion").getCommandSpec().usageMessage().hidden(true); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index ea73fc5..9cca658 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -48,7 +48,7 @@ public abstract class AbstractSopCmd implements Runnable { } public AbstractSopCmd(@Nonnull Locale locale) { - messages = ResourceBundle.getBundle("sop", locale); + messages = ResourceBundle.getBundle("msg_sop", locale); } void throwIfOutputExists(String output) { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java index 9d626e5..5691686 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java @@ -14,12 +14,11 @@ import sop.operation.Armor; import java.io.IOException; @CommandLine.Command(name = "armor", - resourceBundle = "armor", + resourceBundle = "msg_armor", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ArmorCmd extends AbstractSopCmd { @CommandLine.Option(names = {"--label"}, - descriptionKey = "usage.option.label", paramLabel = "{auto|sig|key|cert|message}") ArmorLabel label; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java index 76f1b31..a4fca99 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java @@ -12,7 +12,7 @@ import sop.operation.Dearmor; import java.io.IOException; @CommandLine.Command(name = "dearmor", - resourceBundle = "dearmor", + resourceBundle = "msg_dearmor", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DearmorCmd extends AbstractSopCmd { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index 9f02bb8..f28af58 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.regex.Pattern; @CommandLine.Command(name = "decrypt", - resourceBundle = "decrypt", + resourceBundle = "msg_decrypt", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DecryptCmd extends AbstractSopCmd { @@ -40,50 +40,41 @@ public class DecryptCmd extends AbstractSopCmd { @CommandLine.Option( names = {OPT_SESSION_KEY_OUT}, - descriptionKey = "usage.option.session_key_out", paramLabel = "SESSIONKEY") String sessionKeyOut; @CommandLine.Option( names = {OPT_WITH_SESSION_KEY}, - descriptionKey = "usage.option.with_session_key", paramLabel = "SESSIONKEY") List withSessionKey = new ArrayList<>(); @CommandLine.Option( names = {OPT_WITH_PASSWORD}, - descriptionKey = "usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = {OPT_VERIFY_OUT}, - descriptionKey = "usage.option.verify_out", paramLabel = "VERIFICATIONS") String verifyOut; @CommandLine.Option(names = {OPT_VERIFY_WITH}, - descriptionKey = "usage.option.certs", paramLabel = "CERT") List certs = new ArrayList<>(); @CommandLine.Option(names = {OPT_NOT_BEFORE}, - descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {OPT_NOT_AFTER}, - descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @CommandLine.Parameters(index = "0..*", - descriptionKey = "usage.param.keys", paramLabel = "KEY") List keys = new ArrayList<>(); @CommandLine.Option(names = {OPT_WITH_KEY_PASSWORD}, - descriptionKey = "usage.option.with_key_password", - paramLabel = "PASSWORD") + paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); @Override diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index f87aba2..20b0779 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java @@ -17,37 +17,31 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "encrypt", - resourceBundle = "encrypt", + resourceBundle = "msg_encrypt", exitCodeOnInvalidInput = 37) public class EncryptCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = {"--as"}, - descriptionKey = "usage.option.type", paramLabel = "{binary|text}") EncryptAs type; @CommandLine.Option(names = "--with-password", - descriptionKey = "usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = "--sign-with", - descriptionKey = "usage.option.sign_with", paramLabel = "KEY") List signWith = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); - @CommandLine.Parameters(descriptionKey = "usage.param.certs", - index = "0..*", + @CommandLine.Parameters(index = "0..*", paramLabel = "CERTS") List certs = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java index 4bb1c18..28a74b0 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java @@ -13,12 +13,11 @@ import sop.exception.SOPGPException; import sop.operation.ExtractCert; @CommandLine.Command(name = "extract-cert", - resourceBundle = "extract-cert", + resourceBundle = "msg_extract-cert", exitCodeOnInvalidInput = 37) public class ExtractCertCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index 82c0a37..bab7280 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -15,20 +15,18 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "generate-key", - resourceBundle = "generate-key", + resourceBundle = "msg_generate-key", exitCodeOnInvalidInput = 37) public class GenerateKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; - @CommandLine.Parameters(descriptionKey = "usage.option.user_id") + @CommandLine.Parameters List userId = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") String withKeyPassword; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java index 3ed6f7a..52b654f 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java @@ -14,18 +14,16 @@ import java.io.IOException; import java.io.OutputStream; @CommandLine.Command(name = "inline-detach", - resourceBundle = "inline-detach", + resourceBundle = "msg_inline-detach", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class InlineDetachCmd extends AbstractSopCmd { @CommandLine.Option( names = {"--signatures-out"}, - descriptionKey = "usage.option.signatures_out", paramLabel = "SIGNATURES") String signaturesOut; @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 823e332..476817b 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -17,26 +17,22 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "inline-sign", - resourceBundle = "inline-sign", + resourceBundle = "msg_inline-sign", exitCodeOnInvalidInput = 37) public class InlineSignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = "--as", - descriptionKey = "usage.option.as", paramLabel = "{binary|text|cleartextsigned}") InlineSignAs type; - @CommandLine.Parameters(descriptionKey = "usage.parameter.keys", - paramLabel = "KEYS") + @CommandLine.Parameters(paramLabel = "KEYS") List secretKeyFile = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index df1f805..eb464ea 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -19,27 +19,23 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "inline-verify", - resourceBundle = "inline-verify", + resourceBundle = "msg_inline-verify", exitCodeOnInvalidInput = 37) public class InlineVerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(arity = "1..*", - descriptionKey = "usage.parameter.certs", paramLabel = "CERT") List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; - @CommandLine.Option(names = "--verifications-out", - descriptionKey = "usage.option.verifications_out") + @CommandLine.Option(names = "--verifications-out") String verificationsOut; @Override diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java index d441e1a..521262b 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java @@ -20,31 +20,26 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "sign", - resourceBundle = "detached-sign", + resourceBundle = "msg_detached-sign", exitCodeOnInvalidInput = 37) public class SignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - descriptionKey = "usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = "--as", - descriptionKey = "usage.option.as", paramLabel = "{binary|text}") SignAs type; - @CommandLine.Parameters(descriptionKey = "usage.parameter.keys", - paramLabel = "KEYS") + @CommandLine.Parameters(paramLabel = "KEYS") List secretKeyFile = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - descriptionKey = "usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); @CommandLine.Option(names = "--micalg-out", - descriptionKey = "usage.option.micalg_out", paramLabel = "MICALG") String micAlgOut; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java index d029c52..8054e1f 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java @@ -17,28 +17,24 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "verify", - resourceBundle = "detached-verify", + resourceBundle = "msg_detached-verify", exitCodeOnInvalidInput = 37) public class VerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(index = "0", - descriptionKey = "usage.parameter.signature", paramLabel = "SIGNATURE") String signature; @CommandLine.Parameters(index = "0..*", arity = "1..*", - descriptionKey = "usage.parameter.certs", paramLabel = "CERT") List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - descriptionKey = "usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - descriptionKey = "usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java index 2cf97e9..3610ff4 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java @@ -9,7 +9,7 @@ import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.operation.Version; -@CommandLine.Command(name = "version", resourceBundle = "version", +@CommandLine.Command(name = "version", resourceBundle = "msg_version", exitCodeOnInvalidInput = 37) public class VersionCmd extends AbstractSopCmd { @@ -17,12 +17,10 @@ public class VersionCmd extends AbstractSopCmd { Exclusive exclusive; static class Exclusive { - @CommandLine.Option(names = "--extended", - descriptionKey = "usage.option.extended") + @CommandLine.Option(names = "--extended") boolean extended; - @CommandLine.Option(names = "--backend", - descriptionKey = "usage.option.backend") + @CommandLine.Option(names = "--backend") boolean backend; } diff --git a/sop-java-picocli/src/main/resources/decrypt.properties b/sop-java-picocli/src/main/resources/decrypt.properties deleted file mode 100644 index ddf20eb..0000000 --- a/sop-java-picocli/src/main/resources/decrypt.properties +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Decrypt a message from standard input -usage.option.session_key_out=Can be used to learn the session key on successful decryption -usage.option.with_session_key.0=Symmetric message key (session key). -usage.option.with_session_key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. -usage.option.with_session_key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -usage.option.with_password.0=Symmetric passphrase to decrypt the message with. -usage.option.with_password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". -usage.option.with_password_2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -usage.option.verify_out=Emits signature verification status to the designated output -usage.option.certs=Certificates for signature verification -usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_before.1=Reject signatures with a creation date not in range. -usage.option.not_before.2=Defaults to beginning of time ('-'). -usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_after.1=Reject signatures with a creation date not in range. -usage.option.not_after.2=Defaults to current system time ('now'). -usage.option.not_after.3=Accepts special value '-' for end of time. -usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -usage.param.keys=Secret keys to attempt decryption with diff --git a/sop-java-picocli/src/main/resources/decrypt_de.properties b/sop-java-picocli/src/main/resources/decrypt_de.properties deleted file mode 100644 index 42d55e6..0000000 --- a/sop-java-picocli/src/main/resources/decrypt_de.properties +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Entschlüssle eine Nachricht von Standard-Eingabe -usage.option.session_key_out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung -usage.option.with_session_key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). -usage.option.with_session_key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. -usage.option.with_session_key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.option.with_password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. -usage.option.with_password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. -usage.option.with_password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.option.verify_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -usage.option.certs=Zertifikate zur Signaturprüfung -usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel -usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.param.keys=Private Schlüssel zum Entschlüsseln der Nachricht diff --git a/sop-java-picocli/src/main/resources/detached-sign.properties b/sop-java-picocli/src/main/resources/detached-sign.properties deleted file mode 100644 index 9238b35..0000000 --- a/sop-java-picocli/src/main/resources/detached-sign.properties +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Create a detached signature on the data from standard input -usage.option.armor=ASCII armor the output -usage.option.as.0=Specify the output format of the signed message -usage.option.as.1=Defaults to 'binary'. -usage-option.as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. -usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -usage.option.micalg_out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -usage.parameter.keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/detached-sign_de.properties b/sop-java-picocli/src/main/resources/detached-sign_de.properties deleted file mode 100644 index abc70b4..0000000 --- a/sop-java-picocli/src/main/resources/detached-sign_de.properties +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe -usage.option.armor=Schütze Ausgabe mit ASCII Armor -usage.option.as.0=Bestimme Signaturformat der Nachricht. -usage.option.as.1=Standardmäßig: 'binary'. -usage-option.as.2=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. -usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels -usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.option.micalg_out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -usage.parameter.keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/detached-verify.properties b/sop-java-picocli/src/main/resources/detached-verify.properties deleted file mode 100644 index a4ddd33..0000000 --- a/sop-java-picocli/src/main/resources/detached-verify.properties +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Verify a detached signature over the data from standard input -usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_before.1=Reject signatures with a creation date not in range. -usage.option.not_before.2=Defaults to beginning of time ("-"). -usage.option.not_after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_after.2=Reject signatures with a creation date not in range. -usage.option.not_after.3=Defaults to current system time ("now").\ -usage.option.not_after.4 = Accepts special value "-" for end of time. -usage.parameter.signature=Detached signature -usage.parameter.certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/detached-verify_de.properties b/sop-java-picocli/src/main/resources/detached-verify_de.properties deleted file mode 100644 index f378ad5..0000000 --- a/sop-java-picocli/src/main/resources/detached-verify_de.properties +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe -usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -usage.parameter.signature=Abgetrennte Signatur -usage.parameter.certs=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/encrypt.properties b/sop-java-picocli/src/main/resources/encrypt.properties deleted file mode 100644 index f9b57e9..0000000 --- a/sop-java-picocli/src/main/resources/encrypt.properties +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Encrypt a message from standard input -usage.option.armor=ASCII armor the output -usage.option.type=Type of the input data. Defaults to 'binary' -usage.option.with_password.0=Encrypt the message with a password. -usage.option.with_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) -usage.option.sign_with=Sign the output with a private key -usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -usage.param.certs=Certificates the message gets encrypted to diff --git a/sop-java-picocli/src/main/resources/encrypt_de.properties b/sop-java-picocli/src/main/resources/encrypt_de.properties deleted file mode 100644 index 555f7c1..0000000 --- a/sop-java-picocli/src/main/resources/encrypt_de.properties +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Verschlüssle eine Nachricht von Standard-Eingabe -usage.option.armor=Schütze Ausgabe mit ASCII Armor -usage.option.type=Format der Nachricht. Standardmäßig 'binary' -usage.option.with_password.0=Verschlüssle die Nachricht mit einem Passwort -usage.option.with_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.option.sign_with=Signiere die Nachricht mit einem privaten Schlüssel -usage.option.with_key_password.0=Passwort zum Entsperren der privaten Schlüssel -usage.option.with_key_password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.param.certs=Zertifikate für die die Nachricht verschlüsselt werden soll diff --git a/sop-java-picocli/src/main/resources/generate-key.properties b/sop-java-picocli/src/main/resources/generate-key.properties deleted file mode 100644 index 822a23e..0000000 --- a/sop-java-picocli/src/main/resources/generate-key.properties +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Generate a secret key -usage.option.armor=ASCII armor the output -usage.option.user_id=User-ID, e.g. "Alice " -usage.option.with_key_password.0=Password to protect the private key with -usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/generate-key_de.properties b/sop-java-picocli/src/main/resources/generate-key_de.properties deleted file mode 100644 index a698593..0000000 --- a/sop-java-picocli/src/main/resources/generate-key_de.properties +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Generiere einen privaten Schlüssel -usage.option.armor=Schütze Ausgabe mit ASCII Armor -usage.option.user_id=Nutzer-ID, z.B.. "Alice " -usage.option.with_key_password.0=Passwort zum Schutz des privaten Schlüssels -usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/inline-sign.properties b/sop-java-picocli/src/main/resources/inline-sign.properties deleted file mode 100644 index 79a2dab..0000000 --- a/sop-java-picocli/src/main/resources/inline-sign.properties +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Create an inline-signed message from data on standard input -usage.option.armor=ASCII armor the output -usage.option.as.0=Specify the signature format of the signed message -usage.option.as.1='text' and 'binary' will produce inline-signed messages. -usage.option.as.2='cleartextsigned' will make use of the cleartext signature framework. -usage.option.as.3=Defaults to 'binary'. -usage.option.as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53. -usage.option.with_key_password.0=Passphrase to unlock the secret key(s). -usage.option.with_key_password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -usage.option.micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -usage.parameter.keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/inline-sign_de.properties b/sop-java-picocli/src/main/resources/inline-sign_de.properties deleted file mode 100644 index dc85ecd..0000000 --- a/sop-java-picocli/src/main/resources/inline-sign_de.properties +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen -usage.option.armor=Schütze Ausgabe mit ASCII Armor -usage.option.as.0=Bestimme Signaturformat der Nachricht. -usage.option.as.1='text' und 'binary' resultieren in eingebettete Signaturen. -usage.option.as.2='cleartextsigned' wird die Nachricht Klartext-signieren. -usage.option.as.3=Standardmäßig: 'binary'. -usage.option.as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. -usage.option.with_key_password.0=Passwort zum Entsperren des privaten Schlüssels -usage.option.with_key_password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -usage.option.micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -usage.parameter.keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/inline-verify.properties b/sop-java-picocli/src/main/resources/inline-verify.properties deleted file mode 100644 index 8552671..0000000 --- a/sop-java-picocli/src/main/resources/inline-verify.properties +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Verify inline-signed data from standard input -usage.option.not_before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_before.1=Reject signatures with a creation date not in range. -usage.option.not_before.2=Defaults to beginning of time ("-"). -usage.option.not_after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -usage.option.not_after.1=Reject signatures with a creation date not in range. -usage.option.not_after.2=Defaults to current system time ("now"). -usage.option.not_after.3=Accepts special value "-" for end of time. -usage.option.verifications_out=File to write details over successful verifications to -usage.parameter.certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/inline-verify_de.properties b/sop-java-picocli/src/main/resources/inline-verify_de.properties deleted file mode 100644 index ecc4a8e..0000000 --- a/sop-java-picocli/src/main/resources/inline-verify_de.properties +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: 2022 Paul Schaub -# -# SPDX-License-Identifier: Apache-2.0 -usage.header=Prüfe eingebettete Signaturen einer Nachricht von Standard-Eingabe -usage.option.not_before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_before.2=Standardmäßig: Anbeginn der Zeit ('-'). -usage.option.not_after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -usage.option.not_after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -usage.option.not_after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -usage.option.not_after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -usage.option.verifications_out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -usage.parameter.certs=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties similarity index 67% rename from sop-java-picocli/src/main/resources/armor.properties rename to sop-java-picocli/src/main/resources/msg_armor.properties index b963ceb..aae11eb 100644 --- a/sop-java-picocli/src/main/resources/armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Add ASCII Armor to standard input -usage.option.label=Label to be used in the header and tail of the armoring +label=Label to be used in the header and tail of the armoring diff --git a/sop-java-picocli/src/main/resources/armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties similarity index 71% rename from sop-java-picocli/src/main/resources/armor_de.properties rename to sop-java-picocli/src/main/resources/msg_armor_de.properties index 1023244..c5f7188 100644 --- a/sop-java-picocli/src/main/resources/armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Schütze Standard-Eingabe mit ASCII Armor -usage.option.label=Label für Kopf- und Fußzeile der ASCII Armor +label=Label für Kopf- und Fußzeile der ASCII Armor diff --git a/sop-java-picocli/src/main/resources/dearmor.properties b/sop-java-picocli/src/main/resources/msg_dearmor.properties similarity index 100% rename from sop-java-picocli/src/main/resources/dearmor.properties rename to sop-java-picocli/src/main/resources/msg_dearmor.properties diff --git a/sop-java-picocli/src/main/resources/dearmor_de.properties b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties similarity index 100% rename from sop-java-picocli/src/main/resources/dearmor_de.properties rename to sop-java-picocli/src/main/resources/msg_dearmor_de.properties diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties new file mode 100644 index 0000000..c7dca86 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Decrypt a message from standard input +session-key-out=Can be used to learn the session key on successful decryption +with-session-key.0=Symmetric message key (session key). +with-session-key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. +with-session-key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +with-password.0=Symmetric passphrase to decrypt the message with. +with-password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". +with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +verify-out=Emits signature verification status to the designated output +certs=Certificates for signature verification +not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-before.1=Reject signatures with a creation date not in range. +not-before.2=Defaults to beginning of time ('-'). +not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-after.1=Reject signatures with a creation date not in range. +not-after.2=Defaults to current system time ('now'). +not-after.3=Accepts special value '-' for end of time. +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +keys=Secret keys to attempt decryption with diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties new file mode 100644 index 0000000..2b0cc0d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Entschlüssle eine Nachricht von Standard-Eingabe +session-key-out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung +with-session-key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). +with-session-key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. +with_session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +with-password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. +with-password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. +with-password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +verify-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe +certs=Zertifikate zur Signaturprüfung +not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). +not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +keys=Private Schlüssel zum Entschlüsseln der Nachricht diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties new file mode 100644 index 0000000..8b51124 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Create a detached signature on the data from standard input +armor=ASCII armor the output +as.0=Specify the output format of the signed message +as.1=Defaults to 'binary'. +as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties new file mode 100644 index 0000000..fdd310a --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe +armor=Schütze Ausgabe mit ASCII Armor +as.0=Bestimme Signaturformat der Nachricht. +as.1=Standardmäßig: 'binary'. +as.2=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. +with-key-password.0=Passwort zum Entsperren des privaten Schlüssels +with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. +keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties new file mode 100644 index 0000000..5e4f4b6 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verify a detached signature over the data from standard input +not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-before.1=Reject signatures with a creation date not in range. +not-before.2=Defaults to beginning of time ("-"). +not-after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-after.2=Reject signatures with a creation date not in range. +not-after.3=Defaults to current system time ("now").\ +not-after.4 = Accepts special value "-" for end of time. +signature=Detached signature +certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties new file mode 100644 index 0000000..b5d669e --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe +not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). +not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +signature=Abgetrennte Signatur +certs=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties new file mode 100644 index 0000000..3339187 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Encrypt a message from standard input +armor=ASCII armor the output +type=Type of the input data. Defaults to 'binary' +with-password.0=Encrypt the message with a password. +with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +sign-with=Sign the output with a private key +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +certs=Certificates the message gets encrypted to diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties new file mode 100644 index 0000000..d364f62 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verschlüssle eine Nachricht von Standard-Eingabe +armor=Schütze Ausgabe mit ASCII Armor +type=Format der Nachricht. Standardmäßig 'binary' +with-password.0=Verschlüssle die Nachricht mit einem Passwort +with-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +sign-with=Signiere die Nachricht mit einem privaten Schlüssel +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +certs=Zertifikate für die die Nachricht verschlüsselt werden soll diff --git a/sop-java-picocli/src/main/resources/extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties similarity index 81% rename from sop-java-picocli/src/main/resources/extract-cert.properties rename to sop-java-picocli/src/main/resources/msg_extract-cert.properties index 50f090e..bf285bc 100644 --- a/sop-java-picocli/src/main/resources/extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Extract a public key certificate from a secret key from standard input -usage.option.armor=ASCII armor the output +armor=ASCII armor the output diff --git a/sop-java-picocli/src/main/resources/extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties similarity index 80% rename from sop-java-picocli/src/main/resources/extract-cert_de.properties rename to sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index f1607c3..f13d5cd 100644 --- a/sop-java-picocli/src/main/resources/extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe -usage.option.armor=Schütze Ausgabe mit ASCII Armor +armor=Schütze Ausgabe mit ASCII Armor diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties new file mode 100644 index 0000000..36145bb --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generate a secret key +armor=ASCII armor the output +user-id=User-ID, e.g. "Alice " +with-key-password.0=Password to protect the private key with +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties new file mode 100644 index 0000000..3d3869c --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generiere einen privaten Schlüssel +armor=Schütze Ausgabe mit ASCII Armor +user-id=Nutzer-ID, z.B.. "Alice " +with-key-password.0=Passwort zum Schutz des privaten Schlüssels +with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/help.properties b/sop-java-picocli/src/main/resources/msg_help.properties similarity index 100% rename from sop-java-picocli/src/main/resources/help.properties rename to sop-java-picocli/src/main/resources/msg_help.properties diff --git a/sop-java-picocli/src/main/resources/help_de.properties b/sop-java-picocli/src/main/resources/msg_help_de.properties similarity index 100% rename from sop-java-picocli/src/main/resources/help_de.properties rename to sop-java-picocli/src/main/resources/msg_help_de.properties diff --git a/sop-java-picocli/src/main/resources/inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties similarity index 54% rename from sop-java-picocli/src/main/resources/inline-detach.properties rename to sop-java-picocli/src/main/resources/msg_inline-detach.properties index 0f03363..5500aaa 100644 --- a/sop-java-picocli/src/main/resources/inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Split signatures from a clearsigned message -usage.option.armor=ASCII armor the output -usage.option.signatures_out=Destination to which a detached signatures block will be written +armor=ASCII armor the output +signatures-out=Destination to which a detached signatures block will be written diff --git a/sop-java-picocli/src/main/resources/inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties similarity index 58% rename from sop-java-picocli/src/main/resources/inline-detach_de.properties rename to sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index 3be5f5a..a2bb0ad 100644 --- a/sop-java-picocli/src/main/resources/inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Trenne Signaturen von Klartext-signierter Nachricht -usage.option.armor=Schütze Ausgabe mit ASCII Armor -usage.option.signatures_out=Schreibe abgetrennte Signaturen in Ausgabe +armor=Schütze Ausgabe mit ASCII Armor +signatures-out=Schreibe abgetrennte Signaturen in Ausgabe diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties new file mode 100644 index 0000000..ca53d22 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Create an inline-signed message from data on standard input +armor=ASCII armor the output +as.0=Specify the signature format of the signed message +as.1='text' and 'binary' will produce inline-signed messages. +as.2='cleartextsigned' will make use of the cleartext signature framework. +as.3=Defaults to 'binary'. +as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53. +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +keys=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties new file mode 100644 index 0000000..8c9d000 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen +armor=Schütze Ausgabe mit ASCII Armor +as.0=Bestimme Signaturformat der Nachricht. +as.1='text' und 'binary' resultieren in eingebettete Signaturen. +as.2='cleartextsigned' wird die Nachricht Klartext-signieren. +as.3=Standardmäßig: 'binary'. +as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. +with-key-password.0=Passwort zum Entsperren des privaten Schlüssels +with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. +keys=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties new file mode 100644 index 0000000..e3388c5 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Verify inline-signed data from standard input +not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-before.1=Reject signatures with a creation date not in range. +not-before.2=Defaults to beginning of time ("-"). +not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-after.1=Reject signatures with a creation date not in range. +not-after.2=Defaults to current system time ("now"). +not-after.3=Accepts special value "-" for end of time. +verifications-out=File to write details over successful verifications to +certs=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties new file mode 100644 index 0000000..39268ad --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Prüfe eingebettete Signaturen einer Nachricht von Standard-Eingabe +not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). +not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe +certs=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties similarity index 99% rename from sop-java-picocli/src/main/resources/sop.properties rename to sop-java-picocli/src/main/resources/msg_sop.properties index 4fd31a2..9b38735 100644 --- a/sop-java-picocli/src/main/resources/sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -5,7 +5,7 @@ sop.name=sop usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by picocli%n -sop.locale=Locale for description texts +locale=Locale for description texts # Generic usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties similarity index 99% rename from sop-java-picocli/src/main/resources/sop_de.properties rename to sop-java-picocli/src/main/resources/msg_sop_de.properties index d2542de..cc8e16e 100644 --- a/sop-java-picocli/src/main/resources/sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -5,7 +5,7 @@ sop.name=sop usage.header=Stateless OpenPGP Protocol usage.footerHeading=Powered by Picocli%n -sop.locale=Gebietsschema für Beschreibungstexte +locale=Gebietsschema für Beschreibungstexte # Generic usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/version.properties b/sop-java-picocli/src/main/resources/msg_version.properties similarity index 56% rename from sop-java-picocli/src/main/resources/version.properties rename to sop-java-picocli/src/main/resources/msg_version.properties index 4bf6457..1454a7c 100644 --- a/sop-java-picocli/src/main/resources/version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Display version information about the tool -usage.option.extended=Print an extended version string -usage.option.backend=Print information about the cryptographic backend +extended=Print an extended version string +backend=Print information about the cryptographic backend diff --git a/sop-java-picocli/src/main/resources/version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties similarity index 54% rename from sop-java-picocli/src/main/resources/version_de.properties rename to sop-java-picocli/src/main/resources/msg_version_de.properties index 18250ea..a4f6524 100644 --- a/sop-java-picocli/src/main/resources/version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Zeige Versionsinformationen über das Programm -usage.option.extended=Gebe erweiterte Versionsinformationen aus -usage.option.backend=Gebe Informationen über das kryptografische Backend aus +extended=Gebe erweiterte Versionsinformationen aus +backend=Gebe Informationen über das kryptografische Backend aus From dcb44f96c874335cbff1b419261b143ea9647ea7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 12:48:32 +0200 Subject: [PATCH 018/444] Fix wrongly named resource entries --- .../main/java/sop/cli/picocli/commands/GenerateKeyCmd.java | 2 +- sop-java-picocli/src/main/resources/msg_decrypt.properties | 4 ++-- .../src/main/resources/msg_decrypt_de.properties | 4 ++-- .../src/main/resources/msg_detached-sign.properties | 4 ++-- .../src/main/resources/msg_detached-sign_de.properties | 4 ++-- .../src/main/resources/msg_detached-verify.properties | 4 ++-- .../src/main/resources/msg_detached-verify_de.properties | 4 ++-- sop-java-picocli/src/main/resources/msg_encrypt.properties | 6 +++--- .../src/main/resources/msg_encrypt_de.properties | 6 +++--- .../src/main/resources/msg_extract-cert.properties | 2 +- .../src/main/resources/msg_extract-cert_de.properties | 2 +- .../src/main/resources/msg_generate-key.properties | 4 ++-- .../src/main/resources/msg_generate-key_de.properties | 4 ++-- .../src/main/resources/msg_inline-detach.properties | 2 +- .../src/main/resources/msg_inline-detach_de.properties | 2 +- .../src/main/resources/msg_inline-sign.properties | 4 ++-- .../src/main/resources/msg_inline-sign_de.properties | 4 ++-- .../src/main/resources/msg_inline-verify.properties | 2 +- .../src/main/resources/msg_inline-verify_de.properties | 2 +- 19 files changed, 33 insertions(+), 33 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index bab7280..d215baf 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -23,7 +23,7 @@ public class GenerateKeyCmd extends AbstractSopCmd { negatable = true) boolean armor = true; - @CommandLine.Parameters + @CommandLine.Parameters(paramLabel = "USERID") List userId = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index c7dca86..46f0fb1 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -10,7 +10,7 @@ with-password.0=Symmetric passphrase to decrypt the message with. with-password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) verify-out=Emits signature verification status to the designated output -certs=Certificates for signature verification +verify-with=Certificates for signature verification not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) not-before.1=Reject signatures with a creation date not in range. not-before.2=Defaults to beginning of time ('-'). @@ -20,4 +20,4 @@ not-after.2=Defaults to current system time ('now'). not-after.3=Accepts special value '-' for end of time. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -keys=Secret keys to attempt decryption with +KEY[0..*]=Secret keys to attempt decryption with diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index 2b0cc0d..bc3e58f 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -10,7 +10,7 @@ with-password.0=Symmetrisches Passwort zur Entschl with-password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. with-password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). verify-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -certs=Zertifikate zur Signaturprüfung +verify-with=Zertifikate zur Signaturprüfung not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). @@ -20,4 +20,4 @@ not-after.2=Standardm not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -keys=Private Schlüssel zum Entschlüsseln der Nachricht +KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index 8b51124..9f75641 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Create a detached signature on the data from standard input -armor=ASCII armor the output +no-armor=ASCII armor the output as.0=Specify the output format of the signed message as.1=Defaults to 'binary'. as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -keys=Secret keys used for signing +KEYS[0..*]=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index fdd310a..c2053a2 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe -armor=Schütze Ausgabe mit ASCII Armor +no-armor=Schütze Ausgabe mit ASCII Armor as.0=Bestimme Signaturformat der Nachricht. as.1=Standardmäßig: 'binary'. as.2=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. with-key-password.0=Passwort zum Entsperren des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -keys=Private Signaturschlüssel +KEYS[0..*]=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index 5e4f4b6..afacdbd 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -9,5 +9,5 @@ not-after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) not-after.2=Reject signatures with a creation date not in range. not-after.3=Defaults to current system time ("now").\ not-after.4 = Accepts special value "-" for end of time. -signature=Detached signature -certs=Public key certificates for signature verification +SIGNATURE[0]=Detached signature +CERT[0..*]=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index b5d669e..f69bf0b 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -9,5 +9,5 @@ not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. -signature=Abgetrennte Signatur -certs=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung +SIGNATURE[0]=Abgetrennte Signatur +CERT[0..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index 3339187..4bd7051 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Encrypt a message from standard input -armor=ASCII armor the output -type=Type of the input data. Defaults to 'binary' +no-armor=ASCII armor the output +as=Type of the input data. Defaults to 'binary' with-password.0=Encrypt the message with a password. with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) sign-with=Sign the output with a private key with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -certs=Certificates the message gets encrypted to +CERTS[0..*]=Certificates the message gets encrypted to diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index d364f62..1bfab33 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -2,11 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Verschlüssle eine Nachricht von Standard-Eingabe -armor=Schütze Ausgabe mit ASCII Armor -type=Format der Nachricht. Standardmäßig 'binary' +no-armor=Schütze Ausgabe mit ASCII Armor +as=Format der Nachricht. Standardmäßig 'binary' with-password.0=Verschlüssle die Nachricht mit einem Passwort with-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). sign-with=Signiere die Nachricht mit einem privaten Schlüssel with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). -certs=Zertifikate für die die Nachricht verschlüsselt werden soll +CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index bf285bc..77c04db 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Extract a public key certificate from a secret key from standard input -armor=ASCII armor the output +no-armor=ASCII armor the output diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index f13d5cd..a35bda4 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe -armor=Schütze Ausgabe mit ASCII Armor +no-armor=Schütze Ausgabe mit ASCII Armor diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 36145bb..29eb284 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Generate a secret key -armor=ASCII armor the output -user-id=User-ID, e.g. "Alice " +no-armor=ASCII armor the output +USERID[0..*]=User-ID, e.g. "Alice " with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index 3d3869c..60b2e21 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Generiere einen privaten Schlüssel -armor=Schütze Ausgabe mit ASCII Armor -user-id=Nutzer-ID, z.B.. "Alice " +no-armor=Schütze Ausgabe mit ASCII Armor +USERID[0..*]=Nutzer-ID, z.B.. "Alice " with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties index 5500aaa..5963f88 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Split signatures from a clearsigned message -armor=ASCII armor the output +no-armor=ASCII armor the output signatures-out=Destination to which a detached signatures block will be written diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index a2bb0ad..ffe058b 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -2,5 +2,5 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Trenne Signaturen von Klartext-signierter Nachricht -armor=Schütze Ausgabe mit ASCII Armor +no-armor=Schütze Ausgabe mit ASCII Armor signatures-out=Schreibe abgetrennte Signaturen in Ausgabe diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index ca53d22..efe5c5f 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Create an inline-signed message from data on standard input -armor=ASCII armor the output +no-armor=ASCII armor the output as.0=Specify the signature format of the signed message as.1='text' and 'binary' will produce inline-signed messages. as.2='cleartextsigned' will make use of the cleartext signature framework. @@ -11,4 +11,4 @@ as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails wit with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) -keys=Secret keys used for signing +KEYS[0..*]=Secret keys used for signing diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index 8c9d000..dd5387d 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen -armor=Schütze Ausgabe mit ASCII Armor +no-armor=Schütze Ausgabe mit ASCII Armor as.0=Bestimme Signaturformat der Nachricht. as.1='text' und 'binary' resultieren in eingebettete Signaturen. as.2='cleartextsigned' wird die Nachricht Klartext-signieren. @@ -11,4 +11,4 @@ as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wi with-key-password.0=Passwort zum Entsperren des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. -keys=Private Signaturschlüssel +KEYS[0..*]=Private Signaturschlüssel diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index e3388c5..bde6622 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -10,4 +10,4 @@ not-after.1=Reject signatures with a creation date not in range. not-after.2=Defaults to current system time ("now"). not-after.3=Accepts special value "-" for end of time. verifications-out=File to write details over successful verifications to -certs=Public key certificates for signature verification +CERT[0..*]=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index 39268ad..0adf42b 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -10,4 +10,4 @@ not-after.1=Lehne Signaturen mit Erstellungsdatum au not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe -certs=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung +CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung From 0cb614827bfbfd60d964f959e0a9b4dde999450d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 12:51:01 +0200 Subject: [PATCH 019/444] Un-bump picocli --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index e94f79d..9aa5316 100644 --- a/version.gradle +++ b/version.gradle @@ -10,7 +10,7 @@ allprojects { javaSourceCompatibility = 1.8 junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - picocliVersion = '4.7.0' + picocliVersion = '4.6.3' mockitoVersion = '4.5.1' jsrVersion = '3.0.2' } From e17d4d2efbacb71ee05408c0864be45ddf114362 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 13:14:21 +0200 Subject: [PATCH 020/444] Add shared resources to all subcommand bundles --- sop-java-picocli/src/main/resources/msg_armor.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_armor_de.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_dearmor.properties | 6 ++++++ .../src/main/resources/msg_dearmor_de.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_decrypt.properties | 6 ++++++ .../src/main/resources/msg_decrypt_de.properties | 6 ++++++ .../src/main/resources/msg_detached-sign.properties | 6 ++++++ .../src/main/resources/msg_detached-sign_de.properties | 6 ++++++ .../src/main/resources/msg_detached-verify.properties | 6 ++++++ .../src/main/resources/msg_detached-verify_de.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_encrypt.properties | 6 ++++++ .../src/main/resources/msg_encrypt_de.properties | 6 ++++++ .../src/main/resources/msg_extract-cert.properties | 6 ++++++ .../src/main/resources/msg_extract-cert_de.properties | 6 ++++++ .../src/main/resources/msg_generate-key.properties | 6 ++++++ .../src/main/resources/msg_generate-key_de.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_help.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_help_de.properties | 6 ++++++ .../src/main/resources/msg_inline-detach.properties | 6 ++++++ .../src/main/resources/msg_inline-detach_de.properties | 6 ++++++ .../src/main/resources/msg_inline-sign.properties | 6 ++++++ .../src/main/resources/msg_inline-sign_de.properties | 6 ++++++ .../src/main/resources/msg_inline-verify.properties | 6 ++++++ .../src/main/resources/msg_inline-verify_de.properties | 6 ++++++ sop-java-picocli/src/main/resources/msg_sop.properties | 6 ++++-- sop-java-picocli/src/main/resources/msg_sop_de.properties | 6 ++++-- sop-java-picocli/src/main/resources/msg_version.properties | 6 ++++++ .../src/main/resources/msg_version_de.properties | 6 ++++++ 28 files changed, 164 insertions(+), 4 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties index aae11eb..74feb8b 100644 --- a/sop-java-picocli/src/main/resources/msg_armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -3,3 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Add ASCII Armor to standard input label=Label to be used in the header and tail of the armoring + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties index c5f7188..4dbf43b 100644 --- a/sop-java-picocli/src/main/resources/msg_armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -3,3 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Schütze Standard-Eingabe mit ASCII Armor label=Label für Kopf- und Fußzeile der ASCII Armor + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor.properties b/sop-java-picocli/src/main/resources/msg_dearmor.properties index fc2f119..734c61a 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor.properties @@ -2,3 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Remove ASCII Armor from standard input + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties index 186540e..b601e43 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties @@ -2,3 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Entferne ASCII Armor von Standard-Eingabe + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index 46f0fb1..47d1c31 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -21,3 +21,9 @@ not-after.3=Accepts special value '-' for end of time. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). KEY[0..*]=Secret keys to attempt decryption with + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index bc3e58f..0ef4a4c 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -21,3 +21,9 @@ not-after.3=Akzeptiert speziellen Wert '-' f with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index 9f75641..50ab7c9 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -10,3 +10,9 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) KEYS[0..*]=Secret keys used for signing + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index c2053a2..7c39ebd 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -10,3 +10,9 @@ with-key-password.0=Passwort zum Entsperren des privaten Schl with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index afacdbd..9b843cc 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -11,3 +11,9 @@ not-after.3=Defaults to current system time ("now").\ not-after.4 = Accepts special value "-" for end of time. SIGNATURE[0]=Detached signature CERT[0..*]=Public key certificates for signature verification + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index f69bf0b..e13ccba 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -11,3 +11,9 @@ not-after.2=Standardm not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. SIGNATURE[0]=Abgetrennte Signatur CERT[0..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index 4bd7051..4b59551 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -10,3 +10,9 @@ sign-with=Sign the output with a private key with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). CERTS[0..*]=Certificates the message gets encrypted to + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index 1bfab33..f2fd356 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -10,3 +10,9 @@ sign-with=Signiere die Nachricht mit einem privaten Schl with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index 77c04db..76ee718 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -3,3 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Extract a public key certificate from a secret key from standard input no-armor=ASCII armor the output + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index a35bda4..ce16f7c 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -3,3 +3,9 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe no-armor=Schütze Ausgabe mit ASCII Armor + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 29eb284..5744199 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -6,3 +6,9 @@ no-armor=ASCII armor the output USERID[0..*]=User-ID, e.g. "Alice " with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index 60b2e21..b3cbf62 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -6,3 +6,9 @@ no-armor=Sch USERID[0..*]=Nutzer-ID, z.B.. "Alice " with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help.properties b/sop-java-picocli/src/main/resources/msg_help.properties index 518f793..1146a3e 100644 --- a/sop-java-picocli/src/main/resources/msg_help.properties +++ b/sop-java-picocli/src/main/resources/msg_help.properties @@ -2,3 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Display usage information for the specified subcommand + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help_de.properties b/sop-java-picocli/src/main/resources/msg_help_de.properties index d933a4a..9262114 100644 --- a/sop-java-picocli/src/main/resources/msg_help_de.properties +++ b/sop-java-picocli/src/main/resources/msg_help_de.properties @@ -2,3 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Zeige Nutzungshilfen für den angegebenen Unterbefehl an + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties index 5963f88..44f530d 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -4,3 +4,9 @@ usage.header=Split signatures from a clearsigned message no-armor=ASCII armor the output signatures-out=Destination to which a detached signatures block will be written + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index ffe058b..5177a2f 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -4,3 +4,9 @@ usage.header=Trenne Signaturen von Klartext-signierter Nachricht no-armor=Schütze Ausgabe mit ASCII Armor signatures-out=Schreibe abgetrennte Signaturen in Ausgabe + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index efe5c5f..88584e9 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -12,3 +12,9 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) KEYS[0..*]=Secret keys used for signing + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index dd5387d..10be2f3 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -12,3 +12,9 @@ with-key-password.0=Passwort zum Entsperren des privaten Schl with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index bde6622..901fd07 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -11,3 +11,9 @@ not-after.2=Defaults to current system time ("now"). not-after.3=Accepts special value "-" for end of time. verifications-out=File to write details over successful verifications to CERT[0..*]=Public key certificates for signature verification + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index 0adf42b..e5ea22a 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -11,3 +11,9 @@ not-after.2=Standardm not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 9b38735..1660a34 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -3,12 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 sop.name=sop usage.header=Stateless OpenPGP Protocol -usage.footerHeading=Powered by picocli%n - locale=Locale for description texts + # Generic usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Successful program execution diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index cc8e16e..2da8e89 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -3,12 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 sop.name=sop usage.header=Stateless OpenPGP Protocol -usage.footerHeading=Powered by Picocli%n - locale=Gebietsschema für Beschreibungstexte + # Generic usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index 1454a7c..c081705 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -4,3 +4,9 @@ usage.header=Display version information about the tool extended=Print an extended version string backend=Print information about the cryptographic backend + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index a4f6524..c22d91f 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -4,3 +4,9 @@ usage.header=Zeige Versionsinformationen über das Programm extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n From 06d92eecf32b60bdfc3088332de1f155e62bef6c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 13:56:16 +0200 Subject: [PATCH 021/444] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d60f114..7151d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.1 +- Use shared resources for i18n + - Fix strings not being resolved properly when downstream renames `sop` command + ## 4.0.0 - Switch to new versioning format to indicate implemented SOP version - Implement SOP specification version 04 From a14f008215dfb0d89fc7d98aa80aa0571f5851df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 13:57:41 +0200 Subject: [PATCH 022/444] SOP-Java 4.0.1 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 9aa5316..7555332 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 29660a1d3f24a73f1f224cf17d57f53ca9aeac57 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Aug 2022 13:59:06 +0200 Subject: [PATCH 023/444] SOP-Java 4.0.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 7555332..f24e9f0 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.1' - isSnapshot = false + shortVersion = '4.0.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 043e95e5c0b9c119acd2116acfe876396668e655 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 5 Nov 2022 18:06:35 +0100 Subject: [PATCH 024/444] Fix arity of arguments in verify commands Fixes https://github.com/pgpainless/pgpainless/issues/330 --- .../src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/VerifyCmd.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index eb464ea..05e2c8a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -23,7 +23,7 @@ import java.util.List; exitCodeOnInvalidInput = 37) public class InlineVerifyCmd extends AbstractSopCmd { - @CommandLine.Parameters(arity = "1..*", + @CommandLine.Parameters(arity = "0..*", paramLabel = "CERT") List certificates = new ArrayList<>(); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java index 8054e1f..1bcd0a3 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java @@ -25,7 +25,7 @@ public class VerifyCmd extends AbstractSopCmd { paramLabel = "SIGNATURE") String signature; - @CommandLine.Parameters(index = "0..*", + @CommandLine.Parameters(index = "1..*", arity = "1..*", paramLabel = "CERT") List certificates = new ArrayList<>(); From 162ae120c3bfc074f21e89264a470d4161804b82 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 5 Nov 2022 18:11:02 +0100 Subject: [PATCH 025/444] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7151d5e..30293c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.2-SNAPSHOT +- Fix: `verify`: Do not include detached signature in list of certificates +- Fix: `inline-verify`: Also include the first argument in list of certificates + ## 4.0.1 - Use shared resources for i18n - Fix strings not being resolved properly when downstream renames `sop` command From e75dde16372f15a28ed94273ea206a413a714cff Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:40:53 +0100 Subject: [PATCH 026/444] Remove colons from error message --- sop-java-picocli/src/main/resources/msg_sop.properties | 2 +- sop-java-picocli/src/main/resources/msg_sop_de.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 1660a34..d01e67f 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -52,7 +52,7 @@ sop.error.indirect_data_type.input_file_does_not_exist=Input file '%s' does not sop.error.indirect_data_type.input_not_a_file=Input file '%s' is not a file. sop.error.indirect_data_type.output_file_already_exists=Output file '%s' already exists. sop.error.indirect_data_type.output_file_cannot_be_created=Output file '%s' cannot be created. -sop.error.indirect_data_type.illegal_use_of_env_designator=Special designator '@ENV:' cannot be used for output. +sop.error.indirect_data_type.illegal_use_of_env_designator=Special designator '@ENV' cannot be used for output. sop.error.indirect_data_type.designator_env_not_supported=Special designator '@ENV' is not supported. sop.error.indirect_data_type.designator_fd_not_supported=Special designator '@FD' is not supported. ## Runtime Errors diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 2da8e89..a54fa7e 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -52,7 +52,7 @@ sop.error.indirect_data_type.input_file_does_not_exist=Quelldatei '%s' existiert sop.error.indirect_data_type.input_not_a_file=Quelldatei '%s' ist keine Datei. sop.error.indirect_data_type.output_file_already_exists=Zieldatei '%s' existiert bereits. sop.error.indirect_data_type.output_file_cannot_be_created=Zieldatei '%s' kann nicht erstellt werden. -sop.error.indirect_data_type.illegal_use_of_env_designator=Besonderer Bezeichner-Präfix '@ENV:' darf nicht für Ausgaben verwendet werden. +sop.error.indirect_data_type.illegal_use_of_env_designator=Besonderer Bezeichner-Präfix '@ENV' darf nicht für Ausgaben verwendet werden. sop.error.indirect_data_type.designator_env_not_supported=Besonderer Bezeichner-Präfix '@ENV' wird nicht unterstützt. sop.error.indirect_data_type.designator_fd_not_supported=Besonderer Bezeichner-Präfix '@FD' wird nicht unterstützt. ## Runtime Errors From c32ef9830b3392b5b569e78a1491b3bbc8f3faca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:41:37 +0100 Subject: [PATCH 027/444] Properly throw CannotDecrypt exception with error message --- .../main/java/sop/cli/picocli/commands/DecryptCmd.java | 3 +++ sop-java-picocli/src/main/resources/msg_sop.properties | 1 + sop-java-picocli/src/main/resources/msg_sop_de.properties | 1 + sop-java/src/main/java/sop/exception/SOPGPException.java | 8 ++++++++ 4 files changed, 13 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index f28af58..0b309bc 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -106,6 +106,9 @@ public class DecryptCmd extends AbstractSopCmd { } catch (SOPGPException.BadData badData) { String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); throw new SOPGPException.BadData(errorMsg, badData); + } catch (SOPGPException.CannotDecrypt e) { + String errorMsg = getMsg("sop.error.runtime.cannot_decrypt_message"); + throw new SOPGPException.CannotDecrypt(errorMsg, e); } catch (IOException ioException) { throw new RuntimeException(ioException); } diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index d01e67f..fce60c5 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -64,6 +64,7 @@ sop.error.runtime.key_cannot_sign=Secret key from input '%s' cannot sign. sop.error.runtime.cert_cannot_encrypt=Certificate from input '%s' cannot encrypt. sop.error.runtime.no_session_key_extracted=Session key not extracted. Feature potentially not supported. sop.error.runtime.no_verifiable_signature_found=No verifiable signature found. +sop.error.runtime.cannot_decrypt_message=Message could not be decrypted. ## Usage errors sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption. sop.error.usage.argument_required=Argument '%s' is required. diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index a54fa7e..acbfb68 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -64,6 +64,7 @@ sop.error.runtime.key_cannot_sign=Privater Schl sop.error.runtime.cert_cannot_encrypt=Zertifikat aus Eingabe '%s' kann nicht verschlüsseln. sop.error.runtime.no_session_key_extracted=Nachrichtenschlüssel nicht extrahiert. Funktion wird möglicherweise nicht unterstützt. sop.error.runtime.no_verifiable_signature_found=Keine gültigen Signaturen gefunden. +sop.error.runtime.cannot_decrypt_message=Nachricht konnte nicht entschlüsselt werden. ## Usage errors sop.error.usage.password_or_cert_required=Es wird mindestens ein Passwort und/oder Zertifikat zur Verschlüsselung benötigt. sop.error.usage.argument_required=Argument '%s' ist erforderlich. diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index eca2476..4d8c9f6 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -119,6 +119,14 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 29; + public CannotDecrypt() { + + } + + public CannotDecrypt(String errorMsg, Throwable e) { + super(errorMsg, e); + } + @Override public int getExitCode() { return EXIT_CODE; From fd4c22cde6800a485461f61475162326eef6923e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:41:52 +0100 Subject: [PATCH 028/444] Add --stacktrace option --- .../java/sop/cli/picocli/SOPExecutionExceptionHandler.java | 7 ++++++- sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java index c0cabea..f6906ff 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java @@ -19,8 +19,13 @@ public class SOPExecutionExceptionHandler implements CommandLine.IExecutionExcep // CHECKSTYLE:OFF if (ex.getMessage() != null) { commandLine.getErr().println(colorScheme.errorText(ex.getMessage())); + } else { + commandLine.getErr().println(ex.getClass().getName()); + } + + if (SopCLI.stacktrace) { + ex.printStackTrace(commandLine.getErr()); } - ex.printStackTrace(commandLine.getErr()); // CHECKSTYLE:ON return exitCode; diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 4e3d587..2019f93 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -52,6 +52,10 @@ public class SopCLI { public static String EXECUTABLE_NAME = "sop"; + @CommandLine.Option(names = {"--stacktrace"}, description = "Print Stacktrace", + scope = CommandLine.ScopeType.INHERIT) + static boolean stacktrace; + public static void main(String[] args) { int exitCode = execute(args); if (exitCode != 0) { From 1aaac4e4b9bcbd65e21b84bb15595c094f556fb8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:43:22 +0100 Subject: [PATCH 029/444] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30293c6..80b3325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0 ## 4.0.2-SNAPSHOT - Fix: `verify`: Do not include detached signature in list of certificates - Fix: `inline-verify`: Also include the first argument in list of certificates +- Hide stacktraces by default and add `--stacktrace` option to print them +- Properly throw `CannotDecrypt` exception when message could not be decrypted ## 4.0.1 - Use shared resources for i18n From 60ef0a15a88d6b74ff0576d67948585965eee042 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:56:36 +0100 Subject: [PATCH 030/444] SOP-Java 4.0.2 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b3325..20fe750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.2-SNAPSHOT +## 4.0.2 - Fix: `verify`: Do not include detached signature in list of certificates - Fix: `inline-verify`: Also include the first argument in list of certificates - Hide stacktraces by default and add `--stacktrace` option to print them diff --git a/version.gradle b/version.gradle index f24e9f0..8e84d17 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.2' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 46e62e9e4076d3020e25e0afec1ea21fcb9db2c9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 14:58:18 +0100 Subject: [PATCH 031/444] SOP-Java 4.0.3-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 8e84d17..7681213 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.2' - isSnapshot = false + shortVersion = '4.0.3' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 4ba864e1cdbedb036f6ebd838c6b0dc4703c3713 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 15:12:00 +0100 Subject: [PATCH 032/444] Bump asciidoctor gradle plugin to 3.3.2 --- sop-java-picocli/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 9cbd1ef..8ff1d87 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -4,7 +4,7 @@ plugins { id 'application' - id 'org.asciidoctor.jvm.convert' version '3.1.0' + id 'org.asciidoctor.jvm.convert' version '3.3.2' } dependencies { From 40919be3f78cef1418ced1769d8c72af185155e5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 19:02:37 +0100 Subject: [PATCH 033/444] sop decrypt: rename --verify-out to --verifications-out --verify-out is still available as an alias. See https://gitlab.com/dkg/openpgp-stateless-cli/-/issues/55 --- .../src/main/java/sop/cli/picocli/commands/DecryptCmd.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index 0b309bc..08c8d22 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -33,7 +33,7 @@ public class DecryptCmd extends AbstractSopCmd { private static final String OPT_NOT_BEFORE = "--not-before"; private static final String OPT_NOT_AFTER = "--not-after"; private static final String OPT_SESSION_KEY_OUT = "--session-key-out"; - private static final String OPT_VERIFY_OUT = "--verify-out"; + private static final String OPT_VERIFICATIONS_OUT = "--verifications-out"; private static final String OPT_VERIFY_WITH = "--verify-with"; private static final String OPT_WITH_KEY_PASSWORD = "--with-key-password"; @@ -53,7 +53,7 @@ public class DecryptCmd extends AbstractSopCmd { paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); - @CommandLine.Option(names = {OPT_VERIFY_OUT}, + @CommandLine.Option(names = {OPT_VERIFICATIONS_OUT, "--verify-out"}, // TODO: Remove --verify-out at some point paramLabel = "VERIFICATIONS") String verifyOut; @@ -94,7 +94,7 @@ public class DecryptCmd extends AbstractSopCmd { setDecryptWith(keys, decrypt); if (verifyOut != null && certs.isEmpty()) { - String errorMsg = getMsg("sop.error.usage.option_requires_other_option", OPT_VERIFY_OUT, OPT_VERIFY_WITH); + String errorMsg = getMsg("sop.error.usage.option_requires_other_option", OPT_VERIFICATIONS_OUT, OPT_VERIFY_WITH); throw new SOPGPException.IncompleteVerification(errorMsg); } From dd5d790e211639c063a5019324f21e31d0769d54 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 20:22:40 +0100 Subject: [PATCH 034/444] Properly format session keys for --session-key-out --- .../src/main/java/sop/cli/picocli/commands/DecryptCmd.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index 08c8d22..e6ac82a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -137,7 +138,8 @@ public class DecryptCmd extends AbstractSopCmd { } else { SessionKey sessionKey = result.getSessionKey().get(); outputStream.write(sessionKey.getAlgorithm()); - outputStream.write(sessionKey.getKey()); + // noinspection CharsetObjectCanBeUsed + outputStream.write(sessionKey.toString().getBytes(Charset.forName("UTF8"))); } } } From aa953428ee7c3cb8ebbbe251141352c7632aa53e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 6 Nov 2022 20:23:01 +0100 Subject: [PATCH 035/444] Be less finnicky about session key formats --- .../sop/cli/picocli/commands/DecryptCmd.java | 22 +++++++++---------- sop-java/src/main/java/sop/SessionKey.java | 3 ++- .../test/java/sop/util/SessionKeyTest.java | 8 +++++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index e6ac82a..a7ebb73 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -175,24 +175,22 @@ public class DecryptCmd extends AbstractSopCmd { } private void setWithSessionKeys(List withSessionKey, Decrypt decrypt) { - Pattern sessionKeyPattern = Pattern.compile("^\\d+:[0-9A-F]+$"); for (String sessionKeyFile : withSessionKey) { - String sessionKey; + String sessionKeyString; try { - sessionKey = stringFromInputStream(getInput(sessionKeyFile)); + sessionKeyString = stringFromInputStream(getInput(sessionKeyFile)); } catch (IOException e) { throw new RuntimeException(e); } - if (!sessionKeyPattern.matcher(sessionKey).matches()) { - String errorMsg = getMsg("sop.error.input.malformed_session_key"); - throw new IllegalArgumentException(errorMsg); - } - String[] split = sessionKey.split(":"); - byte algorithm = (byte) Integer.parseInt(split[0]); - byte[] key = HexUtil.hexToBytes(split[1]); - + SessionKey sessionKey; try { - decrypt.withSessionKey(new SessionKey(algorithm, key)); + sessionKey = SessionKey.fromString(sessionKeyString); + } catch (IllegalArgumentException e) { + String errorMsg = getMsg("sop.error.input.malformed_session_key"); + throw new IllegalArgumentException(errorMsg, e); + } + try { + decrypt.withSessionKey(sessionKey); } catch (SOPGPException.UnsupportedOption unsupportedOption) { String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_SESSION_KEY); throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); diff --git a/sop-java/src/main/java/sop/SessionKey.java b/sop-java/src/main/java/sop/SessionKey.java index 2adcec4..3997032 100644 --- a/sop-java/src/main/java/sop/SessionKey.java +++ b/sop-java/src/main/java/sop/SessionKey.java @@ -12,7 +12,7 @@ import sop.util.HexUtil; public class SessionKey { - private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9a-fA-F]+)$"); + private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9A-F]+)$"); private final byte algorithm; private final byte[] sessionKey; @@ -62,6 +62,7 @@ public class SessionKey { } public static SessionKey fromString(String string) { + string = string.trim().toUpperCase().replace("\n", ""); Matcher matcher = PATTERN.matcher(string); if (!matcher.matches()) { throw new IllegalArgumentException("Provided session key does not match expected format."); diff --git a/sop-java/src/test/java/sop/util/SessionKeyTest.java b/sop-java/src/test/java/sop/util/SessionKeyTest.java index 2891d0d..db7e89f 100644 --- a/sop-java/src/test/java/sop/util/SessionKeyTest.java +++ b/sop-java/src/test/java/sop/util/SessionKeyTest.java @@ -20,6 +20,14 @@ public class SessionKeyTest { assertEquals(string, sessionKey.toString()); } + @Test + public void fromLowerStringTest() { + String string = "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"; + String lowercaseWithTrailingNewLine = "9:fca4beaf687f48059cacc14fb019125cd57392bab7037c707835925cbf9f7bcd\n"; + SessionKey sessionKey = SessionKey.fromString(lowercaseWithTrailingNewLine); + assertEquals(string, sessionKey.toString()); + } + @Test public void toStringTest() { SessionKey sessionKey = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD")); From 8494f413e63e01614c0a381ec9c6fb0821793f84 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 01:17:54 +0100 Subject: [PATCH 036/444] inline-verify: flush printwriter of --verifications-out --- .../src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index 05e2c8a..38ad975 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -98,6 +98,8 @@ public class InlineVerifyCmd extends AbstractSopCmd { pw.println(verification); // CHECKSTYLE:ON } + pw.flush(); + pw.close(); } catch (IOException e) { throw new RuntimeException(e); } From f60e14de96623cc1cfb893e83b1a29ce5b80bb58 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 01:18:51 +0100 Subject: [PATCH 037/444] Bump gradlew to 7.2 --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 269 ++++++++++++++--------- 2 files changed, 160 insertions(+), 111 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69a9715..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882..1b6c787 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" From 5cde383b1ab0b6fde28d1750e542dcbf0c8dfb6b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 15:32:54 +0100 Subject: [PATCH 038/444] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20fe750..41d82bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.3-SNAPSHOT +- `decrypt`: Rename option `--verify-out` to `--verifications-out`, but keep `--verify-out` as alias +- Fix: `decrypt`: Flush output stream in order to prevent empty file as result of `--session-key-out` +- Fix: Properly format session key for `--session-key-out` +- Be less finicky about input session key formats + - Allow upper- and lowercase hexadecimal keys + - Allow trailing whitespace + ## 4.0.2 - Fix: `verify`: Do not include detached signature in list of certificates - Fix: `inline-verify`: Also include the first argument in list of certificates From 9ea9cd22b8a1b437d5febe4aa2851061678137c0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:20:37 +0100 Subject: [PATCH 039/444] Fix writing out of session key - again --- .../sop/cli/picocli/commands/DecryptCmd.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index a7ebb73..7b83844 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -12,17 +12,14 @@ import sop.Verification; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; import sop.operation.Decrypt; -import sop.util.HexUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.regex.Pattern; @CommandLine.Command(name = "decrypt", resourceBundle = "msg_decrypt", @@ -130,18 +127,20 @@ public class DecryptCmd extends AbstractSopCmd { } private void writeSessionKeyOut(DecryptionResult result) throws IOException { - if (sessionKeyOut != null) { - try (OutputStream outputStream = getOutput(sessionKeyOut)) { - if (!result.getSessionKey().isPresent()) { - String errorMsg = getMsg("sop.error.runtime.no_session_key_extracted"); - throw new SOPGPException.UnsupportedOption(String.format(errorMsg, OPT_SESSION_KEY_OUT)); - } else { - SessionKey sessionKey = result.getSessionKey().get(); - outputStream.write(sessionKey.getAlgorithm()); - // noinspection CharsetObjectCanBeUsed - outputStream.write(sessionKey.toString().getBytes(Charset.forName("UTF8"))); - } + if (sessionKeyOut == null) { + return; + } + try (OutputStream outputStream = getOutput(sessionKeyOut)) { + if (!result.getSessionKey().isPresent()) { + String errorMsg = getMsg("sop.error.runtime.no_session_key_extracted"); + throw new SOPGPException.UnsupportedOption(String.format(errorMsg, OPT_SESSION_KEY_OUT)); } + SessionKey sessionKey = result.getSessionKey().get(); + PrintWriter writer = new PrintWriter(outputStream); + // CHECKSTYLE:OFF + writer.println(sessionKey.toString()); + // CHECKSTYLE:ON + writer.flush(); } } From dad75bb5222e7d30392a7d3326e54915eb657b20 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:21:01 +0100 Subject: [PATCH 040/444] Clean up toString() method --- sop-java/src/main/java/sop/SessionKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java/src/main/java/sop/SessionKey.java b/sop-java/src/main/java/sop/SessionKey.java index 3997032..722666d 100644 --- a/sop-java/src/main/java/sop/SessionKey.java +++ b/sop-java/src/main/java/sop/SessionKey.java @@ -75,6 +75,6 @@ public class SessionKey { @Override public String toString() { - return "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey); + return Integer.toString(getAlgorithm()) + ':' + HexUtil.bytesToHex(sessionKey); } } From 137d2e7f85063b24fc86041507f654ad73f6396b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:21:34 +0100 Subject: [PATCH 041/444] Add Verification.fromString(string), equals(other) and hashCode() --- sop-java/src/main/java/sop/Verification.java | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index 2047c3d..6ff63f6 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -20,6 +20,15 @@ public class Verification { this.signingCertFingerprint = signingCertFingerprint; } + public static Verification fromString(String toString) { + String[] split = toString.trim().split(" "); + if (split.length != 3) { + throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint'"); + } + + return new Verification(UTCUtil.parseUTCDate(split[0]), split[1], split[2]); + } + /** * Return the signatures' creation time. * @@ -55,4 +64,24 @@ public class Verification { ' ' + getSigningCertFingerprint(); } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } + if (!(obj instanceof Verification)) { + return false; + } + Verification other = (Verification) obj; + return toString().equals(other.toString()); + } } From 7326d3cf85867f8599f9b241bf658ff2ffcff581 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:21:50 +0100 Subject: [PATCH 042/444] Add tests for writing out of session key and verifications --- .../cli/picocli/commands/DecryptCmdTest.java | 85 +++++++++++-------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index 3da2d09..411f0db 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -4,28 +4,6 @@ package sop.cli.picocli.commands; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.Date; - import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,6 +21,26 @@ import sop.operation.Decrypt; import sop.util.HexUtil; import sop.util.UTCUtil; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class DecryptCmdTest { private Decrypt decrypt; @@ -189,24 +187,32 @@ public class DecryptCmdTest { } @Test - public void assertSessionKeyIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { - byte[] key = "C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137".getBytes(StandardCharsets.UTF_8); + public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { + Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); + String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; + String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; + Verification verification = new Verification(signDate, keyFP, certFP); + SessionKey sessionKey = SessionKey.fromString("9:C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"); when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult() { @Override public DecryptionResult writeTo(OutputStream outputStream) { return new DecryptionResult( - new SessionKey((byte) 9, key), - Collections.emptyList() + sessionKey, + Collections.singletonList(verification) ); } }); Path tempDir = Files.createTempDirectory("session-key-out-dir"); - File tempFile = new File(tempDir.toFile(), "session-key"); - tempFile.deleteOnExit(); - SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()}); + File sessionKeyFile = new File(tempDir.toFile(), "session-key"); + sessionKeyFile.deleteOnExit(); + File verificationsFile = new File(tempDir.toFile(), "verifications"); + File keyFile = new File(tempDir.toFile(), "key.asc"); + keyFile.createNewFile(); + SopCLI.main(new String[] {"decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), + "--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", keyFile.getAbsolutePath()}); ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream(); - try (FileInputStream fileIn = new FileInputStream(tempFile)) { + try (FileInputStream fileIn = new FileInputStream(sessionKeyFile)) { byte[] buf = new byte[32]; int read = fileIn.read(buf); while (read != -1) { @@ -215,10 +221,21 @@ public class DecryptCmdTest { } } - byte[] algAndKey = new byte[key.length + 1]; - algAndKey[0] = (byte) 9; - System.arraycopy(key, 0, algAndKey, 1, key.length); - assertArrayEquals(algAndKey, bytesInFile.toByteArray()); + SessionKey parsedSessionKey = SessionKey.fromString(bytesInFile.toString()); + assertEquals(sessionKey, parsedSessionKey); + + bytesInFile = new ByteArrayOutputStream(); + try (FileInputStream fileIn = new FileInputStream(verificationsFile)) { + byte[] buf = new byte[32]; + int read = fileIn.read(buf); + while (read != -1) { + bytesInFile.write(buf, 0, read); + read = fileIn.read(buf); + } + } + + Verification parsedVerification = Verification.fromString(bytesInFile.toString()); + assertEquals(verification, parsedVerification); } @Test From 76cc9098cee37e44b156eead93300be061455917 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:44:33 +0100 Subject: [PATCH 043/444] SOP-Java 4.0.3 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d82bc..2552254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.3-SNAPSHOT +## 4.0.3 - `decrypt`: Rename option `--verify-out` to `--verifications-out`, but keep `--verify-out` as alias - Fix: `decrypt`: Flush output stream in order to prevent empty file as result of `--session-key-out` - Fix: Properly format session key for `--session-key-out` diff --git a/version.gradle b/version.gradle index 7681213..5a947e9 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.3' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 6f20f78339fca29d706174681137da52a83f02bb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 16:46:38 +0100 Subject: [PATCH 044/444] SOP-Java 4.0.5-SNAPSHOT 4.0.4 Not Found --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 5a947e9..6fe23f8 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.3' - isSnapshot = false + shortVersion = '4.0.5' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 01beb99e435e76f88e55b030260cc9982d3d0ecc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 7 Nov 2022 17:26:05 +0100 Subject: [PATCH 045/444] Add packaging badges to README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bdb0b72..dc095f6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/sop-java)](https://search.maven.org/artifact/org.pgpainless/sop-java) [![Spec Revision: 4](https://img.shields.io/badge/Spec%20Revision-4-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/04/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) @@ -16,6 +15,9 @@ The [Stateless OpenPGP Protocol](https://datatracker.ietf.org/doc/draft-dkg-open defines a generic stateless CLI for dealing with OpenPGP messages. Its goal is to provide a minimal, yet powerful API for the most common OpenPGP related operations. +[![Packaging status](https://repology.org/badge/vertical-allrepos/sop-java.svg)](https://repology.org/project/pgpainless/versions) +[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/sop-java)](https://search.maven.org/artifact/org.pgpainless/sop-java) + ## Modules The repository contains the following modules: From c00109c2bfd23b4e646b5e809463809d2884f3aa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 12:47:11 +0100 Subject: [PATCH 046/444] Rename InlineSignAs.CleartextSigned to clearsigned, make options lowercase Fixes #16 --- sop-java/src/main/java/sop/enums/InlineSignAs.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sop-java/src/main/java/sop/enums/InlineSignAs.java b/sop-java/src/main/java/sop/enums/InlineSignAs.java index b0b55dc..c1097df 100644 --- a/sop-java/src/main/java/sop/enums/InlineSignAs.java +++ b/sop-java/src/main/java/sop/enums/InlineSignAs.java @@ -9,16 +9,16 @@ public enum InlineSignAs { /** * Signature is made over the binary message. */ - Binary, + binary, /** * Signature is made over the message in text mode. */ - Text, + text, /** * Signature is made using the Cleartext Signature Framework. */ - CleartextSigned, + clearsigned, } From 82456ec9e16e8a4d4532d18748725775e129d9cf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 12:47:28 +0100 Subject: [PATCH 047/444] Update CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2552254..a52d519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.5-SNAPSHOT +- `inline-sign`: Make possible values of `--as` option lowercase +- `inline-sign`: Rename value `cleartextsigned` of option `--as` to `clearsigned` + +## 4.0.4 +- Not found + ## 4.0.3 - `decrypt`: Rename option `--verify-out` to `--verifications-out`, but keep `--verify-out` as alias - Fix: `decrypt`: Flush output stream in order to prevent empty file as result of `--session-key-out` From 114ee94f0d0e3164618db4cda78c252e68af2b00 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 13:22:13 +0100 Subject: [PATCH 048/444] SOP-Java 4.0.5 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52d519..19794aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.5-SNAPSHOT +## 4.0.5 - `inline-sign`: Make possible values of `--as` option lowercase - `inline-sign`: Rename value `cleartextsigned` of option `--as` to `clearsigned` diff --git a/version.gradle b/version.gradle index 6fe23f8..0485d4b 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.5' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 3ee42ea2edde73c4c90f8e82972289e3659c65e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 13:24:07 +0100 Subject: [PATCH 049/444] SOP-Java 4.0.6-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 0485d4b..39e9a79 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.5' - isSnapshot = false + shortVersion = '4.0.6' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 4bc45a06924cb09961b489b473f2f2f737a61ce1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 15:56:33 +0100 Subject: [PATCH 050/444] Add support for using file descriptors as input and output Fixes #12 --- .../cli/picocli/commands/AbstractSopCmd.java | 41 ++++++++++++++++--- .../src/main/resources/msg_sop.properties | 1 + .../src/main/resources/msg_sop_de.properties | 1 + .../picocli/commands/AbstractSopCmdTest.java | 4 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index 9cca658..c64922f 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -13,6 +13,7 @@ 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; @@ -21,6 +22,7 @@ import java.util.Collection; import java.util.Date; import java.util.Locale; import java.util.ResourceBundle; +import java.util.regex.Pattern; public abstract class AbstractSopCmd implements Runnable { @@ -40,6 +42,8 @@ public abstract class AbstractSopCmd implements Runnable { public static final Date BEGINNING_OF_TIME = new Date(0); public static final Date END_OF_TIME = new Date(8640000000000000L); + public static final Pattern PATTERN_FD = Pattern.compile("^\\d{1,3}$"); + protected final ResourceBundle messages; protected EnvironmentVariableResolver envResolver = System::getenv; @@ -141,9 +145,14 @@ public abstract class AbstractSopCmd implements Runnable { throw new SOPGPException.AmbiguousInput(errorMsg); } - String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); - throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); - + File fdFile = fileDescriptorFromString(trimmed); + try { + FileInputStream fileIn = new FileInputStream(fdFile); + return fileIn; + } catch (FileNotFoundException e) { + String errorMsg = getMsg("sop.error.indirect_data_type.file_descriptor_not_found", fdFile.getAbsolutePath()); + throw new IOException(errorMsg, e); + } } else { File file = new File(trimmed); if (!file.exists()) { @@ -176,9 +185,16 @@ public abstract class AbstractSopCmd implements Runnable { throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); } + // File Descriptor if (trimmed.startsWith(PRFX_FD)) { - String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); - throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); + File fdFile = fileDescriptorFromString(trimmed); + try { + FileOutputStream fout = new FileOutputStream(fdFile); + return fout; + } catch (FileNotFoundException e) { + String errorMsg = getMsg("sop.error.indirect_data_type.file_descriptor_not_found", fdFile.getAbsolutePath()); + throw new IOException(errorMsg, e); + } } File file = new File(trimmed); @@ -194,6 +210,21 @@ public abstract class AbstractSopCmd implements Runnable { return new FileOutputStream(file); } + + public File fileDescriptorFromString(String fdString) { + File fdDir = new File("/dev/fd/"); + if (!fdDir.exists()) { + String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); + throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); + } + String fdNumber = fdString.substring(PRFX_FD.length()); + if (!PATTERN_FD.matcher(fdNumber).matches()) { + throw new IllegalArgumentException("File descriptor must be a 1-3 digit, positive number."); + } + File descriptor = new File(fdDir, fdNumber); + return descriptor; + } + public static String stringFromInputStream(InputStream inputStream) throws IOException { try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index fce60c5..7530994 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -48,6 +48,7 @@ sop.error.input.stdin_not_openpgp_data=Standard Input appears not to contain val sop.error.indirect_data_type.ambiguous_filename=File name '%s' is ambiguous. File with the same name exists on the filesystem. sop.error.indirect_data_type.environment_variable_not_set=Environment variable '%s' not set. sop.error.indirect_data_type.environment_variable_empty=Environment variable '%s' is empty. +sop.error.indirect_data_type.file_descriptor_not_found=File descriptor '%s' not found. sop.error.indirect_data_type.input_file_does_not_exist=Input file '%s' does not exist. sop.error.indirect_data_type.input_not_a_file=Input file '%s' is not a file. sop.error.indirect_data_type.output_file_already_exists=Output file '%s' already exists. diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index acbfb68..6848777 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -48,6 +48,7 @@ sop.error.input.stdin_not_openpgp_data=Standard-Eingabe enth sop.error.indirect_data_type.ambiguous_filename=Dateiname '%s' ist mehrdeutig. Datei mit dem selben Namen existiert im Dateisystem. sop.error.indirect_data_type.environment_variable_not_set=Umgebungsvariable '%s' nicht gesetzt. sop.error.indirect_data_type.environment_variable_empty=Umgebungsvariable '%s' ist leer. +sop.error.indirect_data_type.file_descriptor_not_found=File Descriptor '%s' nicht gefunden. sop.error.indirect_data_type.input_file_does_not_exist=Quelldatei '%s' existiert nicht. sop.error.indirect_data_type.input_not_a_file=Quelldatei '%s' ist keine Datei. sop.error.indirect_data_type.output_file_already_exists=Zieldatei '%s' existiert bereits. diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java index 9f383f5..aed420b 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java @@ -145,8 +145,8 @@ public class AbstractSopCmdTest { } @Test - public void getOutput_fdUnsupportedSpecialPrefix() { - assertThrows(SOPGPException.UnsupportedSpecialPrefix.class, () -> abstractCmd.getOutput("@FD:IS_ILLEGAL")); + public void getOutput_malformedFileDescriptor() { + assertThrows(IllegalArgumentException.class, () -> abstractCmd.getOutput("@FD:IS_ILLEGAL")); } @Test From 1a381becfa17e791e2867069b24742fdc30f9766 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 11 Nov 2022 15:59:41 +0100 Subject: [PATCH 051/444] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19794aa..bfe5772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.6-SNAPSHOT +- Add support for file descriptors on systems where `/dev/fd/` exists + ## 4.0.5 - `inline-sign`: Make possible values of `--as` option lowercase - `inline-sign`: Rename value `cleartextsigned` of option `--as` to `clearsigned` From 00ab68b5044e294878f2489cd0b6a228d1725bab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Nov 2022 13:54:08 +0100 Subject: [PATCH 052/444] Allow larger numbers for file descriptor names --- .../main/java/sop/cli/picocli/commands/AbstractSopCmd.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index c64922f..176d97b 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -42,7 +42,7 @@ public abstract class AbstractSopCmd implements Runnable { public static final Date BEGINNING_OF_TIME = new Date(0); public static final Date END_OF_TIME = new Date(8640000000000000L); - public static final Pattern PATTERN_FD = Pattern.compile("^\\d{1,3}$"); + public static final Pattern PATTERN_FD = Pattern.compile("^\\d{1,20}$"); protected final ResourceBundle messages; protected EnvironmentVariableResolver envResolver = System::getenv; @@ -219,7 +219,7 @@ public abstract class AbstractSopCmd implements Runnable { } String fdNumber = fdString.substring(PRFX_FD.length()); if (!PATTERN_FD.matcher(fdNumber).matches()) { - throw new IllegalArgumentException("File descriptor must be a 1-3 digit, positive number."); + throw new IllegalArgumentException("File descriptor must be a positive number."); } File descriptor = new File(fdDir, fdNumber); return descriptor; From 3a6905c0bd16514b08716ba00540fdd433b761f7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Nov 2022 15:30:31 +0100 Subject: [PATCH 053/444] SOP-Java 4.0.6 --- CHANGELOG.md | 4 ++-- version.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfe5772..f147843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.6-SNAPSHOT -- Add support for file descriptors on systems where `/dev/fd/` exists +## 4.0.6 +- Add support for file descriptors on unix / linux systems ## 4.0.5 - `inline-sign`: Make possible values of `--as` option lowercase diff --git a/version.gradle b/version.gradle index 39e9a79..57cf09a 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.6' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 60f52ab89ee17428adebb2decacaba6b86d8b58d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Nov 2022 15:32:38 +0100 Subject: [PATCH 054/444] SOP-Java 4.0.7-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 57cf09a..fc1ee62 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.6' - isSnapshot = false + shortVersion = '4.0.7' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From ed46adbe52a4b59da1a7dd78af7e046eea6903d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Nov 2022 15:49:06 +0100 Subject: [PATCH 055/444] Add resource string for --stacktrace option --- sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java | 2 +- sop-java-picocli/src/main/resources/msg_sop.properties | 2 ++ sop-java-picocli/src/main/resources/msg_sop_de.properties | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 2019f93..5f8b944 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -52,7 +52,7 @@ public class SopCLI { public static String EXECUTABLE_NAME = "sop"; - @CommandLine.Option(names = {"--stacktrace"}, description = "Print Stacktrace", + @CommandLine.Option(names = {"--stacktrace"}, scope = CommandLine.ScopeType.INHERIT) static boolean stacktrace; diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 7530994..aa56c65 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -34,6 +34,8 @@ usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator alr usage.exitCodeList.18=79:Key is not signing capable ## SHARED RESOURCES +stacktrace=Print stacktrace + ## Malformed Input sop.error.input.malformed_session_key=Session keys are expected in the format 'ALGONUM:HEXKEY'. sop.error.input.not_a_private_key=Input '%s' does not contain an OpenPGP private key. diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 6848777..9e1cbc3 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -34,6 +34,7 @@ usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren ## SHARED RESOURCES +stacktrace=Stacktrace ausgeben ## Malformed Input sop.error.input.malformed_session_key=Nachrichtenschlüssel werden im folgenden Format erwartet: 'ALGONUM:HEXKEY' sop.error.input.not_a_private_key=Eingabe '%s' enthält keinen privaten OpenPGP Schlüssel. From 0ed7163fd5f2a8f4436b06a79e09c031fb4c35fa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Nov 2022 21:29:45 +0100 Subject: [PATCH 056/444] Fix capitalization of @ENV/@FD descriptions --- sop-java-picocli/src/main/resources/msg_sop.properties | 2 +- sop-java-picocli/src/main/resources/msg_sop_de.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index aa56c65..c0f9f67 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -29,7 +29,7 @@ usage.exitCodeList.12=59:Output file already exists usage.exitCodeList.13=61:Input file does not exist usage.exitCodeList.14=67:Cannot unlock password protected secret key usage.exitCodeList.15=69:Unsupported subcommand -usage.exitCodeList.16=71:Unsupported special prefix (e.g. \"@env/@fd\") of indirect parameter +usage.exitCodeList.16=71:Unsupported special prefix (e.g. \"@ENV/@FD\") of indirect parameter usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator already exists) usage.exitCodeList.18=79:Key is not signing capable diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 9e1cbc3..b05c86a 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -29,7 +29,7 @@ usage.exitCodeList.12=59:Ausgabedatei existiert bereits usage.exitCodeList.13=61:Eingabedatei existiert nicht usage.exitCodeList.14=67:Passwort-gesicherter privater Schlüssel kann nicht entsperrt werden usage.exitCodeList.15=69:Nicht unterstützter Unterbefehl -usage.exitCodeList.16=71:Nicht unterstützter Spezialprefix (z.B.. "@env/@fd") von indirektem Parameter +usage.exitCodeList.16=71:Nicht unterstützter Spezialprefix (z.B.. "@ENV/@FD") von indirektem Parameter usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner entspricht, existiert bereits) usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren From 0e777de14f927ad4b265adfc5ba201be29b1bde1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 15 Nov 2022 21:43:43 +0100 Subject: [PATCH 057/444] Make asciiDoctor gradle task generate reproducible output --- sop-java-picocli/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 8ff1d87..664c385 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -64,6 +64,7 @@ task generateManpageAsciiDoc(type: JavaExec) { apply plugin: 'org.asciidoctor.jvm.convert' asciidoctor { + attributes 'reproducible': '' dependsOn(generateManpageAsciiDoc) sourceDir = file("${project.buildDir}/generated-picocli-docs") outputDir = file("${project.buildDir}/docs") @@ -71,4 +72,4 @@ asciidoctor { outputOptions { backends = ['manpage', 'html5'] } -} \ No newline at end of file +} From 519fb891a186e202a2805d29c15c6d01f0785aa2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 18 Nov 2022 15:17:33 +0100 Subject: [PATCH 058/444] Dearmor: transform IOExceptions into BadData properly --- .../java/sop/cli/picocli/commands/DearmorCmd.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java index a4fca99..f73e351 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java @@ -28,6 +28,19 @@ public class DearmorCmd extends AbstractSopCmd { String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); throw new SOPGPException.BadData(errorMsg, e); } catch (IOException e) { + String msg = e.getMessage(); + if (msg == null) { + throw new RuntimeException(e); + } + + String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); + if (msg.equals("invalid armor") || + msg.equals("invalid armor header") || + msg.equals("inconsistent line endings in headers") || + msg.startsWith("unable to decode base64 data")) { + throw new SOPGPException.BadData(errorMsg, e); + } + throw new RuntimeException(e); } } From 62d3ffd9ae6d806c5d0774436205e19b5ef37331 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Nov 2022 20:10:47 +0100 Subject: [PATCH 059/444] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f147843..090dab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.7-SNAPSHOT +- Make i18n string for `--stacktrace` option translatable +- Make manpages generation reproducible +- `dearmor`: Transform `IOException` into `BadData` + ## 4.0.6 - Add support for file descriptors on unix / linux systems From 55f196b241fd9b706a5e80d093785b19229a6719 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Nov 2022 20:16:14 +0100 Subject: [PATCH 060/444] SOP-Java 4.0.7 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index fc1ee62..278b4be 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.0.7' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From ae128d2cbbf2dbe7322bd4865de2378e37364ffa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Nov 2022 20:18:29 +0100 Subject: [PATCH 061/444] SOP-Java 4.0.8-SNAPSHOT --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 090dab7..f1d65c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.7-SNAPSHOT +## 4.0.7 - Make i18n string for `--stacktrace` option translatable - Make manpages generation reproducible - `dearmor`: Transform `IOException` into `BadData` diff --git a/version.gradle b/version.gradle index 278b4be..0cbaf74 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.7' - isSnapshot = false + shortVersion = '4.0.8' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 junitVersion = '5.8.2' From 2328bdf6af6d3bf42602b53a9fdb2c103a22e92d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 13 Dec 2022 17:06:35 +0100 Subject: [PATCH 062/444] Fix parameter label of --as=clearsigned --- .../src/main/java/sop/cli/picocli/commands/InlineSignCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 476817b..2608783 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -26,7 +26,7 @@ public class InlineSignCmd extends AbstractSopCmd { boolean armor = true; @CommandLine.Option(names = "--as", - paramLabel = "{binary|text|cleartextsigned}") + paramLabel = "{binary|text|clearsigned}") InlineSignAs type; @CommandLine.Parameters(paramLabel = "KEYS") From 2c3717157a662432be202ff385a3b66dfcc61669 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 5 Jan 2023 01:42:31 +0100 Subject: [PATCH 063/444] Bump gradlew to 7.5 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ffed3a2..8049c68 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From d9708e882dd0b3e7b75df23f459c15b83f01d022 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 14:03:12 +0100 Subject: [PATCH 064/444] decrypt: rename --not-after, --not-before to --verify-not-after, --verify-not-before --- .../java/sop/cli/picocli/commands/DecryptCmd.java | 10 +++++----- .../sop/cli/picocli/commands/DecryptCmdTest.java | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index 7b83844..c0fca9a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -26,14 +26,14 @@ import java.util.List; exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DecryptCmd extends AbstractSopCmd { + private static final String OPT_SESSION_KEY_OUT = "--session-key-out"; private static final String OPT_WITH_SESSION_KEY = "--with-session-key"; private static final String OPT_WITH_PASSWORD = "--with-password"; - private static final String OPT_NOT_BEFORE = "--not-before"; - private static final String OPT_NOT_AFTER = "--not-after"; - private static final String OPT_SESSION_KEY_OUT = "--session-key-out"; - private static final String OPT_VERIFICATIONS_OUT = "--verifications-out"; - private static final String OPT_VERIFY_WITH = "--verify-with"; private static final String OPT_WITH_KEY_PASSWORD = "--with-key-password"; + private static final String OPT_VERIFICATIONS_OUT = "--verifications-out"; // see SOP-05 + private static final String OPT_VERIFY_WITH = "--verify-with"; + private static final String OPT_NOT_BEFORE = "--verify-not-before"; + private static final String OPT_NOT_AFTER = "--verify-not-after"; @CommandLine.Option( diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index 411f0db..b445a6d 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -123,7 +123,7 @@ public class DecryptCmdTest { @Test public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"decrypt", "--not-before", "-", "--not-after", "-"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-before", "-", "--verify-not-after", "-"}); verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME); } @@ -136,7 +136,7 @@ public class DecryptCmdTest { return Math.abs(now.getTime() - argument.getTime()) <= 1000; }; - SopCLI.main(new String[] {"decrypt", "--not-before", "now", "--not-after", "now"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now", "--verify-not-after", "now"}); verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff)); verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff)); } @@ -145,28 +145,28 @@ public class DecryptCmdTest { @ExpectSystemExitWithStatus(1) public void assertMalformedDateInNotBeforeCausesExit1() { // ParserException causes exit(1) - SopCLI.main(new String[] {"decrypt", "--not-before", "invalid"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-before", "invalid"}); } @Test @ExpectSystemExitWithStatus(1) public void assertMalformedDateInNotAfterCausesExit1() { // ParserException causes exit(1) - SopCLI.main(new String[] {"decrypt", "--not-after", "invalid"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-after", "invalid"}); } @Test @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption { when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); - SopCLI.main(new String[] {"decrypt", "--not-after", "now"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-after", "now"}); } @Test @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption { when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); - SopCLI.main(new String[] {"decrypt", "--not-before", "now"}); + SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now"}); } @Test From 14c665565ed554b8647590d98aa86a47570b6f77 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 15:52:16 +0100 Subject: [PATCH 065/444] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d65c3..8714ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.0.8-SNAPSHOT +- `decrypt`: Rename `--not-before`, `--not-after` to `--verify-not-before`, `--verify-not-after` +- `inline-sign`: Fix parameter label of `--as=clearsigned` + ## 4.0.7 - Make i18n string for `--stacktrace` option translatable - Make manpages generation reproducible From ff5f98e8ee5d18c3289935259fc74f285888a213 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 17:40:51 +0100 Subject: [PATCH 066/444] sop decrypt: Throw NoSignature if no verifiable signature found --- .../src/main/java/sop/cli/picocli/commands/DecryptCmd.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index c0fca9a..cde7c50 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -101,6 +101,7 @@ public class DecryptCmd extends AbstractSopCmd { DecryptionResult result = ready.writeTo(System.out); writeSessionKeyOut(result); writeVerifyOut(result); + } catch (SOPGPException.BadData badData) { String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); throw new SOPGPException.BadData(errorMsg, badData); @@ -114,6 +115,11 @@ public class DecryptCmd extends AbstractSopCmd { private void writeVerifyOut(DecryptionResult result) throws IOException { if (verifyOut != null) { + if (result.getVerifications().isEmpty()) { + String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); + throw new SOPGPException.NoSignature(errorMsg); + } + try (OutputStream fileOut = getOutput(verifyOut)) { PrintWriter writer = new PrintWriter(fileOut); for (Verification verification : result.getVerifications()) { From eddcc11c991d85a0c8c44cf7f5748bba6059bc54 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 17:45:15 +0100 Subject: [PATCH 067/444] Update changelgo --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8714ca1..90f0939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ SPDX-License-Identifier: Apache-2.0 ## 4.0.8-SNAPSHOT - `decrypt`: Rename `--not-before`, `--not-after` to `--verify-not-before`, `--verify-not-after` +- `decrypt`: Throw `NoSignature` error if no verifiable signature found, but signature verification is requested using `--verify-with`. - `inline-sign`: Fix parameter label of `--as=clearsigned` ## 4.0.7 From 3bc19b27ade1b4e6fba5bd4b7b96eae95092d80a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 18:50:00 +0100 Subject: [PATCH 068/444] toString() of enum options: Ensure lowercase --- sop-java/src/main/java/sop/enums/ArmorLabel.java | 8 +++++++- sop-java/src/main/java/sop/enums/EncryptAs.java | 8 +++++++- sop-java/src/main/java/sop/enums/SignAs.java | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/sop-java/src/main/java/sop/enums/ArmorLabel.java b/sop-java/src/main/java/sop/enums/ArmorLabel.java index aeaa6f9..bb97e84 100644 --- a/sop-java/src/main/java/sop/enums/ArmorLabel.java +++ b/sop-java/src/main/java/sop/enums/ArmorLabel.java @@ -9,5 +9,11 @@ public enum ArmorLabel { Sig, Key, Cert, - Message + Message, + ; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } } diff --git a/sop-java/src/main/java/sop/enums/EncryptAs.java b/sop-java/src/main/java/sop/enums/EncryptAs.java index 85a2cd7..7e7d4d1 100644 --- a/sop-java/src/main/java/sop/enums/EncryptAs.java +++ b/sop-java/src/main/java/sop/enums/EncryptAs.java @@ -6,5 +6,11 @@ package sop.enums; public enum EncryptAs { Binary, - Text + Text, + ; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } } diff --git a/sop-java/src/main/java/sop/enums/SignAs.java b/sop-java/src/main/java/sop/enums/SignAs.java index f7f3671..1174098 100644 --- a/sop-java/src/main/java/sop/enums/SignAs.java +++ b/sop-java/src/main/java/sop/enums/SignAs.java @@ -13,5 +13,11 @@ public enum SignAs { /** * Signature is made over the message in text mode. */ - Text + Text, + ; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } } From ed296ec4b2f7f088543818b0e14ad94d46c89c03 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 18:52:13 +0100 Subject: [PATCH 069/444] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f0939..0d73b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ SPDX-License-Identifier: Apache-2.0 - `decrypt`: Rename `--not-before`, `--not-after` to `--verify-not-before`, `--verify-not-after` - `decrypt`: Throw `NoSignature` error if no verifiable signature found, but signature verification is requested using `--verify-with`. - `inline-sign`: Fix parameter label of `--as=clearsigned` +- `ArmorLabel`, `EncryptAs`, `SignAs`: make `toString()` return lowercase ## 4.0.7 - Make i18n string for `--stacktrace` option translatable From 28912618ea0ef0afdd35c89ea61714361e532344 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 5 Jan 2023 03:06:22 +0100 Subject: [PATCH 070/444] Initial work on binary-sop This module is intended to allow the use of SOP command line applications such as sqop, pgpainless-sop, etc. as drop-ins for sop-java. --- binary-sop/build.gradle | 24 +++++ .../src/main/java/sop/binary/BinarySop.java | 91 +++++++++++++++++++ .../binary/operation/BinaryExtractCert.java | 69 ++++++++++++++ .../binary/operation/BinaryGenerateKey.java | 89 ++++++++++++++++++ .../sop/binary/operation/BinaryVersion.java | 87 ++++++++++++++++++ .../sop/binary/operation/package-info.java | 8 ++ .../main/java/sop/binary/package-info.java | 8 ++ .../test/java/sop/binary/BinarySopTest.java | 67 ++++++++++++++ settings.gradle | 3 +- 9 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 binary-sop/build.gradle create mode 100644 binary-sop/src/main/java/sop/binary/BinarySop.java create mode 100644 binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java create mode 100644 binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java create mode 100644 binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java create mode 100644 binary-sop/src/main/java/sop/binary/operation/package-info.java create mode 100644 binary-sop/src/main/java/sop/binary/package-info.java create mode 100644 binary-sop/src/test/java/sop/binary/BinarySopTest.java diff --git a/binary-sop/build.gradle b/binary-sop/build.gradle new file mode 100644 index 0000000..85b6bc2 --- /dev/null +++ b/binary-sop/build.gradle @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id 'java-library' +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + api project(":sop-java") +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/binary-sop/src/main/java/sop/binary/BinarySop.java b/binary-sop/src/main/java/sop/binary/BinarySop.java new file mode 100644 index 0000000..c048480 --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/BinarySop.java @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.binary; + +import sop.SOP; +import sop.binary.operation.BinaryExtractCert; +import sop.binary.operation.BinaryGenerateKey; +import sop.binary.operation.BinaryVersion; +import sop.operation.Armor; +import sop.operation.Dearmor; +import sop.operation.Decrypt; +import sop.operation.DetachedSign; +import sop.operation.DetachedVerify; +import sop.operation.Encrypt; +import sop.operation.ExtractCert; +import sop.operation.GenerateKey; +import sop.operation.InlineDetach; +import sop.operation.InlineSign; +import sop.operation.InlineVerify; +import sop.operation.Version; + +public class BinarySop implements SOP { + + private final String binaryName; + + public BinarySop(String binaryName) { + this.binaryName = binaryName; + } + + @Override + public Version version() { + return new BinaryVersion(binaryName); + } + + @Override + public GenerateKey generateKey() { + return new BinaryGenerateKey(binaryName); + } + + @Override + public ExtractCert extractCert() { + return new BinaryExtractCert(binaryName); + } + + @Override + public DetachedSign detachedSign() { + return null; + } + + @Override + public InlineSign inlineSign() { + return null; + } + + @Override + public DetachedVerify detachedVerify() { + return null; + } + + @Override + public InlineVerify inlineVerify() { + return null; + } + + @Override + public InlineDetach inlineDetach() { + return null; + } + + @Override + public Encrypt encrypt() { + return null; + } + + @Override + public Decrypt decrypt() { + return null; + } + + @Override + public Armor armor() { + return null; + } + + @Override + public Dearmor dearmor() { + return null; + } +} diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java b/binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java new file mode 100644 index 0000000..7814e2f --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.binary.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.operation.ExtractCert; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class BinaryExtractCert implements ExtractCert { + + private final String binary; + private final Runtime runtime = Runtime.getRuntime(); + + private boolean noArmor; + + public BinaryExtractCert(String binary) { + this.binary = binary; + } + + @Override + public ExtractCert noArmor() { + this.noArmor = true; + return this; + } + + @Override + public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData { + List commandList = new ArrayList<>(); + + commandList.add(binary); + commandList.add("extract-cert"); + + if (noArmor) { + commandList.add("--no-armor"); + } + + String[] command = commandList.toArray(new String[0]); + try { + Process process = runtime.exec(command); + OutputStream stdOut = process.getOutputStream(); + InputStream stdIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = keyInputStream.read(buf)) > 0) { + stdOut.write(buf, 0, r); + } + + while ((r = stdIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java b/binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java new file mode 100644 index 0000000..11ed378 --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.binary.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.operation.GenerateKey; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +public class BinaryGenerateKey implements GenerateKey { + + private final String binary; + private boolean noArmor = false; + private List userIds = new ArrayList<>(); + private String keyPassword; + + private final Runtime runtime = Runtime.getRuntime(); + + public BinaryGenerateKey(String binary) { + this.binary = binary; + } + + @Override + public GenerateKey noArmor() { + this.noArmor = true; + return this; + } + + @Override + public GenerateKey userId(String userId) { + this.userIds.add(userId); + return this; + } + + @Override + public GenerateKey withKeyPassword(String password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + this.keyPassword = password; + return this; + } + + @Override + public Ready generate() + throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { + List commandList = new ArrayList<>(); + + commandList.add(binary); + commandList.add("generate-key"); + + if (noArmor) { + commandList.add("--no-armor"); + } + + if (keyPassword != null) { + commandList.add("--with-key-password"); + commandList.add(keyPassword); + } + + for (String userId : userIds) { + commandList.add(userId); + } + + String[] command = commandList.toArray(new String[0]); + try { + Process process = runtime.exec(command); + InputStream stdIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = stdIn.read(buf)) >= 0) { + outputStream.write(buf, 0, r); + } + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java b/binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java new file mode 100644 index 0000000..4ca55d4 --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.binary.operation; + +import sop.operation.Version; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class BinaryVersion implements Version { + + private final Runtime runtime = Runtime.getRuntime(); + private final String binary; + + public BinaryVersion(String binaryName) { + this.binary = binaryName; + } + + @Override + public String getName() { + String[] command = new String[] {binary, "version"}; + try { + Process process = runtime.exec(command); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = stdInput.readLine().trim(); + if (line.contains(" ")) { + return line.substring(0, line.lastIndexOf(" ")); + } + return line; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getVersion() { + String[] command = new String[] {binary, "version"}; + try { + Process process = runtime.exec(command); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = stdInput.readLine().trim(); + if (line.contains(" ")) { + return line.substring(line.lastIndexOf(" ") + 1); + } + return line; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getBackendVersion() { + String[] command = new String[] {binary, "version", "--backend"}; + try { + Process process = runtime.exec(command); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = stdInput.readLine()) != null) { + sb.append(line).append('\n'); + } + return sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getExtendedVersion() { + String[] command = new String[] {binary, "version", "--extended"}; + try { + Process process = runtime.exec(command); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = stdInput.readLine()) != null) { + sb.append(line).append('\n'); + } + return sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/binary-sop/src/main/java/sop/binary/operation/package-info.java b/binary-sop/src/main/java/sop/binary/operation/package-info.java new file mode 100644 index 0000000..362fa8a --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/operation/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Bindings for SOP subcommands to a SOP binary. + */ +package sop.binary.operation; diff --git a/binary-sop/src/main/java/sop/binary/package-info.java b/binary-sop/src/main/java/sop/binary/package-info.java new file mode 100644 index 0000000..d6c44e1 --- /dev/null +++ b/binary-sop/src/main/java/sop/binary/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Implementation of sop-java which delegates execution to a binary implementing the SOP command line interface. + */ +package sop.binary; diff --git a/binary-sop/src/test/java/sop/binary/BinarySopTest.java b/binary-sop/src/test/java/sop/binary/BinarySopTest.java new file mode 100644 index 0000000..f095622 --- /dev/null +++ b/binary-sop/src/test/java/sop/binary/BinarySopTest.java @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.binary; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.SOP; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("sop.binary.BinarySopTest#sopBinaryInstalled") +public class BinarySopTest { + + private static final String BINARY = "/usr/bin/sqop"; + + private final SOP sop = new BinarySop(BINARY); + + public static boolean sopBinaryInstalled() { + return new File(BINARY).exists(); + } + + @Test + public void versionNameTest() { + assertEquals("sqop", sop.version().getName()); + } + + @Test + public void versionVersionTest() { + String version = sop.version().getVersion(); + assertTrue(version.matches("\\d+(\\.\\d+)*")); + } + + @Test + public void backendVersionTest() { + String backend = sop.version().getBackendVersion(); + assertFalse(backend.isEmpty()); + } + + @Test + public void extendedVersionTest() { + String extended = sop.version().getExtendedVersion(); + assertFalse(extended.isEmpty()); + } + + @Test + public void generateKeyTest() throws IOException { + String key = new String(sop.generateKey().userId("Alice").generate().getBytes()); + assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + } + + @Test + @Disabled + public void extractCertTest() throws IOException { + InputStream keyIn = sop.generateKey().userId("Alice").generate().getInputStream(); + String cert = new String(sop.extractCert().key(keyIn).getBytes()); + assertTrue(cert.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + } +} diff --git a/settings.gradle b/settings.gradle index cc5c0bc..1ac71bb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,5 +5,6 @@ rootProject.name = 'SOP-Java' include 'sop-java', - 'sop-java-picocli' + 'sop-java-picocli', + 'binary-sop' From e602cc16cc0fd737a4ec7df965fea9aab28bda4d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 7 Jan 2023 15:23:57 +0100 Subject: [PATCH 071/444] Rename module to external-sop and make backend in tests configurable --- {binary-sop => external-sop}/build.gradle | 3 ++ .../main/java/sop/external/ExternalSOP.java | 18 +++---- .../operation/ExtractCertExternal.java | 6 +-- .../operation/GenerateKeyExternal.java | 6 +-- .../external/operation/VersionExternal.java | 6 +-- .../sop/external}/operation/package-info.java | 2 +- .../main/java/sop/external}/package-info.java | 2 +- .../main/resources/sop/external/.gitignore | 5 ++ .../resources/sop/external/backend.properties | 7 +++ .../java/sop/external/ExternalSOPTest.java | 47 ++++++++++++++++--- settings.gradle | 2 +- version.gradle | 6 ++- 12 files changed, 80 insertions(+), 30 deletions(-) rename {binary-sop => external-sop}/build.gradle (78%) rename binary-sop/src/main/java/sop/binary/BinarySop.java => external-sop/src/main/java/sop/external/ExternalSOP.java (78%) rename binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java => external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java (92%) rename binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java => external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java (94%) rename binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java => external-sop/src/main/java/sop/external/operation/VersionExternal.java (95%) rename {binary-sop/src/main/java/sop/binary => external-sop/src/main/java/sop/external}/operation/package-info.java (84%) rename {binary-sop/src/main/java/sop/binary => external-sop/src/main/java/sop/external}/package-info.java (91%) create mode 100644 external-sop/src/main/resources/sop/external/.gitignore create mode 100644 external-sop/src/main/resources/sop/external/backend.properties rename binary-sop/src/test/java/sop/binary/BinarySopTest.java => external-sop/src/test/java/sop/external/ExternalSOPTest.java (52%) diff --git a/binary-sop/build.gradle b/external-sop/build.gradle similarity index 78% rename from binary-sop/build.gradle rename to external-sop/build.gradle index 85b6bc2..0989747 100644 --- a/binary-sop/build.gradle +++ b/external-sop/build.gradle @@ -17,6 +17,9 @@ dependencies { testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" api project(":sop-java") + + api "org.slf4j:slf4j-api:$slf4jVersion" + testImplementation "ch.qos.logback:logback-classic:$logbackVersion" } test { diff --git a/binary-sop/src/main/java/sop/binary/BinarySop.java b/external-sop/src/main/java/sop/external/ExternalSOP.java similarity index 78% rename from binary-sop/src/main/java/sop/binary/BinarySop.java rename to external-sop/src/main/java/sop/external/ExternalSOP.java index c048480..ff27d7c 100644 --- a/binary-sop/src/main/java/sop/binary/BinarySop.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.binary; +package sop.external; import sop.SOP; -import sop.binary.operation.BinaryExtractCert; -import sop.binary.operation.BinaryGenerateKey; -import sop.binary.operation.BinaryVersion; +import sop.external.operation.ExtractCertExternal; +import sop.external.operation.GenerateKeyExternal; +import sop.external.operation.VersionExternal; import sop.operation.Armor; import sop.operation.Dearmor; import sop.operation.Decrypt; @@ -21,27 +21,27 @@ import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.Version; -public class BinarySop implements SOP { +public class ExternalSOP implements SOP { private final String binaryName; - public BinarySop(String binaryName) { + public ExternalSOP(String binaryName) { this.binaryName = binaryName; } @Override public Version version() { - return new BinaryVersion(binaryName); + return new VersionExternal(binaryName); } @Override public GenerateKey generateKey() { - return new BinaryGenerateKey(binaryName); + return new GenerateKeyExternal(binaryName); } @Override public ExtractCert extractCert() { - return new BinaryExtractCert(binaryName); + return new ExtractCertExternal(binaryName); } @Override diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java similarity index 92% rename from binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java rename to external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index 7814e2f..9349d4e 100644 --- a/binary-sop/src/main/java/sop/binary/operation/BinaryExtractCert.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.binary.operation; +package sop.external.operation; import sop.Ready; import sop.exception.SOPGPException; @@ -14,14 +14,14 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -public class BinaryExtractCert implements ExtractCert { +public class ExtractCertExternal implements ExtractCert { private final String binary; private final Runtime runtime = Runtime.getRuntime(); private boolean noArmor; - public BinaryExtractCert(String binary) { + public ExtractCertExternal(String binary) { this.binary = binary; } diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java similarity index 94% rename from binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java rename to external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index 11ed378..c91ce06 100644 --- a/binary-sop/src/main/java/sop/binary/operation/BinaryGenerateKey.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.binary.operation; +package sop.external.operation; import sop.Ready; import sop.exception.SOPGPException; @@ -14,7 +14,7 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -public class BinaryGenerateKey implements GenerateKey { +public class GenerateKeyExternal implements GenerateKey { private final String binary; private boolean noArmor = false; @@ -23,7 +23,7 @@ public class BinaryGenerateKey implements GenerateKey { private final Runtime runtime = Runtime.getRuntime(); - public BinaryGenerateKey(String binary) { + public GenerateKeyExternal(String binary) { this.binary = binary; } diff --git a/binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java similarity index 95% rename from binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java rename to external-sop/src/main/java/sop/external/operation/VersionExternal.java index 4ca55d4..b40ed30 100644 --- a/binary-sop/src/main/java/sop/binary/operation/BinaryVersion.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.binary.operation; +package sop.external.operation; import sop.operation.Version; @@ -10,12 +10,12 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -public class BinaryVersion implements Version { +public class VersionExternal implements Version { private final Runtime runtime = Runtime.getRuntime(); private final String binary; - public BinaryVersion(String binaryName) { + public VersionExternal(String binaryName) { this.binary = binaryName; } diff --git a/binary-sop/src/main/java/sop/binary/operation/package-info.java b/external-sop/src/main/java/sop/external/operation/package-info.java similarity index 84% rename from binary-sop/src/main/java/sop/binary/operation/package-info.java rename to external-sop/src/main/java/sop/external/operation/package-info.java index 362fa8a..9c7dd29 100644 --- a/binary-sop/src/main/java/sop/binary/operation/package-info.java +++ b/external-sop/src/main/java/sop/external/operation/package-info.java @@ -5,4 +5,4 @@ /** * Bindings for SOP subcommands to a SOP binary. */ -package sop.binary.operation; +package sop.external.operation; diff --git a/binary-sop/src/main/java/sop/binary/package-info.java b/external-sop/src/main/java/sop/external/package-info.java similarity index 91% rename from binary-sop/src/main/java/sop/binary/package-info.java rename to external-sop/src/main/java/sop/external/package-info.java index d6c44e1..208985e 100644 --- a/binary-sop/src/main/java/sop/binary/package-info.java +++ b/external-sop/src/main/java/sop/external/package-info.java @@ -5,4 +5,4 @@ /** * Implementation of sop-java which delegates execution to a binary implementing the SOP command line interface. */ -package sop.binary; +package sop.external; diff --git a/external-sop/src/main/resources/sop/external/.gitignore b/external-sop/src/main/resources/sop/external/.gitignore new file mode 100644 index 0000000..4c2e2f3 --- /dev/null +++ b/external-sop/src/main/resources/sop/external/.gitignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: CC0-1.0 + +backend.local.properties \ No newline at end of file diff --git a/external-sop/src/main/resources/sop/external/backend.properties b/external-sop/src/main/resources/sop/external/backend.properties new file mode 100644 index 0000000..0cf721f --- /dev/null +++ b/external-sop/src/main/resources/sop/external/backend.properties @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: CC0-1.0 + +## Do not change this file. To overwrite the SOP backend used during testing, +## simply create a file 'backend.local.properties' in this directory and override sop.backend in there. +sop.backend=/path/to/backend \ No newline at end of file diff --git a/binary-sop/src/test/java/sop/binary/BinarySopTest.java b/external-sop/src/test/java/sop/external/ExternalSOPTest.java similarity index 52% rename from binary-sop/src/test/java/sop/binary/BinarySopTest.java rename to external-sop/src/test/java/sop/external/ExternalSOPTest.java index f095622..fa12246 100644 --- a/binary-sop/src/test/java/sop/binary/BinarySopTest.java +++ b/external-sop/src/test/java/sop/external/ExternalSOPTest.java @@ -2,30 +2,63 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.binary; +package sop.external; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sop.SOP; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Properties; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIf("sop.binary.BinarySopTest#sopBinaryInstalled") -public class BinarySopTest { +@EnabledIf("sop.external.ExternalSOPTest#externalSopInstalled") +public class ExternalSOPTest { - private static final String BINARY = "/usr/bin/sqop"; + private static final Logger LOGGER = LoggerFactory.getLogger(ExternalSOPTest.class); - private final SOP sop = new BinarySop(BINARY); + private final SOP sop; - public static boolean sopBinaryInstalled() { - return new File(BINARY).exists(); + public ExternalSOPTest() { + String backend = readSopBackendFromProperties(); + sop = new ExternalSOP(backend); + } + + private static String readSopBackendFromProperties() { + Properties properties = new Properties(); + try { + InputStream resourceIn = ExternalSOPTest.class.getResourceAsStream("backend.local.properties"); + if (resourceIn == null) { + LOGGER.info("Could not find backend.local.properties file. Try backend.properties instead."); + resourceIn = ExternalSOPTest.class.getResourceAsStream("backend.properties"); + } + if (resourceIn == null) { + throw new FileNotFoundException("Could not find backend.properties file."); + } + + properties.load(resourceIn); + String backend = properties.getProperty("sop.backend"); + return backend; + } catch (IOException e) { + return null; + } + } + + public static boolean externalSopInstalled() { + String binary = readSopBackendFromProperties(); + if (binary == null) { + return false; + } + return new File(binary).exists(); } @Test diff --git a/settings.gradle b/settings.gradle index 1ac71bb..5dc6372 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,5 +6,5 @@ rootProject.name = 'SOP-Java' include 'sop-java', 'sop-java-picocli', - 'binary-sop' + 'external-sop' diff --git a/version.gradle b/version.gradle index 0cbaf74..a069baf 100644 --- a/version.gradle +++ b/version.gradle @@ -8,10 +8,12 @@ allprojects { isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 + jsrVersion = '3.0.2' junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - picocliVersion = '4.6.3' + logbackVersion = '1.2.11' mockitoVersion = '4.5.1' - jsrVersion = '3.0.2' + picocliVersion = '4.6.3' + slf4jVersion = '1.7.36' } } From efec4d911055c40c02f6e509c10faf7716790b10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Jan 2023 14:56:53 +0100 Subject: [PATCH 072/444] Wip: working extract-cert, fix generate-key parameter passing --- .../main/java/sop/external/ExternalSOP.java | 141 +++++++++++++++++- .../operation/DetachedSignExternal.java | 43 ++++++ .../operation/ExtractCertExternal.java | 30 +++- .../operation/GenerateKeyExternal.java | 18 ++- .../external/operation/VersionExternal.java | 22 ++- .../main/resources/sop/external/.gitignore | 3 +- .../sop/external/AbstractExternalSOPTest.java | 75 ++++++++++ .../sop/external/ExternalExtractCertTest.java | 24 +++ .../sop/external/ExternalGenerateKeyTest.java | 30 ++++ .../java/sop/external/ExternalSOPTest.java | 100 ------------- .../sop/external/ExternalVersionTest.java | 40 +++++ .../java/sop/exception/SOPGPException.java | 26 +++- 12 files changed, 430 insertions(+), 122 deletions(-) create mode 100644 external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java create mode 100644 external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalExtractCertTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java delete mode 100644 external-sop/src/test/java/sop/external/ExternalSOPTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalVersionTest.java diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index ff27d7c..28fda4d 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -5,6 +5,7 @@ package sop.external; import sop.SOP; +import sop.exception.SOPGPException; import sop.external.operation.ExtractCertExternal; import sop.external.operation.GenerateKeyExternal; import sop.external.operation.VersionExternal; @@ -21,27 +22,41 @@ import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.Version; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Properties; + public class ExternalSOP implements SOP { private final String binaryName; + private final Properties properties; public ExternalSOP(String binaryName) { + this(binaryName, new Properties()); + } + + public ExternalSOP(String binaryName, Properties properties) { this.binaryName = binaryName; + this.properties = properties; } @Override public Version version() { - return new VersionExternal(binaryName); + return new VersionExternal(binaryName, properties); } @Override public GenerateKey generateKey() { - return new GenerateKeyExternal(binaryName); + return new GenerateKeyExternal(binaryName, properties); } @Override public ExtractCert extractCert() { - return new ExtractCertExternal(binaryName); + return new ExtractCertExternal(binaryName, properties); } @Override @@ -88,4 +103,124 @@ public class ExternalSOP implements SOP { public Dearmor dearmor() { return null; } + + public static void finish(Process process) throws IOException { + try { + mapExitCodeOrException(process); + } catch (SOPGPException e) { + InputStream errIn = process.getErrorStream(); + ByteArrayOutputStream errOut = new ByteArrayOutputStream(); + byte[] buf = new byte[512]; + int r; + while ((r = errIn.read(buf)) > 0 ) { + errOut.write(buf, 0, r); + } + + e.initCause(new IOException(errOut.toString())); + throw e; + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static void mapExitCodeOrException(Process process) throws InterruptedException, IOException { + int exitCode = process.waitFor(); + + if (exitCode == 0) { + return; + } + + InputStream errIn = process.getErrorStream(); + ByteArrayOutputStream errOut = new ByteArrayOutputStream(); + byte[] buf = new byte[512]; + int r; + while ((r = errIn.read(buf)) > 0 ) { + errOut.write(buf, 0, r); + } + + String errorMessage = errOut.toString(); + + switch (exitCode) { + case SOPGPException.NoSignature.EXIT_CODE: + throw new SOPGPException.NoSignature("External SOP backend reported error NoSignature (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE: + throw new UnsupportedOperationException("External SOP backend reported error UnsupportedAsymmetricAlgo (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.CertCannotEncrypt.EXIT_CODE: + throw new SOPGPException.CertCannotEncrypt("External SOP backend reported error CertCannotEncrypt (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.MissingArg.EXIT_CODE: + throw new SOPGPException.MissingArg("External SOP backend reported error MissingArg (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.IncompleteVerification.EXIT_CODE: + throw new SOPGPException.IncompleteVerification("External SOP backend reported error IncompleteVerification (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.CannotDecrypt.EXIT_CODE: + throw new SOPGPException.CannotDecrypt("External SOP backend reported error CannotDecrypt (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.PasswordNotHumanReadable.EXIT_CODE: + throw new SOPGPException.PasswordNotHumanReadable("External SOP backend reported error PasswordNotHumanReadable (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.UnsupportedOption.EXIT_CODE: + throw new SOPGPException.UnsupportedOption("External SOP backend reported error UnsupportedOption (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.BadData.EXIT_CODE: + throw new SOPGPException.BadData("External SOP backend reported error BadData (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.ExpectedText.EXIT_CODE: + throw new SOPGPException.ExpectedText("External SOP backend reported error ExpectedText (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.OutputExists.EXIT_CODE: + throw new SOPGPException.OutputExists("External SOP backend reported error OutputExists (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.MissingInput.EXIT_CODE: + throw new SOPGPException.MissingInput("External SOP backend reported error MissingInput (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.KeyIsProtected.EXIT_CODE: + throw new SOPGPException.KeyIsProtected("External SOP backend reported error KeyIsProtected (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.UnsupportedSubcommand.EXIT_CODE: + throw new SOPGPException.UnsupportedSubcommand("External SOP backend reported error UnsupportedSubcommand (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.UnsupportedSpecialPrefix.EXIT_CODE: + throw new SOPGPException.UnsupportedSpecialPrefix("External SOP backend reported error UnsupportedSpecialPrefix (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.AmbiguousInput.EXIT_CODE: + throw new SOPGPException.AmbiguousInput("External SOP backend reported error AmbiguousInput (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.KeyCannotSign.EXIT_CODE: + throw new SOPGPException.KeyCannotSign("External SOP backend reported error KeyCannotSign (" + + exitCode + "):\n" + errorMessage); + + default: + throw new RuntimeException("External SOP backend reported unknown exit code (" + + exitCode + "):\n" + errorMessage); + } + } + + public static List propertiesToEnv(Properties properties) { + List env = new ArrayList<>(); + for (Object key : properties.keySet()) { + env.add(key + "=" + properties.get(key)); + } + return env; + } } diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java new file mode 100644 index 0000000..2ab126f --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -0,0 +1,43 @@ +package sop.external.operation; + +import sop.ReadyWithResult; +import sop.SigningResult; +import sop.enums.SignAs; +import sop.exception.SOPGPException; +import sop.operation.DetachedSign; + +import java.io.IOException; +import java.io.InputStream; + +public class DetachedSignExternal implements DetachedSign { + + private boolean noArmor; + private byte[] keyPassword; + + @Override + public DetachedSign noArmor() { + this.noArmor = true; + return this; + } + + @Override + public DetachedSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + return null; + } + + @Override + public DetachedSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + this.keyPassword = password; + return this; + } + + @Override + public DetachedSign mode(SignAs mode) throws SOPGPException.UnsupportedOption { + return null; + } + + @Override + public ReadyWithResult data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + return null; + } +} diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index 9349d4e..da7f3db 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -6,6 +6,7 @@ package sop.external.operation; import sop.Ready; import sop.exception.SOPGPException; +import sop.external.ExternalSOP; import sop.operation.ExtractCert; import java.io.IOException; @@ -13,16 +14,19 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Properties; public class ExtractCertExternal implements ExtractCert { private final String binary; private final Runtime runtime = Runtime.getRuntime(); + private final Properties environment; private boolean noArmor; - public ExtractCertExternal(String binary) { + public ExtractCertExternal(String binary, Properties properties) { this.binary = binary; + this.environment = properties; } @Override @@ -32,7 +36,7 @@ public class ExtractCertExternal implements ExtractCert { } @Override - public Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData { + public Ready key(InputStream keyInputStream) throws SOPGPException.BadData { List commandList = new ArrayList<>(); commandList.add(binary); @@ -42,11 +46,15 @@ public class ExtractCertExternal implements ExtractCert { commandList.add("--no-armor"); } + List envList = ExternalSOP.propertiesToEnv(environment); + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { - Process process = runtime.exec(command); - OutputStream stdOut = process.getOutputStream(); - InputStream stdIn = process.getInputStream(); + Process process = runtime.exec(command, env); + OutputStream extractOut = process.getOutputStream(); + InputStream extractIn = process.getInputStream(); return new Ready() { @Override @@ -54,12 +62,20 @@ public class ExtractCertExternal implements ExtractCert { byte[] buf = new byte[4096]; int r; while ((r = keyInputStream.read(buf)) > 0) { - stdOut.write(buf, 0, r); + extractOut.write(buf, 0, r); } - while ((r = stdIn.read(buf)) > 0) { + keyInputStream.close(); + extractOut.close(); + + while ((r = extractIn.read(buf)) > 0) { outputStream.write(buf, 0 , r); } + + extractIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); } }; } catch (IOException e) { diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index c91ce06..0c09304 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -6,6 +6,7 @@ package sop.external.operation; import sop.Ready; import sop.exception.SOPGPException; +import sop.external.ExternalSOP; import sop.operation.GenerateKey; import java.io.IOException; @@ -13,6 +14,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Properties; public class GenerateKeyExternal implements GenerateKey { @@ -22,9 +24,11 @@ public class GenerateKeyExternal implements GenerateKey { private String keyPassword; private final Runtime runtime = Runtime.getRuntime(); + private final Properties properties; - public GenerateKeyExternal(String binary) { + public GenerateKeyExternal(String binary, Properties environment) { this.binary = binary; + this.properties = environment; } @Override @@ -60,16 +64,22 @@ public class GenerateKeyExternal implements GenerateKey { if (keyPassword != null) { commandList.add("--with-key-password"); - commandList.add(keyPassword); + commandList.add("@ENV:key_password"); } for (String userId : userIds) { commandList.add(userId); } + List envList = ExternalSOP.propertiesToEnv(properties); + if (keyPassword != null) { + envList.add("key_password=" + keyPassword); + } + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); try { - Process process = runtime.exec(command); + Process process = runtime.exec(command, env); InputStream stdIn = process.getInputStream(); return new Ready() { @@ -80,6 +90,8 @@ public class GenerateKeyExternal implements GenerateKey { while ((r = stdIn.read(buf)) >= 0) { outputStream.write(buf, 0, r); } + + ExternalSOP.finish(process); } }; } catch (IOException e) { diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index b40ed30..b4fc50b 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -4,31 +4,37 @@ package sop.external.operation; +import sop.external.ExternalSOP; import sop.operation.Version; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.Properties; public class VersionExternal implements Version { private final Runtime runtime = Runtime.getRuntime(); private final String binary; + private final Properties environment; - public VersionExternal(String binaryName) { + public VersionExternal(String binaryName, Properties environment) { this.binary = binaryName; + this.environment = environment; } @Override public String getName() { String[] command = new String[] {binary, "version"}; + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); try { - Process process = runtime.exec(command); + Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = stdInput.readLine().trim(); if (line.contains(" ")) { return line.substring(0, line.lastIndexOf(" ")); } + ExternalSOP.finish(process); return line; } catch (IOException e) { throw new RuntimeException(e); @@ -38,13 +44,15 @@ public class VersionExternal implements Version { @Override public String getVersion() { String[] command = new String[] {binary, "version"}; + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); try { - Process process = runtime.exec(command); + Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = stdInput.readLine().trim(); if (line.contains(" ")) { return line.substring(line.lastIndexOf(" ") + 1); } + ExternalSOP.finish(process); return line; } catch (IOException e) { throw new RuntimeException(e); @@ -54,14 +62,16 @@ public class VersionExternal implements Version { @Override public String getBackendVersion() { String[] command = new String[] {binary, "version", "--backend"}; + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); try { - Process process = runtime.exec(command); + Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = stdInput.readLine()) != null) { sb.append(line).append('\n'); } + ExternalSOP.finish(process); return sb.toString(); } catch (IOException e) { throw new RuntimeException(e); @@ -71,14 +81,16 @@ public class VersionExternal implements Version { @Override public String getExtendedVersion() { String[] command = new String[] {binary, "version", "--extended"}; + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); try { - Process process = runtime.exec(command); + Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = stdInput.readLine()) != null) { sb.append(line).append('\n'); } + ExternalSOP.finish(process); return sb.toString(); } catch (IOException e) { throw new RuntimeException(e); diff --git a/external-sop/src/main/resources/sop/external/.gitignore b/external-sop/src/main/resources/sop/external/.gitignore index 4c2e2f3..b1790be 100644 --- a/external-sop/src/main/resources/sop/external/.gitignore +++ b/external-sop/src/main/resources/sop/external/.gitignore @@ -2,4 +2,5 @@ # # SPDX-License-Identifier: CC0-1.0 -backend.local.properties \ No newline at end of file +backend.local.properties +backend.env \ No newline at end of file diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java new file mode 100644 index 0000000..3a779ca --- /dev/null +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sop.SOP; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public abstract class AbstractExternalSOPTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExternalSOPTest.class); + + private final SOP sop; + + public AbstractExternalSOPTest() { + String backend = readSopBackendFromProperties(); + Properties environment = readBackendEnvironment(); + sop = new ExternalSOP(backend, environment); + } + + public SOP getSop() { + return sop; + } + + public static boolean isExternalSopInstalled() { + String binary = readSopBackendFromProperties(); + if (binary == null) { + return false; + } + return new File(binary).exists(); + } + + private static String readSopBackendFromProperties() { + Properties properties = new Properties(); + try { + InputStream resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.local.properties"); + if (resourceIn == null) { + LOGGER.info("Could not find backend.local.properties file. Try backend.properties instead."); + resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.properties"); + } + if (resourceIn == null) { + throw new FileNotFoundException("Could not find backend.properties file."); + } + + properties.load(resourceIn); + String backend = properties.getProperty("sop.backend"); + return backend; + } catch (IOException e) { + return null; + } + } + + protected static Properties readBackendEnvironment() { + Properties properties = new Properties(); + try { + InputStream resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.env"); + if (resourceIn == null) { + LOGGER.info("Could not read backend.env file."); + } else { + properties.load(resourceIn); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return properties; + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java new file mode 100644 index 0000000..6849179 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalExtractCertTest extends AbstractExternalSOPTest { + + @Test + public void extractCertTest() throws IOException { + InputStream keyIn = getSop().generateKey().userId("Alice").generate().getInputStream(); + String cert = new String(getSop().extractCert().key(keyIn).getBytes()); + assertTrue(cert.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java new file mode 100644 index 0000000..1ad4e51 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { + + @Test + public void generateKeyTest() throws IOException { + String key = new String(getSop().generateKey().userId("Alice").generate().getBytes()); + assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + } + + @Test + public void generateKeyWithPasswordTest() throws IOException { + String key = new String(getSop().generateKey().userId("Alice").withKeyPassword("swßrdf1sh").generate().getBytes()); + assertEquals("asd", key); + } + +} diff --git a/external-sop/src/test/java/sop/external/ExternalSOPTest.java b/external-sop/src/test/java/sop/external/ExternalSOPTest.java deleted file mode 100644 index fa12246..0000000 --- a/external-sop/src/test/java/sop/external/ExternalSOPTest.java +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sop.SOP; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@EnabledIf("sop.external.ExternalSOPTest#externalSopInstalled") -public class ExternalSOPTest { - - private static final Logger LOGGER = LoggerFactory.getLogger(ExternalSOPTest.class); - - private final SOP sop; - - public ExternalSOPTest() { - String backend = readSopBackendFromProperties(); - sop = new ExternalSOP(backend); - } - - private static String readSopBackendFromProperties() { - Properties properties = new Properties(); - try { - InputStream resourceIn = ExternalSOPTest.class.getResourceAsStream("backend.local.properties"); - if (resourceIn == null) { - LOGGER.info("Could not find backend.local.properties file. Try backend.properties instead."); - resourceIn = ExternalSOPTest.class.getResourceAsStream("backend.properties"); - } - if (resourceIn == null) { - throw new FileNotFoundException("Could not find backend.properties file."); - } - - properties.load(resourceIn); - String backend = properties.getProperty("sop.backend"); - return backend; - } catch (IOException e) { - return null; - } - } - - public static boolean externalSopInstalled() { - String binary = readSopBackendFromProperties(); - if (binary == null) { - return false; - } - return new File(binary).exists(); - } - - @Test - public void versionNameTest() { - assertEquals("sqop", sop.version().getName()); - } - - @Test - public void versionVersionTest() { - String version = sop.version().getVersion(); - assertTrue(version.matches("\\d+(\\.\\d+)*")); - } - - @Test - public void backendVersionTest() { - String backend = sop.version().getBackendVersion(); - assertFalse(backend.isEmpty()); - } - - @Test - public void extendedVersionTest() { - String extended = sop.version().getExtendedVersion(); - assertFalse(extended.isEmpty()); - } - - @Test - public void generateKeyTest() throws IOException { - String key = new String(sop.generateKey().userId("Alice").generate().getBytes()); - assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); - } - - @Test - @Disabled - public void extractCertTest() throws IOException { - InputStream keyIn = sop.generateKey().userId("Alice").generate().getInputStream(); - String cert = new String(sop.extractCert().key(keyIn).getBytes()); - assertTrue(cert.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); - } -} diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/external-sop/src/test/java/sop/external/ExternalVersionTest.java new file mode 100644 index 0000000..8fbf371 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalVersionTest.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalVersionTest extends AbstractExternalSOPTest { + + @Test + public void versionNameTest() { + assertEquals("sqop", getSop().version().getName()); + } + + @Test + public void versionVersionTest() { + String version = getSop().version().getVersion(); + assertTrue(version.matches("\\d+(\\.\\d+)*")); + } + + @Test + public void backendVersionTest() { + String backend = getSop().version().getBackendVersion(); + assertFalse(backend.isEmpty()); + } + + @Test + public void extendedVersionTest() { + String extended = getSop().version().getExtendedVersion(); + assertFalse(extended.isEmpty()); + } + +} diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 4d8c9f6..b7032c3 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -32,7 +32,11 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 3; public NoSignature() { - super("No verifiable signature found."); + this("No verifiable signature found."); + } + + public NoSignature(String message) { + super(message); } public NoSignature(String errorMsg, NoSignature e) { @@ -72,6 +76,10 @@ public abstract class SOPGPException extends RuntimeException { super(message, cause); } + public CertCannotEncrypt(String message) { + super(message); + } + @Override public int getExitCode() { return EXIT_CODE; @@ -85,8 +93,8 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 19; - public MissingArg(String s) { - super(s); + public MissingArg(String message) { + super(message); } @Override @@ -127,6 +135,10 @@ public abstract class SOPGPException extends RuntimeException { super(errorMsg, e); } + public CannotDecrypt(String message) { + super(message); + } + @Override public int getExitCode() { return EXIT_CODE; @@ -144,6 +156,10 @@ public abstract class SOPGPException extends RuntimeException { super(); } + public PasswordNotHumanReadable(String message) { + super(message); + } + @Override public int getExitCode() { return EXIT_CODE; @@ -203,6 +219,10 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 53; + public ExpectedText(String message) { + super(message); + } + @Override public int getExitCode() { return EXIT_CODE; From a63b29fe80678dc7dd212639994384cdb85dbb04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Jan 2023 19:48:25 +0100 Subject: [PATCH 073/444] WiP: Implement first prototypes of all SOP commands for external binaries --- external-sop/build.gradle | 3 + .../main/java/sop/external/ExternalSOP.java | 104 +++++- .../sop/external/operation/ArmorExternal.java | 40 +++ .../external/operation/DearmorExternal.java | 33 ++ .../external/operation/DecryptExternal.java | 139 ++++++++ .../operation/DetachedSignExternal.java | 75 ++++- .../operation/DetachedVerifyExternal.java | 108 ++++++ .../external/operation/EncryptExternal.java | 90 +++++ .../operation/ExtractCertExternal.java | 61 +--- .../operation/GenerateKeyExternal.java | 71 +--- .../operation/InlineDetachExternal.java | 75 +++++ .../operation/InlineSignExternal.java | 65 ++++ .../operation/InlineVerifyExternal.java | 94 ++++++ .../sop/external/AbstractExternalSOPTest.java | 76 +++++ .../external/EncryptDecryptRoundTripTest.java | 70 ++++ .../sop/external/ExternalExtractCertTest.java | 66 +++- .../sop/external/ExternalGenerateKeyTest.java | 77 ++++- .../sop/external/ExternalVersionTest.java | 8 +- .../src/test/java/sop/external/JUtils.java | 53 +++ .../src/test/java/sop/external/TestKeys.java | 307 ++++++++++++++++++ .../java/sop/exception/SOPGPException.java | 4 + 21 files changed, 1473 insertions(+), 146 deletions(-) create mode 100644 external-sop/src/main/java/sop/external/operation/ArmorExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DearmorExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DecryptExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/EncryptExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineSignExternal.java create mode 100644 external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java create mode 100644 external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java create mode 100644 external-sop/src/test/java/sop/external/JUtils.java create mode 100644 external-sop/src/test/java/sop/external/TestKeys.java diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 0989747..83bfe3c 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -20,6 +20,9 @@ dependencies { api "org.slf4j:slf4j-api:$slf4jVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + + // Compare version strings + implementation 'org.apache.maven:maven-artifact:3.6.3' } test { diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 28fda4d..b4763c6 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -4,10 +4,20 @@ package sop.external; +import sop.Ready; import sop.SOP; import sop.exception.SOPGPException; +import sop.external.operation.ArmorExternal; +import sop.external.operation.DearmorExternal; +import sop.external.operation.DecryptExternal; +import sop.external.operation.DetachedSignExternal; +import sop.external.operation.DetachedVerifyExternal; +import sop.external.operation.EncryptExternal; import sop.external.operation.ExtractCertExternal; import sop.external.operation.GenerateKeyExternal; +import sop.external.operation.InlineDetachExternal; +import sop.external.operation.InlineSignExternal; +import sop.external.operation.InlineVerifyExternal; import sop.external.operation.VersionExternal; import sop.operation.Armor; import sop.operation.Dearmor; @@ -25,9 +35,9 @@ import sop.operation.Version; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Properties; public class ExternalSOP implements SOP { @@ -61,47 +71,47 @@ public class ExternalSOP implements SOP { @Override public DetachedSign detachedSign() { - return null; + return new DetachedSignExternal(binaryName, properties); } @Override public InlineSign inlineSign() { - return null; + return new InlineSignExternal(binaryName, properties); } @Override public DetachedVerify detachedVerify() { - return null; + return new DetachedVerifyExternal(binaryName, properties); } @Override public InlineVerify inlineVerify() { - return null; + return new InlineVerifyExternal(binaryName, properties); } @Override public InlineDetach inlineDetach() { - return null; + return new InlineDetachExternal(binaryName, properties); } @Override public Encrypt encrypt() { - return null; + return new EncryptExternal(binaryName, properties); } @Override public Decrypt decrypt() { - return null; + return new DecryptExternal(binaryName, properties); } @Override public Armor armor() { - return null; + return new ArmorExternal(binaryName, properties); } @Override public Dearmor dearmor() { - return null; + return new DearmorExternal(binaryName, properties); } public static void finish(Process process) throws IOException { @@ -112,7 +122,7 @@ public class ExternalSOP implements SOP { ByteArrayOutputStream errOut = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int r; - while ((r = errIn.read(buf)) > 0 ) { + while ((r = errIn.read(buf)) > 0) { errOut.write(buf, 0, r); } @@ -135,7 +145,7 @@ public class ExternalSOP implements SOP { ByteArrayOutputStream errOut = new ByteArrayOutputStream(); byte[] buf = new byte[512]; int r; - while ((r = errIn.read(buf)) > 0 ) { + while ((r = errIn.read(buf)) > 0) { errOut.write(buf, 0, r); } @@ -223,4 +233,74 @@ public class ExternalSOP implements SOP { } return env; } + + public static String readFully(InputStream inputStream) throws IOException { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int r; + while ((r = inputStream.read(buf)) > 0) { + bOut.write(buf, 0, r); + } + return bOut.toString(); + } + + public static Ready ready(Runtime runtime, List commandList, List envList) { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + + try { + Process process = runtime.exec(command, env); + InputStream stdIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = stdIn.read(buf)) >= 0) { + outputStream.write(buf, 0, r); + } + + outputStream.close(); + ExternalSOP.finish(process); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static Ready ready(Runtime runtime, List commandList, List envList, InputStream standardIn) { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { + Process process = runtime.exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new Ready() { + @Override + public void writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = standardIn.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + standardIn.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + finish(process); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java new file mode 100644 index 0000000..5149792 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.ArmorLabel; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Armor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Properties; +import java.util.List; + +public class ArmorExternal implements Armor { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public ArmorExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("armor"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption { + commandList.add("--label=" + label); + return this; + } + + @Override + public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java new file mode 100644 index 0000000..936746f --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Dearmor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class DearmorExternal implements Dearmor { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public DearmorExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("dearmor"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java new file mode 100644 index 0000000..9373bcd --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.DecryptionResult; +import sop.ReadyWithResult; +import sop.SessionKey; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Decrypt; +import sop.util.UTCUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +public class DecryptExternal implements Decrypt { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int verifyWithCounter = 0; + private int withSessionKeyCounter = 0; + private int withPasswordCounter = 0; + private int keyCounter = 0; + private int withKeyPasswordCounter = 0; + + public DecryptExternal(String binary, Properties environment) { + this.commandList.add(binary); + this.commandList.add("decrypt"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Decrypt verifyNotBefore(Date timestamp) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public Decrypt verifyNotAfter(Date timestamp) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public Decrypt verifyWithCert(InputStream cert) + throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "VERIFY_WITH_" + verifyWithCounter++; + commandList.add("--verify-with=@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public Decrypt withSessionKey(SessionKey sessionKey) + throws SOPGPException.UnsupportedOption { + String envVar = "SESSION_KEY_" + withSessionKeyCounter++; + commandList.add("--with-session-key=@ENV:" + envVar); + envList.add(envVar + "=" + sessionKey); + return this; + } + + @Override + public Decrypt withPassword(String password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "PASSWORD_" + withPasswordCounter++; + commandList.add("--with-password=@ENV:" + envVar); + envList.add(envVar + "=" + password); + return this; + } + + @Override + public Decrypt withKey(InputStream key) + throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public Decrypt withKeyPassword(byte[] password) + throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); + return this; + } + + @Override + public ReadyWithResult ciphertext(InputStream ciphertext) + throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, + SOPGPException.KeyIsProtected, IOException { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new ReadyWithResult() { + @Override + public DecryptionResult writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = ciphertext.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + ciphertext.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return new DecryptionResult(null, Collections.emptyList()); // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 2ab126f..d82950b 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -1,43 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.external.operation; import sop.ReadyWithResult; import sop.SigningResult; import sop.enums.SignAs; import sop.exception.SOPGPException; +import sop.external.ExternalSOP; import sop.operation.DetachedSign; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; public class DetachedSignExternal implements DetachedSign { - private boolean noArmor; - private byte[] keyPassword; + private final List commandList = new ArrayList<>(); + private final List envList; + + private int withKeyPasswordCounter = 0; + private int keyCounter = 0; + + public DetachedSignExternal(String binary, Properties properties) { + commandList.add(binary); + commandList.add("sign"); + envList = ExternalSOP.propertiesToEnv(properties); + } @Override public DetachedSign noArmor() { - this.noArmor = true; + commandList.add("--no-armor"); return this; } @Override public DetachedSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - return null; + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; } @Override public DetachedSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - this.keyPassword = password; + String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); return this; } @Override public DetachedSign mode(SignAs mode) throws SOPGPException.UnsupportedOption { - return null; + commandList.add("--as=" + mode); + return this; } @Override - public ReadyWithResult data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - return null; + public ReadyWithResult data(InputStream data) + throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new ReadyWithResult() { + @Override + public SigningResult writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return SigningResult.builder().build(); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java new file mode 100644 index 0000000..d95c9dc --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Verification; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.DetachedVerify; +import sop.operation.VerifySignatures; +import sop.util.UTCUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +public class DetachedVerifyExternal implements DetachedVerify { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private Set certs = new HashSet<>(); + private InputStream signatures; + private int certCounter = 0; + + public DetachedVerifyExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("verify"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public DetachedVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public DetachedVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + this.certs.add(cert); + return this; + } + + @Override + public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData, IOException { + this.signatures = signatures; + return this; + } + + @Override + public List data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + commandList.add("@ENV:SIGNATURE"); + envList.add("SIGNATURE=" + ExternalSOP.readFully(signatures)); + + for (InputStream cert : certs) { + String envVar = "CERT_" + certCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + } + + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(processIn)); + List verifications = new ArrayList<>(); + + String line = null; + while ((line = bufferedReader.readLine()) != null) { + verifications.add(Verification.fromString(line)); + } + + ExternalSOP.finish(process); + + return verifications; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java new file mode 100644 index 0000000..7096842 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.EncryptAs; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.Encrypt; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class EncryptExternal implements Encrypt { + + private final List commandList = new ArrayList<>(); + private final List envList; + private int SIGN_WITH_COUNTER = 0; + private int KEY_PASSWORD_COUNTER = 0; + private int PASSWORD_COUNTER = 0; + private int CERT_COUNTER = 0; + + public EncryptExternal(String binary, Properties environment) { + this.commandList.add(binary); + this.commandList.add("encrypt"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public Encrypt noArmor() { + this.commandList.add("--no-armor"); + return this; + } + + @Override + public Encrypt mode(EncryptAs mode) + throws SOPGPException.UnsupportedOption { + this.commandList.add("--as=" + mode); + return this; + } + + @Override + public Encrypt signWith(InputStream key) + throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, + IOException { + String envVar = "SIGN_WITH_" + SIGN_WITH_COUNTER++; + commandList.add("--sign-with=@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public Encrypt withKeyPassword(byte[] password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "KEY_PASSWORD_" + KEY_PASSWORD_COUNTER++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); + return this; + } + + @Override + public Encrypt withPassword(String password) + throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { + String envVar = "PASSWORD_" + PASSWORD_COUNTER++; + commandList.add("--with-password=@ENV:" + envVar); + envList.add(envVar + "=" + password); + return this; + } + + @Override + public Encrypt withCert(InputStream cert) + throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, + IOException { + String envVar = "CERT_" + CERT_COUNTER++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public Ready plaintext(InputStream plaintext) + throws IOException, SOPGPException.KeyIsProtected { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, plaintext); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index da7f3db..68d0750 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -9,77 +9,30 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.ExtractCert; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class ExtractCertExternal implements ExtractCert { - private final String binary; - private final Runtime runtime = Runtime.getRuntime(); - private final Properties environment; - - private boolean noArmor; + private final List commandList = new ArrayList<>(); + private final List envList; public ExtractCertExternal(String binary, Properties properties) { - this.binary = binary; - this.environment = properties; + this.commandList.add(binary); + this.commandList.add("extract-cert"); + this.envList = ExternalSOP.propertiesToEnv(properties); } @Override public ExtractCert noArmor() { - this.noArmor = true; + this.commandList.add("--no-armor"); return this; } @Override public Ready key(InputStream keyInputStream) throws SOPGPException.BadData { - List commandList = new ArrayList<>(); - - commandList.add(binary); - commandList.add("extract-cert"); - - if (noArmor) { - commandList.add("--no-armor"); - } - - List envList = ExternalSOP.propertiesToEnv(environment); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = runtime.exec(command, env); - OutputStream extractOut = process.getOutputStream(); - InputStream extractIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = keyInputStream.read(buf)) > 0) { - extractOut.write(buf, 0, r); - } - - keyInputStream.close(); - extractOut.close(); - - while ((r = extractIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - extractIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, keyInputStream); } } diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index 0c09304..b348fa4 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -9,93 +9,48 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.GenerateKey; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class GenerateKeyExternal implements GenerateKey { - private final String binary; - private boolean noArmor = false; - private List userIds = new ArrayList<>(); - private String keyPassword; + private final List commandList = new ArrayList<>(); + private final List envList; - private final Runtime runtime = Runtime.getRuntime(); - private final Properties properties; + private int keyPasswordCounter = 0; public GenerateKeyExternal(String binary, Properties environment) { - this.binary = binary; - this.properties = environment; + this.commandList.add(binary); + this.commandList.add("generate-key"); + this.envList = ExternalSOP.propertiesToEnv(environment); } @Override public GenerateKey noArmor() { - this.noArmor = true; + this.commandList.add("--no-armor"); return this; } @Override public GenerateKey userId(String userId) { - this.userIds.add(userId); + this.commandList.add(userId); return this; } @Override public GenerateKey withKeyPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - this.keyPassword = password; + this.commandList.add("--with-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); + this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + password); + keyPasswordCounter++; + return this; } @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { - List commandList = new ArrayList<>(); - - commandList.add(binary); - commandList.add("generate-key"); - - if (noArmor) { - commandList.add("--no-armor"); - } - - if (keyPassword != null) { - commandList.add("--with-key-password"); - commandList.add("@ENV:key_password"); - } - - for (String userId : userIds) { - commandList.add(userId); - } - - List envList = ExternalSOP.propertiesToEnv(properties); - if (keyPassword != null) { - envList.add("key_password=" + keyPassword); - } - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - InputStream stdIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = stdIn.read(buf)) >= 0) { - outputStream.write(buf, 0, r); - } - - ExternalSOP.finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java new file mode 100644 index 0000000..f8ebe09 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.ReadyWithResult; +import sop.Signatures; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineDetach; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class InlineDetachExternal implements InlineDetach { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public InlineDetachExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-detach"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineDetach noArmor() { + commandList.add("--no-armor"); + return this; + } + + @Override + public ReadyWithResult message(InputStream messageInputStream) throws IOException, SOPGPException.BadData { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new ReadyWithResult() { + @Override + public Signatures writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = messageInputStream.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + messageInputStream.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return null; // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java new file mode 100644 index 0000000..2a49ad5 --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.enums.InlineSignAs; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineSign; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class InlineSignExternal implements InlineSign { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int keyCounter = 0; + private int withKeyPasswordCounter = 0; + + public InlineSignExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-sign"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineSign noArmor() { + commandList.add("--no-armor"); + return this; + } + + @Override + public InlineSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + String envVar = "KEY_" + keyCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(key)); + return this; + } + + @Override + public InlineSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); + return this; + } + + @Override + public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { + commandList.add("--as=" + mode); + return this; + } + + @Override + public Ready data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); + } +} diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java new file mode 100644 index 0000000..b18cc4c --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.ReadyWithResult; +import sop.Verification; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.InlineVerify; +import sop.util.UTCUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +public class InlineVerifyExternal implements InlineVerify { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int certCounter = 0; + + public InlineVerifyExternal(String binary, Properties environment) { + commandList.add(binary); + commandList.add("inline-verify"); + envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + return this; + } + + @Override + public InlineVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + String envVar = "CERT_" + certCounter++; + commandList.add("@ENV:" + envVar); + envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + return this; + } + + @Override + public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new ReadyWithResult>() { + @Override + public List writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { + byte[] buf = new byte[4096]; + int r; + while ((r = data.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + data.close(); + processOut.close(); + + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + return null; // TODO + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index 3a779ca..d557fd8 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.apache.maven.artifact.versioning.ComparableVersion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sop.SOP; @@ -14,6 +15,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + public abstract class AbstractExternalSOPTest { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExternalSOPTest.class); @@ -26,10 +29,20 @@ public abstract class AbstractExternalSOPTest { sop = new ExternalSOP(backend, environment); } + /** + * Return the SOP backend. + * + * @return SOP backend + */ public SOP getSop() { return sop; } + /** + * Return
true
iff the specified SOP backend binary is available and accessible. + * + * @return true if external SOP backend is usable + */ public static boolean isExternalSopInstalled() { String binary = readSopBackendFromProperties(); if (binary == null) { @@ -38,6 +51,69 @@ public abstract class AbstractExternalSOPTest { return new File(binary).exists(); } + public enum Is { + le("<"), + leq("<="), + eq("=="), + geq(">="), + ge(">"), + ; + + private final String display; + + Is(String display) { + this.display = display; + } + + public String toDisplay() { + return display; + } + } + + /** + * Ignore a test if the tested binary version matches a version criterion. + * Example: + * If the installed version of example-sop is 0.1.3,
ignoreIf("example-sop", Is.le, "0.1.4")
will + * make the test be ignored. + *
ignoreIf("example-sop", Is.eq, "0.1.3")
will skip the test as well. + *
ignoreIf("another-sop", Is.gt, "0.0.0")
will not skip the test, since the binary name does not match. + * + * @param name name of the binary + * @param is relation of the version + * @param version the reference version + */ + public void ignoreIf(String name, Is is, String version) { + String actualName = getSop().version().getName(); + String actualVersion = getSop().version().getVersion(); + + if (!name.matches(actualName)) { + // Name mismatch, do not ignore + return; + } + + ComparableVersion reference = new ComparableVersion(version); + ComparableVersion actual = new ComparableVersion(actualVersion); + + int res = actual.compareTo(reference); + String msg = "Skip since installed " + name + " " + actual + " " + is.toDisplay() + " " + reference; + switch (is) { + case le: + assumeFalse(res < 0, msg); + break; + case leq: + assumeFalse(res <= 0, msg); + case eq: + assumeFalse(res == 0, msg); + break; + case geq: + assumeFalse(res >= 0, msg); + break; + case ge: + assumeFalse(res > 0, msg); + break; + } + } + private static String readSopBackendFromProperties() { Properties properties = new Properties(); try { diff --git a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java new file mode 100644 index 0000000..de16215 --- /dev/null +++ b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { + + @Test + public void encryptDecryptRoundTripAliceTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } + + @Test + public void encryptDecryptRoundTripBobTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } + + @Test + public void encryptDecryptRoundTripCarolTest() throws IOException { + ignoreIf("sqop", Is.geq, "0.0.0"); // sqop reports cert not encryption capable + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withKey(TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 6849179..559316f 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -9,16 +9,72 @@ import org.junit.jupiter.api.condition.EnabledIf; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.arrayStartsWith; +import static sop.external.JUtils.assertArrayStartsWith; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalExtractCertTest extends AbstractExternalSOPTest { + private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; + private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); + @Test - public void extractCertTest() throws IOException { - InputStream keyIn = getSop().generateKey().userId("Alice").generate().getInputStream(); - String cert = new String(getSop().extractCert().key(keyIn).getBytes()); - assertTrue(cert.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n")); + public void extractArmoredCertFromArmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert().key(keyIn).getBytes(); + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + } + + @Test + public void extractUnarmoredCertFromArmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .noArmor() + .key(keyIn) + .getBytes(); + + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + } + + @Test + public void extractArmoredCertFromUnarmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .userId("Alice ") + .noArmor() + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .key(keyIn) + .getBytes(); + + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + } + + @Test + public void extractUnarmoredCertFromUnarmoredKeyTest() throws IOException { + InputStream keyIn = getSop().generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getInputStream(); + + byte[] cert = getSop().extractCert() + .noArmor() + .key(keyIn) + .getBytes(); + + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); } } diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index 1ad4e51..c492ebe 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -8,23 +8,88 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.assertArrayStartsWith; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { + private static final Charset UTF8 = StandardCharsets.UTF_8; + private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; + byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(UTF8); + @Test public void generateKeyTest() throws IOException { - String key = new String(getSop().generateKey().userId("Alice").generate().getBytes()); - assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n")); + byte[] key = getSop().generateKey() + .userId("Alice ") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } + + @Test + public void generateKeyNoArmor() throws IOException { + byte[] key = getSop().generateKey() + .userId("Alice ") + .noArmor() + .generate() + .getBytes(); + + assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + } + + @Test + public void generateKeyWithMultipleUserIdsTest() throws IOException { + byte[] key = getSop().generateKey() + .userId("Alice ") + .userId("Bob ") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } + + @Test + public void generateKeyWithoutUserIdTest() throws IOException { + ignoreIf("pgpainless-cli", Is.le, "1.3.15"); + + byte[] key = getSop().generateKey() + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } @Test public void generateKeyWithPasswordTest() throws IOException { - String key = new String(getSop().generateKey().userId("Alice").withKeyPassword("swßrdf1sh").generate().getBytes()); - assertEquals("asd", key); + ignoreIf("sqop", Is.le, "0.27.0"); + ignoreIf("pgpainless-cli", Is.le, "1.3.0"); + + byte[] key = getSop().generateKey() + .userId("Alice ") + .withKeyPassword("sw0rdf1sh") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } + @Test + public void generateKeyWithMultipleUserIdsAndPassword() throws IOException { + ignoreIf("sqop", Is.le, "0.27.0"); + ignoreIf("pgpainless-cli", Is.le, "1.3.15"); + + byte[] key = getSop().generateKey() + .userId("Alice ") + .userId("Bob ") + .withKeyPassword("sw0rdf1sh") + .generate() + .getBytes(); + + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/external-sop/src/test/java/sop/external/ExternalVersionTest.java index 8fbf371..bde6cb5 100644 --- a/external-sop/src/test/java/sop/external/ExternalVersionTest.java +++ b/external-sop/src/test/java/sop/external/ExternalVersionTest.java @@ -7,8 +7,8 @@ package sop.external; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") @@ -16,13 +16,15 @@ public class ExternalVersionTest extends AbstractExternalSOPTest { @Test public void versionNameTest() { - assertEquals("sqop", getSop().version().getName()); + String name = getSop().version().getName(); + assertNotNull(name); + assertFalse(name.isEmpty()); } @Test public void versionVersionTest() { String version = getSop().version().getVersion(); - assertTrue(version.matches("\\d+(\\.\\d+)*")); + assertTrue(version.matches("\\d+(\\.\\d+)*\\S*")); } @Test diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/external-sop/src/test/java/sop/external/JUtils.java new file mode 100644 index 0000000..36d6d85 --- /dev/null +++ b/external-sop/src/test/java/sop/external/JUtils.java @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.fail; + +public class JUtils { + + public static boolean arrayStartsWith(byte[] array, byte[] start) { + return arrayStartsWith(array, start, 0); + } + + public static boolean arrayStartsWith(byte[] array, byte[] start, int offset) { + if (offset < 0) { + throw new IllegalArgumentException("Offset cannot be negative"); + } + + if (start.length + offset > array.length) { + return false; + } + + for (int i = 0; i < start.length; i++) { + if (array[offset + i] != start[i]) { + return false; + } + } + return true; + } + + public static void assertArrayStartsWith(byte[] array, byte[] start) { + if (!arrayStartsWith(array, start)) { + byte[] actual = new byte[Math.min(start.length, array.length)]; + System.arraycopy(array, 0, actual, 0, actual.length); + fail("Array does not start with expected bytes.\n" + + "Expected: <" + Arrays.toString(start) + ">\n" + + "Actual: <" + Arrays.toString(actual) + ">"); + } + } + + public static void assertArrayStartsWith(byte[] array, byte[] start, int offset) { + if (!arrayStartsWith(array, start, offset)) { + byte[] actual = new byte[Math.min(start.length, array.length - offset)]; + System.arraycopy(array, offset, actual, 0, actual.length); + fail("Array does not start with expected bytes at offset " + offset + ".\n" + + "Expected: <" + Arrays.toString(start) + ">\n" + + "Actual: <" + Arrays.toString(actual) + ">"); + } + } +} diff --git a/external-sop/src/test/java/sop/external/TestKeys.java b/external-sop/src/test/java/sop/external/TestKeys.java new file mode 100644 index 0000000..1a2bb94 --- /dev/null +++ b/external-sop/src/test/java/sop/external/TestKeys.java @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +public class TestKeys { + + // 'Alice' key from draft-bre-openpgp-samples-00 + public static final String ALICE_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E\n" + + "Comment: Alice Lovelace \n" + + "\n" + + "xjMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u13NJkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+wpAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gLO\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHwngEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=QX3Q\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String ALICE_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E\n" + + "Comment: Alice Lovelace \n" + + "\n" + + "xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj\n" + + "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ\n" + + "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l\n" + + "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf\n" + + "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB\n" + + "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA\n" + + "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CeAQYFggAIBYhBOuF\n" + + "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM\n" + + "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb\n" + + "Pnn+We1aTBhaGa86AQ==\n" + + "=3GfK\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + // 'Bob' key from draft-bre-openpgp-samples-00 + public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w\n" + + "bGU+wsEOBBMBCgA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE0aZuGiOx\n" + + "gsmYD3iM+/zIKgFeczAFAl2lnvoACgkQ+/zIKgFeczBvbAv/VNk90a6hG8Od9xTz\n" + + "XxH5YRFUSGfIA1yjPIVOnKqhMwps2U+sWE3urL+MvjyQRlyRV8oY9IOhQ5Esm6DO\n" + + "ZYrTnE7qVETm1ajIAP2OFChEc55uH88x/anpPOXOJY7S8jbn3naC9qad75BrZ+3g\n" + + "9EBUWiy5p8TykP05WSnSxNRt7vFKLfEB4nGkehpwHXOVF0CRNwYle42bg8lpmdXF\n" + + "DcCZCi+qEbafmTQzkAqyzS3nCh3IAqq6Y0kBuaKLm2tSNUOlZbD+OHYQNZ5Jix7c\n" + + "ZUzs6Xh4+I55NRWl5smrLq66yOQoFPy9jot/Qxikx/wP3MsAzeGaZSEPc0fHp5G1\n" + + "6rlGbxQ3vl8/usUV7W+TMEMljgwd5x8POR6HC8EaCDfVnUBCPi/Gv+egLjsIbPJZ\n" + + "ZEroiE40e6/UoCiQtlpQB5exPJYSd1Q1txCwueih99PHepsDhmUQKiACszNU+RRo\n" + + "zAYau2VdHqnRJ7QYdxHDiH49jPK4NTMyb/tJh2TiIwcmsIpGzsDNBF2lnPIBDADW\n" + + "ML9cbGMrp12CtF9b2P6z9TTT74S8iyBOzaSvdGDQY/sUtZXRg21HWamXnn9sSXvI\n" + + "DEINOQ6A9QxdxoqWdCHrOuW3ofneYXoG+zeKc4dC86wa1TR2q9vW+RMXSO4uImA+\n" + + "Uzula/6k1DogDf28qhCxMwG/i/m9g1c/0aApuDyKdQ1PXsHHNlgd/Dn6rrd5y2AO\n" + + "baifV7wIhEJnvqgFXDN2RXGjLeCOHV4Q2WTYPg/S4k1nMXVDwZXrvIsA0YwIMgIT\n" + + "86Rafp1qKlgPNbiIlC1g9RY/iFaGN2b4Ir6GDohBQSfZW2+LXoPZuVE/wGlQ01rh\n" + + "827KVZW4lXvqsge+wtnWlszcselGATyzqOK9LdHPdZGzROZYI2e8c+paLNDdVPL6\n" + + "vdRBUnkCaEkOtl1mr2JpQi5nTU+gTX4IeInC7E+1a9UDF/Y85ybUz8XV8rUnR76U\n" + + "qVC7KidNepdHbZjjXCt8/Zo+Tec9JNbYNQB/e9ExmDntmlHEsSEQzFwzj8sxH48A\n" + + "EQEAAcLA9gQYAQoAIBYhBNGmbhojsYLJmA94jPv8yCoBXnMwBQJdpZzyAhsMAAoJ\n" + + "EPv8yCoBXnMw6f8L/26C34dkjBffTzMj5Bdzm8MtF67OYneJ4TQMw7+41IL4rVcS\n" + + "KhIhk/3Ud5knaRtP2ef1+5F66h9/RPQOJ5+tvBwhBAcUWSupKnUrdVaZQanYmtSx\n" + + "cVV2PL9+QEiNN3tzluhaWO//rACxJ+K/ZXQlIzwQVTpNhfGzAaMVV9zpf3u0k14i\n" + + "tcv6alKY8+rLZvO1wIIeRZLmU0tZDD5HtWDvUV7rIFI1WuoLb+KZgbYn3OWjCPHV\n" + + "dTrdZ2CqnZbG3SXw6awH9bzRLV9EXkbhIMez0deCVdeo+wFFklh8/5VK2b0vk/+w\n" + + "qMJxfpa1lHvJLobzOP9fvrswsr92MA2+k901WeISR7qEzcI0Fdg8AyFAExaEK6Vy\n" + + "jP7SXGLwvfisw34OxuZr3qmx1Sufu4toH3XrB7QJN8XyqqbsGxUCBqWif9RSK4xj\n" + + "zRTe56iPeiSJJOIciMP9i2ldI+KgLycyeDvGoBj0HCLO3gVaBe4ubVrj5KjhX2PV\n" + + "NEJd3XZRzaXZE2aAMQ==\n" + + "=F9yX\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String BOB_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: D1A6 6E1A 23B1 82C9 980F 788C FBFC C82A 015E 7330\n" + + "Comment: Bob Babbage \n" + + "\n" + + "xcSYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" + + "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" + + "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" + + "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" + + "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" + + "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" + + "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" + + "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" + + "vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" + + "cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" + + "3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" + + "Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" + + "hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" + + "bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" + + "i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" + + "1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" + + "fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" + + "fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" + + "LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" + + "+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" + + "hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" + + "WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" + + "MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" + + "mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" + + "YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" + + "he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" + + "zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" + + "NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" + + "t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qizSFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" + + "ZW5wZ3AuZXhhbXBsZT7CwQ4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" + + "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" + + "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" + + "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" + + "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" + + "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" + + "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" + + "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" + + "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" + + "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikbH\n" + + "xJgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" + + "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" + + "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" + + "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" + + "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" + + "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" + + "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" + + "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" + + "IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" + + "SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" + + "OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" + + "Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" + + "+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" + + "tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" + + "BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" + + "zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" + + "clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" + + "zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" + + "gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" + + "aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" + + "fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" + + "ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" + + "HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" + + "SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" + + "5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" + + "E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" + + "GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" + + "vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" + + "26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hrCwPYEGAEKACAWIQTRpm4aI7GCyZgP\n" + + "eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" + + "c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" + + "rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" + + "JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" + + "71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" + + "s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" + + "NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" + + "6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" + + "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" + + "=FAzO\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + // 'Carol' key from draft-bre-openpgp-samples-00 + public static final String CAROL_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: 71FF DA00 4409 E5DD B0C3 E8F1 9BA7 89DC 76D6 849A\n" + + "Comment: Carol Oldstyle \n" + + "\n" + + "xsPuBF3+CmgRDADZhdKTM3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0\n" + + "OJz2vh59nusbBLzgI//Y1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vh\n" + + "yVeJt0k/NnxvNhMd0587KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0Uj\n" + + "REWs5Jpj/XU9LhEoyXZkeJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcG\n" + + "zYgeMNOvdWJwn43dNhxoeuXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7\n" + + "MNuQx/ejIMZHl+Iaf7hG976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9\n" + + "+4dq6ybUM65tnozRyyN+1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpX\n" + + "duVd32MA33UVNH5/KXMVczVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0\n" + + "SFhlfnBEUj1my1sBAMOSO/I67BvBS3IPHZWXHjgclhs26mPzRlZLryAUWR2DDACH\n" + + "5fx+yUAdZ8Vu/2zWTHxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwS\n" + + "KJUBSA75HExbv0naWg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwp\n" + + "dr1ZwEbb3L6IGQ5i/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdP\n" + + "xGhM8w6a18+fdQr22f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV8\n" + + "2hP4K+rb9FwknYdV9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzo\n" + + "mYmaTO7mp6xFAu43yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4\n" + + "xwfOQ7pf3kC7r9fm8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnU\n" + + "yQs4ksAfIHTzTdLttRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL\n" + + "/0rUAxwegTNIG/5M612s2a45QvYK1turZ7spI1RGitJUIjBXUuR76jIsyqagIhBl\n" + + "5nEsQ4HLv8OQ3EgJ5T9gldLFpHNczLxBQnnNwfPoD2e0kC/iy0rfiNX8HWpTgQpb\n" + + "zAosLj5/E0iNlildynIhuqBosyRWFqGva0O6qioL90srlzlfKCloe9R9w3HizjCb\n" + + "f59yEspuJt9iHVNOPOW2Wj5ub0KTiJPp9vBmrFaB79/IlgojpQoYvQ77Hx5A9CJq\n" + + "paMCHGOW6Uz9euN1ozzETEkIPtL8XAxcogfpe2JKE1uS7ugxsKEGEDfxOQFKAGV0\n" + + "XFtIx50vFCr2vQro0WB858CGN47dCxChhNUxNtGc11JNEkNv/X7hKtRf/5VCmnaz\n" + + "GWwNK47cqZ7GJfEBnElD7s/tQvTC5Qp7lg9gEt47TUX0bjzUTCxNvLosuKL9+J1W\n" + + "ln1myRpff/5ZOAnZTPHR+AbX4bRB4sK5zijQe4139Dn2oRYK+EIYoBAxFxSOzehP\n" + + "IcKKBB8RCAA8BQJd/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYh\n" + + "BHH/2gBECeXdsMPo8Zunidx21oSaAABihQD/VWnF1HbBhP+kLwWsqxuYjEslEsM2\n" + + "UQPeKGK9an8HZ78BAJPaiL3OpuOmsIoCfOghhMZOKXjIV+Z57LwaMw7FQfPgzSZD\n" + + "YXJvbCBPbGRzdHlsZSA8Y2Fyb2xAb3BlbnBncC5leGFtcGxlPsKKBBMRCAA8BQJd\n" + + "/gppAwsJCgkQm6eJ3HbWhJoEFQoJCAIWAQIXgAIbAwIeARYhBHH/2gBECeXdsMPo\n" + + "8Zunidx21oSaAABQTAD/ZMXAvSbKaMJJpAfwp1C7KAj6K2k2CAz5jwUXyGf1+jUA\n" + + "/2iAMiX1XcLy3n0L8ytzge8/UAFHafBl4rn4DmUugfhjzsPMBF3+CmgQDADZhdKT\n" + + "M3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0OJz2vh59nusbBLzgI//Y\n" + + "1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vhyVeJt0k/NnxvNhMd0587\n" + + "KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0UjREWs5Jpj/XU9LhEoyXZk\n" + + "eJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcGzYgeMNOvdWJwn43dNhxo\n" + + "euXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7MNuQx/ejIMZHl+Iaf7hG\n" + + "976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9+4dq6ybUM65tnozRyyN+\n" + + "1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpXduVd32MA33UVNH5/KXMV\n" + + "czVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0SFhlfnBEUj1my1sMAIfl\n" + + "/H7JQB1nxW7/bNZMfHBYn9fqAZMupr0KZ8OrlQOpgUXO5bA3gcn6vI65qTUIbBIo\n" + + "lQFIDvkcTFu/SdpaD6y7L6kQO8XRUAs9T1VSRJC0fJHXRg7YVY57cAS2ltgNHCl2\n" + + "vVnARtvcvogZDmL/gI0dsna7fJR5ewM0C+ulVIRwiMDTVE8I4qZ/nxINmnjIN0/E\n" + + "aEzzDprXz591CvbZ/ZwnTGB8+VvMVs74VSwSAq+fpBMuFtpjDjOzut1AN6NYdXza\n" + + "E/gr6tv0XCSdh1X26jibvsyAaVT7jK8mcYRhovePCMjdsf1qig06Xpdu9UDM3OiZ\n" + + "iZpM7uanrEUC7jfK4bJ30r7UTiTsJBNE7FNn5F21CNX3mFKwSYyDv3adC8NIFbjH\n" + + "B85Dul/eQLuv1+by72cGUQ3XYextDxi+7H+V3mrlFoiUPX2PN9VHr6EnNuPZmdTJ\n" + + "CziSwB8gdPNN0u21HFL2VNFORXHa9tSehIHLpNgXWZ/qdE+lKbWuJnGeRHj4FAv+\n" + + "MQaafW0uHF+N8MDm8UWPvf4Vd0UJ0UpIjRWl2hTV+BHkNfvZlBRhhQIphNiKRe/W\n" + + "ap0f/lW2Gm2uS0KgByjjNXEzTiwrte2GX65M6F6Lz8N31kt1Iig1xGOuv+6HmxTN\n" + + "R8gL2K5PdJeJn8PTJWrRS7+BY8Hdkgb+wVpzE5cCvpFiG/P0yqfBdLWxVPlPI7dc\n" + + "hDkmx4iAhHJX9J/gX/hC6L3AzPNJqNPAKy20wYp/ruTbbwBolW/4ikWij460JrvB\n" + + "sm6Sp81A3ebaiN9XkJygLOyhGyhMieGulCYz6AahAFcECtPXGTcordV1mJth8yjF\n" + + "4gZfDQyg0nMW4Yr49yeFXcRMUw1yzN3Q9v2zzqDuFi2lGYTXYmVqLYzM9KbLO2Wx\n" + + "E/21xnBjLsl09l/FdA/bhdZq3t4/apbFOeQQ/j/AphvzWbsJnhG9Q7+d3VoDlz0g\n" + + "FiSduCYIAAq8dUOJNjrUTkZsL1pOIjhYjCMi2uiKS6RQkT6nvuumPF/D/VTnUGeZ\n" + + "wooEGBEIADwFAl3+CmkDCwkKCRCbp4ncdtaEmgQVCgkIAhYBAheAAhsMAh4BFiEE\n" + + "cf/aAEQJ5d2ww+jxm6eJ3HbWhJoAAEEpAP91hFqmcb2ZqVcaRDMSVmhkEcFIRmpH\n" + + "vDoQtVn8sArWqwEAi8HwbMhL+YwRItRZDknpC4vFjTHVMd1zMrz/JyeuT9k=\n" + + "=pa/S\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + + public static final String CAROL_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: 71FF DA00 4409 E5DD B0C3 E8F1 9BA7 89DC 76D6 849A\n" + + "Comment: Carol Oldstyle \n" + + "\n" + + "xcQTBF3+CmgRDADZhdKTM3ms3XpXnQke83FgaIBtP1g1qhqpCfg50WiPS0kjiMC0\n" + + "OJz2vh59nusbBLzgI//Y1VMhKfIWYbqMcIY+lWbseHjl52rqW6AaJ0TH4NgVt7vh\n" + + "yVeJt0k/NnxvNhMd0587KXmfpDxrwBqc/l5cVB+p0rL8vs8kxojHXAi5V3koM0Uj\n" + + "REWs5Jpj/XU9LhEoyXZkeJC/pes1u6UKoFYn7dFIP49Kkd1kb+1bNfdPYtA0JpcG\n" + + "zYgeMNOvdWJwn43dNhxoeuXfmAEhA8LdzT0C0O+7akXOKWrfhXJ8MTBqvPgWZYx7\n" + + "MNuQx/ejIMZHl+Iaf7hG976ILH+NCGiKkhidd9GIuA/WteHiQbXLyfiQ4n8P12q9\n" + + "+4dq6ybUM65tnozRyyN+1m3rU2a/+Ly3JCh4TeO27w+cxMWkaeHyTQaJVMbMbDpX\n" + + "duVd32MA33UVNH5/KXMVczVi5asVjuKDSojJDV1QwX8izZNl1t+AI0L3balCabV0\n" + + "SFhlfnBEUj1my1sBAMOSO/I67BvBS3IPHZWXHjgclhs26mPzRlZLryAUWR2DDACH\n" + + "5fx+yUAdZ8Vu/2zWTHxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwS\n" + + "KJUBSA75HExbv0naWg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwp\n" + + "dr1ZwEbb3L6IGQ5i/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdP\n" + + "xGhM8w6a18+fdQr22f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV8\n" + + "2hP4K+rb9FwknYdV9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzo\n" + + "mYmaTO7mp6xFAu43yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4\n" + + "xwfOQ7pf3kC7r9fm8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnU\n" + + "yQs4ksAfIHTzTdLttRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL\n" + + "/0rUAxwegTNIG/5M612s2a45QvYK1turZ7spI1RGitJUIjBXUuR76jIsyqagIhBl\n" + + "5nEsQ4HLv8OQ3EgJ5T9gldLFpHNczLxBQnnNwfPoD2e0kC/iy0rfiNX8HWpTgQpb\n" + + "zAosLj5/E0iNlildynIhuqBosyRWFqGva0O6qioL90srlzlfKCloe9R9w3HizjCb\n" + + "f59yEspuJt9iHVNOPOW2Wj5ub0KTiJPp9vBmrFaB79/IlgojpQoYvQ77Hx5A9CJq\n" + + "paMCHGOW6Uz9euN1ozzETEkIPtL8XAxcogfpe2JKE1uS7ugxsKEGEDfxOQFKAGV0\n" + + "XFtIx50vFCr2vQro0WB858CGN47dCxChhNUxNtGc11JNEkNv/X7hKtRf/5VCmnaz\n" + + "GWwNK47cqZ7GJfEBnElD7s/tQvTC5Qp7lg9gEt47TUX0bjzUTCxNvLosuKL9+J1W\n" + + "ln1myRpff/5ZOAnZTPHR+AbX4bRB4sK5zijQe4139Dn2oRYK+EIYoBAxFxSOzehP\n" + + "IQAA/2BCN5HryGjVff2t7Q6fVrQQS9hsMisszZl5rWwUOO6zETHCigQfEQgAPAUC\n" + + "Xf4KaQMLCQoJEJunidx21oSaBBUKCQgCFgECF4ACGwMCHgEWIQRx/9oARAnl3bDD\n" + + "6PGbp4ncdtaEmgAAYoUA/1VpxdR2wYT/pC8FrKsbmIxLJRLDNlED3ihivWp/B2e/\n" + + "AQCT2oi9zqbjprCKAnzoIYTGTil4yFfmeey8GjMOxUHz4M0mQ2Fyb2wgT2xkc3R5\n" + + "bGUgPGNhcm9sQG9wZW5wZ3AuZXhhbXBsZT7CigQTEQgAPAUCXf4KaQMLCQoJEJun\n" + + "idx21oSaBBUKCQgCFgECF4ACGwMCHgEWIQRx/9oARAnl3bDD6PGbp4ncdtaEmgAA\n" + + "UEwA/2TFwL0mymjCSaQH8KdQuygI+itpNggM+Y8FF8hn9fo1AP9ogDIl9V3C8t59\n" + + "C/Mrc4HvP1ABR2nwZeK5+A5lLoH4Y8fD8QRd/gpoEAwA2YXSkzN5rN16V50JHvNx\n" + + "YGiAbT9YNaoaqQn4OdFoj0tJI4jAtDic9r4efZ7rGwS84CP/2NVTISnyFmG6jHCG\n" + + "PpVm7Hh45edq6lugGidEx+DYFbe74clXibdJPzZ8bzYTHdOfOyl5n6Q8a8AanP5e\n" + + "XFQfqdKy/L7PJMaIx1wIuVd5KDNFI0RFrOSaY/11PS4RKMl2ZHiQv6XrNbulCqBW\n" + + "J+3RSD+PSpHdZG/tWzX3T2LQNCaXBs2IHjDTr3VicJ+N3TYcaHrl35gBIQPC3c09\n" + + "AtDvu2pFzilq34VyfDEwarz4FmWMezDbkMf3oyDGR5fiGn+4Rve+iCx/jQhoipIY\n" + + "nXfRiLgP1rXh4kG1y8n4kOJ/D9dqvfuHausm1DOubZ6M0csjftZt61Nmv/i8tyQo\n" + + "eE3jtu8PnMTFpGnh8k0GiVTGzGw6V3blXd9jAN91FTR+fylzFXM1YuWrFY7ig0qI\n" + + "yQ1dUMF/Is2TZdbfgCNC922pQmm1dEhYZX5wRFI9ZstbDACH5fx+yUAdZ8Vu/2zW\n" + + "THxwWJ/X6gGTLqa9CmfDq5UDqYFFzuWwN4HJ+ryOuak1CGwSKJUBSA75HExbv0na\n" + + "Wg+suy+pEDvF0VALPU9VUkSQtHyR10YO2FWOe3AEtpbYDRwpdr1ZwEbb3L6IGQ5i\n" + + "/4CNHbJ2u3yUeXsDNAvrpVSEcIjA01RPCOKmf58SDZp4yDdPxGhM8w6a18+fdQr2\n" + + "2f2cJ0xgfPlbzFbO+FUsEgKvn6QTLhbaYw4zs7rdQDejWHV82hP4K+rb9FwknYdV\n" + + "9uo4m77MgGlU+4yvJnGEYaL3jwjI3bH9aooNOl6XbvVAzNzomYmaTO7mp6xFAu43\n" + + "yuGyd9K+1E4k7CQTROxTZ+RdtQjV95hSsEmMg792nQvDSBW4xwfOQ7pf3kC7r9fm\n" + + "8u9nBlEN12HsbQ8Yvux/ld5q5RaIlD19jzfVR6+hJzbj2ZnUyQs4ksAfIHTzTdLt\n" + + "tRxS9lTRTkVx2vbUnoSBy6TYF1mf6nRPpSm1riZxnkR4+BQL/jEGmn1tLhxfjfDA\n" + + "5vFFj73+FXdFCdFKSI0VpdoU1fgR5DX72ZQUYYUCKYTYikXv1mqdH/5VthptrktC\n" + + "oAco4zVxM04sK7Xthl+uTOhei8/Dd9ZLdSIoNcRjrr/uh5sUzUfIC9iuT3SXiZ/D\n" + + "0yVq0Uu/gWPB3ZIG/sFacxOXAr6RYhvz9MqnwXS1sVT5TyO3XIQ5JseIgIRyV/Sf\n" + + "4F/4Qui9wMzzSajTwCsttMGKf67k228AaJVv+IpFoo+OtCa7wbJukqfNQN3m2ojf\n" + + "V5CcoCzsoRsoTInhrpQmM+gGoQBXBArT1xk3KK3VdZibYfMoxeIGXw0MoNJzFuGK\n" + + "+PcnhV3ETFMNcszd0Pb9s86g7hYtpRmE12Jlai2MzPSmyztlsRP9tcZwYy7JdPZf\n" + + "xXQP24XWat7eP2qWxTnkEP4/wKYb81m7CZ4RvUO/nd1aA5c9IBYknbgmCAAKvHVD\n" + + "iTY61E5GbC9aTiI4WIwjItroikukUJE+p77rpjxfw/1U51BnmQAA/ih5jIthn2ZE\n" + + "r1YoOsUs8CBhylTsRZK6VS4ZCErcyl2tD2LCigQYEQgAPAUCXf4KaQMLCQoJEJun\n" + + "idx21oSaBBUKCQgCFgECF4ACGwwCHgEWIQRx/9oARAnl3bDD6PGbp4ncdtaEmgAA\n" + + "QSkA/3WEWqZxvZmpVxpEMxJWaGQRwUhGake8OhC1WfywCtarAQCLwfBsyEv5jBEi\n" + + "1FkOSekLi8WNMdUx3XMyvP8nJ65P2Q==\n" + + "=Xj8h\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; +} diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index b7032c3..0e71d6b 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -219,6 +219,10 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 53; + public ExpectedText() { + super(); + } + public ExpectedText(String message) { super(message); } From 3eb25038528dfb181e2dc5aea08bac3568fe8ea0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 9 Jan 2023 19:53:18 +0100 Subject: [PATCH 074/444] Add back missing exception constructor --- sop-java/src/main/java/sop/exception/SOPGPException.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 0e71d6b..71c14a7 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -93,6 +93,10 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 19; + public MissingArg() { + + } + public MissingArg(String message) { super(message); } From e3b618a0a863924502d5350ccd0ab011e16da17f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 13:53:49 +0100 Subject: [PATCH 075/444] Wip: Add TempDirProvider --- external-sop/build.gradle | 3 +++ .../main/java/sop/external/ExternalSOP.java | 27 ++++++++++++++++++- .../external/operation/DecryptExternal.java | 17 +++++++++--- .../java/sop/exception/SOPGPException.java | 2 +- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 83bfe3c..87b8e04 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -23,6 +23,9 @@ dependencies { // Compare version strings implementation 'org.apache.maven:maven-artifact:3.6.3' + + // @Nonnull, @Nullable... + implementation "com.google.code.findbugs:jsr305:$jsrVersion" } test { diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index b4763c6..c2ec911 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -33,9 +33,11 @@ import sop.operation.InlineVerify; import sop.operation.Version; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -44,14 +46,24 @@ public class ExternalSOP implements SOP { private final String binaryName; private final Properties properties; + private final TempDirProvider tempDirProvider; public ExternalSOP(String binaryName) { this(binaryName, new Properties()); } public ExternalSOP(String binaryName, Properties properties) { + this(binaryName, properties, defaultTempDirProvider()); + } + + public ExternalSOP(String binaryName, TempDirProvider tempDirProvider) { + this(binaryName, new Properties(), tempDirProvider); + } + + public ExternalSOP(String binaryName, Properties properties, TempDirProvider tempDirProvider) { this.binaryName = binaryName; this.properties = properties; + this.tempDirProvider = tempDirProvider; } @Override @@ -101,7 +113,7 @@ public class ExternalSOP implements SOP { @Override public Decrypt decrypt() { - return new DecryptExternal(binaryName, properties); + return new DecryptExternal(binaryName, properties, tempDirProvider); } @Override @@ -303,4 +315,17 @@ public class ExternalSOP implements SOP { throw new RuntimeException(e); } } + + public interface TempDirProvider { + File provideTempDirectory() throws IOException; + } + + public static TempDirProvider defaultTempDirProvider() { + return new TempDirProvider() { + @Override + public File provideTempDirectory() throws IOException { + return Files.createTempDirectory("ext-sop").toFile(); + } + }; + } } diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index 9373bcd..f6b2b7f 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -12,6 +12,7 @@ import sop.external.ExternalSOP; import sop.operation.Decrypt; import sop.util.UTCUtil; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -23,6 +24,7 @@ import java.util.Properties; public class DecryptExternal implements Decrypt { + private final ExternalSOP.TempDirProvider tempDirProvider; private final List commandList = new ArrayList<>(); private final List envList; @@ -32,7 +34,8 @@ public class DecryptExternal implements Decrypt { private int keyCounter = 0; private int withKeyPasswordCounter = 0; - public DecryptExternal(String binary, Properties environment) { + public DecryptExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { + this.tempDirProvider = tempDirProvider; this.commandList.add(binary); this.commandList.add("decrypt"); this.envList = ExternalSOP.propertiesToEnv(environment); @@ -41,14 +44,14 @@ public class DecryptExternal implements Decrypt { @Override public Decrypt verifyNotBefore(Date timestamp) throws SOPGPException.UnsupportedOption { - this.commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); + this.commandList.add("--verify-not-before=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override public Decrypt verifyNotAfter(Date timestamp) throws SOPGPException.UnsupportedOption { - this.commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); + this.commandList.add("--verify-not-after=" + UTCUtil.formatUTCDate(timestamp)); return this; } @@ -101,6 +104,14 @@ public class DecryptExternal implements Decrypt { public ReadyWithResult ciphertext(InputStream ciphertext) throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, SOPGPException.KeyIsProtected, IOException { + + File tempDir = tempDirProvider.provideTempDirectory(); + File sessionKeyOut = new File(tempDir, "session-key-out"); + commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); + + File verifyOut = new File(tempDir, "verify-out"); + commandList.add("--verify-out=" + verifyOut.getAbsolutePath()); + String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); try { diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 71c14a7..7d15705 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -94,7 +94,7 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 19; public MissingArg() { - + } public MissingArg(String message) { From d079a345d293c5a0a06a5fe5bc566733b71ac12d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 14:40:00 +0100 Subject: [PATCH 076/444] decrypt: Parse out sessionkey and verifications, sign: parse out micalg --- .../main/java/sop/external/ExternalSOP.java | 2 +- .../external/operation/DecryptExternal.java | 33 +++++++++-- .../operation/DetachedSignExternal.java | 27 ++++++++- .../external/EncryptDecryptRoundTripTest.java | 58 ++++++++++++++++++- 4 files changed, 109 insertions(+), 11 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index c2ec911..70acf5b 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -83,7 +83,7 @@ public class ExternalSOP implements SOP { @Override public DetachedSign detachedSign() { - return new DetachedSignExternal(binaryName, properties); + return new DetachedSignExternal(binaryName, properties, tempDirProvider); } @Override diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index f6b2b7f..d0ad5d8 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -7,17 +7,20 @@ package sop.external.operation; import sop.DecryptionResult; import sop.ReadyWithResult; import sop.SessionKey; +import sop.Verification; import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Decrypt; import sop.util.UTCUtil; +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Properties; @@ -104,13 +107,17 @@ public class DecryptExternal implements Decrypt { public ReadyWithResult ciphertext(InputStream ciphertext) throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, SOPGPException.KeyIsProtected, IOException { - File tempDir = tempDirProvider.provideTempDirectory(); + File sessionKeyOut = new File(tempDir, "session-key-out"); + sessionKeyOut.delete(); commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); - File verifyOut = new File(tempDir, "verify-out"); - commandList.add("--verify-out=" + verifyOut.getAbsolutePath()); + File verifyOut = new File(tempDir, "verifications-out"); + verifyOut.delete(); + if (verifyWithCounter != 0) { + commandList.add("--verify-out=" + verifyOut.getAbsolutePath()); + } String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -140,7 +147,23 @@ public class DecryptExternal implements Decrypt { ExternalSOP.finish(process); - return new DecryptionResult(null, Collections.emptyList()); // TODO + FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); + String line = ExternalSOP.readFully(sessionKeyOutIn); + SessionKey sessionKey = SessionKey.fromString(line.trim()); + sessionKeyOutIn.close(); + sessionKeyOut.delete(); + + List verifications = new ArrayList<>(); + if (verifyWithCounter != 0) { + FileInputStream verifyOutIn = new FileInputStream(verifyOut); + BufferedReader reader = new BufferedReader(new InputStreamReader(verifyOutIn)); + while ((line = reader.readLine()) != null) { + verifications.add(Verification.fromString(line.trim())); + } + reader.close(); + } + + return new DecryptionResult(sessionKey, verifications); // TODO } }; } catch (IOException e) { diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index d82950b..9cdf6ec 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -4,6 +4,7 @@ package sop.external.operation; +import sop.MicAlg; import sop.ReadyWithResult; import sop.SigningResult; import sop.enums.SignAs; @@ -11,8 +12,12 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.DetachedSign; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -20,13 +25,15 @@ import java.util.Properties; public class DetachedSignExternal implements DetachedSign { + private final ExternalSOP.TempDirProvider tempDirProvider; private final List commandList = new ArrayList<>(); private final List envList; private int withKeyPasswordCounter = 0; private int keyCounter = 0; - public DetachedSignExternal(String binary, Properties properties) { + public DetachedSignExternal(String binary, Properties properties, ExternalSOP.TempDirProvider tempDirProvider) { + this.tempDirProvider = tempDirProvider; commandList.add(binary); commandList.add("sign"); envList = ExternalSOP.propertiesToEnv(properties); @@ -64,6 +71,11 @@ public class DetachedSignExternal implements DetachedSign { public ReadyWithResult data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + File tempDir = tempDirProvider.provideTempDirectory(); + File micAlgOut = new File(tempDir, "micAlgOut"); + micAlgOut.delete(); + commandList.add("--micalg-out=" + micAlgOut.getAbsolutePath()); + String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); try { @@ -92,7 +104,18 @@ public class DetachedSignExternal implements DetachedSign { ExternalSOP.finish(process); - return SigningResult.builder().build(); + SigningResult.Builder builder = SigningResult.builder(); + if (micAlgOut.exists()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(micAlgOut))); + String line = reader.readLine(); + if (line != null && !line.trim().isEmpty()) { + builder.setMicAlg(MicAlg.fromHashAlgorithmId(Integer.parseInt(line))); + } + reader.close(); + micAlgOut.delete(); + } + + return builder.build(); } }; } catch (IOException e) { diff --git a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java index de16215..4a5ad01 100644 --- a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java @@ -6,15 +6,39 @@ package sop.external; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; +import sop.ByteArrayAndResult; +import sop.DecryptionResult; +import sop.Verification; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { + @Test + public void encryptDecryptRoundTripPasswordTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withPassword("sw0rdf1sh") + .plaintext(message) + .getBytes(); + + byte[] plaintext = getSop().decrypt() + .withPassword("sw0rdf1sh") + .ciphertext(ciphertext) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals(message, plaintext); + } + @Test public void encryptDecryptRoundTripAliceTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); @@ -23,13 +47,16 @@ public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { .plaintext(message) .getBytes(); - byte[] plaintext = getSop().decrypt() + ByteArrayAndResult bytesAndResult = getSop().decrypt() .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) - .toByteArrayAndResult() - .getBytes(); + .toByteArrayAndResult(); + byte[] plaintext = bytesAndResult.getBytes(); assertArrayEquals(message, plaintext); + + DecryptionResult result = bytesAndResult.getResult(); + assertNotNull(result.getSessionKey().get()); } @Test @@ -67,4 +94,29 @@ public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { assertArrayEquals(message, plaintext); } + + @Test + public void encryptSignDecryptVerifyRoundTripAliceTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .plaintext(message) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult(); + + byte[] plaintext = bytesAndResult.getBytes(); + assertArrayEquals(message, plaintext); + + DecryptionResult result = bytesAndResult.getResult(); + assertNotNull(result.getSessionKey().get()); + List verificationList = result.getVerifications(); + assertEquals(1, verificationList.size()); + assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + } } From 909e28432d7a5b5a559d485d146643649c05b5fd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 14:58:42 +0100 Subject: [PATCH 077/444] Wip: Implement result parsing for decrypt, inline-detach and inline-verify --- .../main/java/sop/external/ExternalSOP.java | 4 +-- .../external/operation/DecryptExternal.java | 2 +- .../operation/InlineDetachExternal.java | 29 +++++++++++++++++-- .../operation/InlineVerifyExternal.java | 24 +++++++++++++-- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 70acf5b..6ce8485 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -98,12 +98,12 @@ public class ExternalSOP implements SOP { @Override public InlineVerify inlineVerify() { - return new InlineVerifyExternal(binaryName, properties); + return new InlineVerifyExternal(binaryName, properties, tempDirProvider); } @Override public InlineDetach inlineDetach() { - return new InlineDetachExternal(binaryName, properties); + return new InlineDetachExternal(binaryName, properties, tempDirProvider); } @Override diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index d0ad5d8..b90e657 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -163,7 +163,7 @@ public class DecryptExternal implements Decrypt { reader.close(); } - return new DecryptionResult(sessionKey, verifications); // TODO + return new DecryptionResult(sessionKey, verifications); } }; } catch (IOException e) { diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java index f8ebe09..0ed3e18 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java @@ -10,6 +10,9 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.InlineDetach; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -19,10 +22,12 @@ import java.util.Properties; public class InlineDetachExternal implements InlineDetach { + private final ExternalSOP.TempDirProvider tempDirProvider; private final List commandList = new ArrayList<>(); private final List envList; - public InlineDetachExternal(String binary, Properties environment) { + public InlineDetachExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { + this.tempDirProvider = tempDirProvider; commandList.add(binary); commandList.add("inline-detach"); envList = ExternalSOP.propertiesToEnv(environment); @@ -36,6 +41,12 @@ public class InlineDetachExternal implements InlineDetach { @Override public ReadyWithResult message(InputStream messageInputStream) throws IOException, SOPGPException.BadData { + File tempDir = tempDirProvider.provideTempDirectory(); + + File signaturesOut = new File(tempDir, "signatures"); + signaturesOut.delete(); + commandList.add("--signatures-out=" + signaturesOut.getAbsolutePath()); + String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -65,7 +76,21 @@ public class InlineDetachExternal implements InlineDetach { ExternalSOP.finish(process); - return null; // TODO + FileInputStream signaturesOutIn = new FileInputStream(signaturesOut); + ByteArrayOutputStream signaturesBuffer = new ByteArrayOutputStream(); + while ((r = signaturesOutIn.read(buf)) > 0) { + signaturesBuffer.write(buf, 0, r); + } + signaturesOutIn.close(); + signaturesOut.delete(); + + final byte[] sigBytes = signaturesBuffer.toByteArray(); + return new Signatures() { + @Override + public void writeTo(OutputStream signatureOutputStream) throws IOException { + signatureOutputStream.write(sigBytes); + } + }; } }; } catch (IOException e) { diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java index b18cc4c..17e3ff9 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -11,8 +11,12 @@ import sop.external.ExternalSOP; import sop.operation.InlineVerify; import sop.util.UTCUtil; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; @@ -21,12 +25,14 @@ import java.util.Properties; public class InlineVerifyExternal implements InlineVerify { + private final ExternalSOP.TempDirProvider tempDirProvider; private final List commandList = new ArrayList<>(); private final List envList; private int certCounter = 0; - public InlineVerifyExternal(String binary, Properties environment) { + public InlineVerifyExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { + this.tempDirProvider = tempDirProvider; commandList.add(binary); commandList.add("inline-verify"); envList = ExternalSOP.propertiesToEnv(environment); @@ -54,6 +60,12 @@ public class InlineVerifyExternal implements InlineVerify { @Override public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + File tempDir = tempDirProvider.provideTempDirectory(); + + File verificationsOut = new File(tempDir, "verifications-out"); + verificationsOut.delete(); + commandList.add("--verifications-out=" + verificationsOut.getAbsolutePath()); + String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -84,7 +96,15 @@ public class InlineVerifyExternal implements InlineVerify { ExternalSOP.finish(process); - return null; // TODO + FileInputStream verificationsOutIn = new FileInputStream(verificationsOut); + BufferedReader reader = new BufferedReader(new InputStreamReader(verificationsOutIn)); + List verificationList = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + verificationList.add(Verification.fromString(line.trim())); + } + + return verificationList; } }; } catch (IOException e) { From 9c27141c00a79e1b0bf63a0237a0084a96b14001 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 15:08:26 +0100 Subject: [PATCH 078/444] Add documentation to TempDirProvider --- .../main/java/sop/external/ExternalSOP.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 6ce8485..5cf0716 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.attribute.FileAttribute; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -281,6 +282,7 @@ public class ExternalSOP implements SOP { throw new RuntimeException(e); } } + public static Ready ready(Runtime runtime, List commandList, List envList, InputStream standardIn) { String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -316,10 +318,27 @@ public class ExternalSOP implements SOP { } } + /** + * This interface can be used to provide a directory in which external SOP binaries can temporarily store + * additional results of OpenPGP operations such that the binding classes can parse them out from there. + * Unfortunately, on Java you cannot open {@link java.io.FileDescriptor FileDescriptors} arbitrarily, so we + * have to rely on temporary files to pass results. + * An example: + *
sop decrypt
can emit signature verifications via
--verify-out=/path/to/tempfile
. + * {@link DecryptExternal} will then parse the temp file to make the result available to consumers. + * Temporary files are deleted after being read, yet creating temp files for sensitive information on disk + * might pose a security risk. Use with care! + */ public interface TempDirProvider { File provideTempDirectory() throws IOException; } + /** + * Default implementation of the {@link TempDirProvider} which stores temporary files in the systems temp dir + * ({@link Files#createTempDirectory(String, FileAttribute[])}). + * + * @return default implementation + */ public static TempDirProvider defaultTempDirProvider() { return new TempDirProvider() { @Override From 85f61b413f2643ed4cdcb5de1c77b7d77b78898f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 15:32:40 +0100 Subject: [PATCH 079/444] Add external-sop/README.md --- external-sop/README.md | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 external-sop/README.md diff --git a/external-sop/README.md b/external-sop/README.md new file mode 100644 index 0000000..187683c --- /dev/null +++ b/external-sop/README.md @@ -0,0 +1,52 @@ + + +# External-SOP + +Access an external SOP binary from within your Java/Kotlin application. + +This module implements a backend for `sop-java` that binds to external SOP binaries (such as +[sqop](https://gitlab.com/sequoia-pgp/sequoia-sop/), [python-sop](https://pypi.org/project/sop/) etc.). +SOP operation calls will be delegated to the external binary, and the results are parsed back, so that you can +access them from your Java application as usual. + +## Example +Let's say you are using `ExampleSOP` which is a binary installed in `/usr/bin/example-sop`. +Instantiating a `SOP` object is as simple as this: + +```java +SOP sop = new ExternalSOP("/usr/bin/example-sop"); +``` + +This SOP object can now be used as usual (see [here](../sop-java/README.md)). + +Some SOP binaries might require additional configuration, e.g. a Java based SOP might need to know which JAVA_HOME to use. +For this purpose, additional environment variables can be passed in using a `Properties` object: + +```java +Properties properties = new Properties(); +properties.put("JAVA_HOME", "/usr/lib/jvm/[...]"); +SOP sop = new ExternalSOP("/usr/bin/example-sop", properties); +``` + +Most results of SOP operations are communicated via standard-out, standard-in. However, some operations rely on +writing results to additional output files. +To handle such results, we need to provide a temporary directory, to which those results can be written by the SOP, +and from which `External-SOP` reads them back. +The default implementation relies on `Files.createTempDirectory()` to provide a temporary directory. +It is however possible to overwrite this behavior, in order to specify a custom, perhaps more private directory: + +```java +ExternalSOP.TempDirProvider provider = new ExternalSOP.TempDirProvider() { + @Override + public File provideTempDirectory() throws IOException { + File myTempDir = new File("/path/to/directory"); + myTempDir.mkdirs(); + return myTempDir; + } +}; +SOP sop = new ExternalSOP("/usr/bin/example-sop", provider); +``` From f1c6fd67d3ec49851839cf4a2ba77f78e1082df8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 15:34:22 +0100 Subject: [PATCH 080/444] Update sop-java/README --- sop-java/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sop-java/README.md b/sop-java/README.md index 471d510..507ac5b 100644 --- a/sop-java/README.md +++ b/sop-java/README.md @@ -55,16 +55,13 @@ List signatureVerifications = messageInfo.getVerifications(); Furthermore, the API is capable of signing messages and verifying unencrypted signed data, as well as adding and removing ASCII armor. -### Limitations -As per the spec, sop-java does not (yet) deal with encrypted OpenPGP keys. - ## Why should I use this? If you need to use OpenPGP functionality like encrypting/decrypting messages, or creating/verifying signatures inside your application, you probably don't want to start from scratch and instead reuse some library. Instead of locking yourselves in by depending hard on that one library, you can simply depend on the interfaces from -`sop-java` and plug in a library (such as `pgpainless-sop`) that implements said interfaces. +`sop-java` and plug in a library (such as `pgpainless-sop`, `external-sop`) that implements said interfaces. That way you don't make yourself dependent from a single OpenPGP library and stay flexible. Should another library emerge, that better suits your needs (and implements `sop-java`), you can easily switch From b15acc79b39e71ae5a8a22a7dc9dd2264b940c40 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 15:37:36 +0100 Subject: [PATCH 081/444] Update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc095f6..f23e580 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,10 @@ compatible with the SOP-CLI specification. ## Known Implementations (Please expand!) -| Project | Description | -|---------------------------------------------------------------------------------------|-----------------------------------------------| -| [pgpainless-sop](https://github.com/pgpainless/pgpainless/tree/master/pgpainless-sop) | Implementation of `sop-java` using PGPainless | +| Project | Description | +|-------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [pgpainless-sop](https://github.com/pgpainless/pgpainless/tree/main/pgpainless-sop) | Implementation of `sop-java` using PGPainless | +| [external-sop](https://github.com/pgpainless/sop-java/tree/main/external-sop) | Implementation of `sop-java` that allows binding to external SOP binaries such as `sqop` | ### Implementations in other languages | Project | Language | From 951cf9cbca2d366fb2bc3a94bf9f62f71ac0cc7b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 16:04:22 +0100 Subject: [PATCH 082/444] Fix external armor and dearmor commands --- .../src/main/java/sop/external/operation/ArmorExternal.java | 2 +- .../src/main/java/sop/external/operation/DearmorExternal.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java index 5149792..68edc23 100644 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -35,6 +35,6 @@ public class ArmorExternal implements Armor { @Override public Ready data(InputStream data) throws SOPGPException.BadData, IOException { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java index 936746f..11e0322 100644 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -28,6 +28,6 @@ public class DearmorExternal implements Dearmor { @Override public Ready data(InputStream data) throws SOPGPException.BadData, IOException { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); } } From bd02b11944d6cac0be1c98c9ae59b3b211afe6ce Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 16:04:44 +0100 Subject: [PATCH 083/444] Fix external version command --- .../src/main/java/sop/external/operation/VersionExternal.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index b4fc50b..447078a 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -31,10 +31,10 @@ public class VersionExternal implements Version { Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = stdInput.readLine().trim(); + ExternalSOP.finish(process); if (line.contains(" ")) { return line.substring(0, line.lastIndexOf(" ")); } - ExternalSOP.finish(process); return line; } catch (IOException e) { throw new RuntimeException(e); @@ -49,10 +49,10 @@ public class VersionExternal implements Version { Process process = runtime.exec(command, env); BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = stdInput.readLine().trim(); + ExternalSOP.finish(process); if (line.contains(" ")) { return line.substring(line.lastIndexOf(" ") + 1); } - ExternalSOP.finish(process); return line; } catch (IOException e) { throw new RuntimeException(e); From 6e40c7dc17e746916a39b6769bb5837527bb97f2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Jan 2023 16:55:47 +0100 Subject: [PATCH 084/444] Add initial tests for all operations --- .../operation/DetachedSignExternal.java | 3 +- .../ExternalArmorDearmorRoundTripTest.java | 39 ++++++++ .../ExternalDecryptWithSessionKeyTest.java | 54 +++++++++++ ...ternalDetachedSignVerifyRoundTripTest.java | 74 +++++++++++++++ ... ExternalEncryptDecryptRoundTripTest.java} | 34 ++++++- ...alInlineSignDetachVerifyRoundTripTest.java | 49 ++++++++++ .../ExternalInlineSignVerifyTest.java | 89 +++++++++++++++++++ 7 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java rename external-sop/src/test/java/sop/external/{EncryptDecryptRoundTripTest.java => ExternalEncryptDecryptRoundTripTest.java} (77%) create mode 100644 external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java create mode 100644 external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 9cdf6ec..e48bfa0 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -109,7 +109,8 @@ public class DetachedSignExternal implements DetachedSign { BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(micAlgOut))); String line = reader.readLine(); if (line != null && !line.trim().isEmpty()) { - builder.setMicAlg(MicAlg.fromHashAlgorithmId(Integer.parseInt(line))); + MicAlg micAlg = new MicAlg(line.trim()); + builder.setMicAlg(micAlg); } reader.close(); micAlgOut.delete(); diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java new file mode 100644 index 0000000..0a4dbdf --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.arrayStartsWith; +import static sop.external.JUtils.assertArrayStartsWith; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { + + private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; + private static final byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); + + @Test + public void dearmorArmorAliceKey() throws IOException { + byte[] aliceKey = TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(aliceKey) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java new file mode 100644 index 0000000..fcff3e9 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.ByteArrayAndResult; +import sop.DecryptionResult; +import sop.SessionKey; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { + + private static final String CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wV4DR2b2udXyHrYSAQdAy+Et2hCh4ubh8KsmM8ctRDN6Pee+UHVVcI6YXpY9S2cw\n" + + "1QEROCgfm6xGb+hgxmoFrWhtZU03Arb27ZmpWA6e6Ha9jFdB4/DDbqbhlVuFOmti\n" + + "0j8BqGjEvEYAon+8F9TwMaDbPjjy9SdgQBorlM88ChIW14KQtpG9FZN+r+xVKPG1\n" + + "8EIOxI4qOZaH3Wejraca31M=\n" + + "=1imC\n" + + "-----END PGP MESSAGE-----\n"; + private static final String SESSION_KEY = "9:ED682800F5FEA829A82E8B7DDF8CE9CF4BF9BB45024B017764462EE53101C36A"; + + @Test + public void testDecryptAndExtractSessionKey() throws IOException { + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) + .toByteArrayAndResult(); + + assertEquals(SESSION_KEY, bytesAndResult.getResult().getSessionKey().get().toString()); + + assertArrayEquals("Hello, World!\n".getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); + } + + @Test + public void testDecryptWithSessionKey() throws IOException { + byte[] decrypted = getSop().decrypt() + .withSessionKey(SessionKey.fromString(SESSION_KEY)) + .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) + .toByteArrayAndResult() + .getBytes(); + + assertArrayEquals("Hello, World!\n".getBytes(StandardCharsets.UTF_8), decrypted); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java new file mode 100644 index 0000000..4832770 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.Verification; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static sop.external.JUtils.assertArrayStartsWith; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { + + private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; + private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); + + @Test + public void signVerifyWithAliceKey() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().detachedSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + } + + @Test + public void signVerifyWithFreshEncryptedKey() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); + byte[] key = getSop().generateKey() + .userId("Alice ") + .withKeyPassword(keyPassword) + .generate() + .getBytes(); + + byte[] cert = getSop().extractCert() + .key(key) + .getBytes(); + + byte[] signature = getSop().detachedSign() + .key(key) + .withKeyPassword(keyPassword) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE_BYTES); + + List verificationList = getSop().detachedVerify() + .cert(cert) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + } +} diff --git a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java similarity index 77% rename from external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java rename to external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 4a5ad01..95bf15a 100644 --- a/external-sop/src/test/java/sop/external/EncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -16,11 +16,12 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") -public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { +public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest { @Test public void encryptDecryptRoundTripPasswordTest() throws IOException { @@ -119,4 +120,35 @@ public class EncryptDecryptRoundTripTest extends AbstractExternalSOPTest { assertEquals(1, verificationList.size()); assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); } + + @Test + public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest() throws IOException { + byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); + byte[] key = getSop().generateKey() + .withKeyPassword(keyPassword) + .userId("Alice ") + .generate() + .getBytes(); + byte[] cert = getSop().extractCert() + .key(key) + .getBytes(); + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(cert) + .signWith(key) + .withKeyPassword(keyPassword) + .plaintext(message) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(key) + .withKeyPassword(keyPassword) + .verifyWithCert(cert) + .ciphertext(ciphertext) + .toByteArrayAndResult(); + + assertFalse(bytesAndResult.getResult().getVerifications().isEmpty()); + assertArrayEquals(message, bytesAndResult.getBytes()); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java new file mode 100644 index 0000000..6a47f46 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.ByteArrayAndResult; +import sop.Signatures; +import sop.Verification; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { + + @Test + public void inlineSignThenDetachThenDetachedVerifyTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().inlineDetach() + .message(inlineSigned) + .toByteArrayAndResult(); + + byte[] plaintext = bytesAndResult.getBytes(); + assertArrayEquals(message, plaintext); + + byte[] signatures = bytesAndResult.getResult() + .getBytes(); + + List verifications = getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signatures) + .data(plaintext); + + assertFalse(verifications.isEmpty()); + } +} diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java new file mode 100644 index 0000000..31687e9 --- /dev/null +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.ByteArrayAndResult; +import sop.Verification; +import sop.enums.InlineSignAs; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { + + private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; + private static final byte[] BEGIN_PGP_MESSAGE_BYTES = BEGIN_PGP_MESSAGE.getBytes(StandardCharsets.UTF_8); + private static final String BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n"; + private static final byte[] BEGIN_PGP_SIGNED_MESSAGE_BYTES = BEGIN_PGP_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + + @Test + public void inlineSignVerifyAlice() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .getBytes(); + + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(inlineSigned) + .toByteArrayAndResult(); + + assertArrayEquals(message, bytesAndResult.getBytes()); + assertFalse(bytesAndResult.getResult().isEmpty()); + } + + @Test + public void inlineSignVerifyAliceNoArmor() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .noArmor() + .data(message) + .getBytes(); + + assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES)); + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(inlineSigned) + .toByteArrayAndResult(); + + assertArrayEquals(message, bytesAndResult.getBytes()); + assertFalse(bytesAndResult.getResult().isEmpty()); + } + + @Test + public void clearsignVerifyAlice() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] clearsigned = getSop().inlineSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .mode(InlineSignAs.clearsigned) + .data(message) + .getBytes(); + + JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE_BYTES); + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(clearsigned) + .toByteArrayAndResult(); + + assertArrayEquals(message, bytesAndResult.getBytes()); + assertFalse(bytesAndResult.getResult().isEmpty()); + } +} From 125eefed6e85b36c99066f0515d03de073f13a9b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 16:06:17 +0100 Subject: [PATCH 085/444] Fix IOException when trying to close already-closed output stream --- external-sop/src/main/java/sop/external/ExternalSOP.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 5cf0716..30154cd 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -301,7 +301,11 @@ public class ExternalSOP implements SOP { } standardIn.close(); - processOut.close(); + try { + processOut.close(); + } catch (IOException e) { + // ignore + } while ((r = processIn.read(buf)) > 0) { outputStream.write(buf, 0 , r); From 670aa0f2424a50435ca443a7126bed23280d7dfa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 16:06:34 +0100 Subject: [PATCH 086/444] Add test for dearmoring and armoring certificate --- .../ExternalArmorDearmorRoundTripTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index 0a4dbdf..7763842 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -19,6 +19,8 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; private static final byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); + private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; + private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); @Test public void dearmorArmorAliceKey() throws IOException { @@ -36,4 +38,22 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } + + + @Test + public void dearmorArmorAliceCert() throws IOException { + byte[] aliceCert = TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(aliceCert) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + } } From 4726362df8bca5f93202993d05673bac6e0734a5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 17:03:05 +0100 Subject: [PATCH 087/444] Add more armor/dearmor tests --- .../main/java/sop/external/ExternalSOP.java | 13 +- .../ExternalArmorDearmorRoundTripTest.java | 165 +++++++++++++++++- .../src/test/java/sop/external/JUtils.java | 19 ++ 3 files changed, 189 insertions(+), 8 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 30154cd..c3ecd00 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -274,6 +274,7 @@ public class ExternalSOP implements SOP { outputStream.write(buf, 0, r); } + outputStream.flush(); outputStream.close(); ExternalSOP.finish(process); } @@ -299,19 +300,17 @@ public class ExternalSOP implements SOP { while ((r = standardIn.read(buf)) > 0) { processOut.write(buf, 0, r); } - standardIn.close(); - try { - processOut.close(); - } catch (IOException e) { - // ignore - } + + processOut.flush(); + processOut.close(); while ((r = processIn.read(buf)) > 0) { outputStream.write(buf, 0 , r); } - processIn.close(); + + outputStream.flush(); outputStream.close(); finish(process); diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index 7763842..4079fd0 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -10,9 +10,11 @@ import org.junit.jupiter.api.condition.EnabledIf; import java.io.IOException; import java.nio.charset.StandardCharsets; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; +import static sop.external.JUtils.assertAsciiArmorEquals; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @@ -21,6 +23,10 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { private static final byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); + private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; + private static final byte[] BEGIN_PGP_MESSAGE_BYTES = BEGIN_PGP_MESSAGE.getBytes(StandardCharsets.UTF_8); + private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; + private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); @Test public void dearmorArmorAliceKey() throws IOException { @@ -37,9 +43,9 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .getBytes(); assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(aliceKey, armored); } - @Test public void dearmorArmorAliceCert() throws IOException { byte[] aliceCert = TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8); @@ -55,5 +61,162 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .getBytes(); assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(aliceCert, armored); } + + @Test + public void dearmorArmorBobKey() throws IOException { + byte[] bobKey = TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(bobKey) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(bobKey, armored); + } + + @Test + public void dearmorArmorBobCert() throws IOException { + byte[] bobCert = TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(bobCert) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(bobCert, armored); + } + + @Test + public void dearmorArmorCarolKey() throws IOException { + byte[] carolKey = TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(carolKey) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(carolKey, armored); + } + + @Test + public void dearmorArmorCarolCert() throws IOException { + byte[] carolCert = TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(carolCert) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + assertAsciiArmorEquals(carolCert, armored); + } + + @Test + public void dearmorArmorMessage() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // falsely reports Invalid Data Type + byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wV4DR2b2udXyHrYSAQdAMZy9Iqb1IxszjI3v+TsfK//0lnJ9PKHDqVAB5ohp+RMw\n" + + "8fmuL3phS9uISFT/DrizC8ALJhMqw5R+lLB/RvTTA/qS6tN5dRyL+YLFU3/N0CRF\n" + + "0j8BtQEsMmRo60LzUq/OBI0dFjwFq1efpfOGkpRYkuIzndCjBEgnLUkrHzUc1uD9\n" + + "CePQFpprprnGEzpE3flQLUc=\n" + + "=ZiFR\n" + + "-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8); + byte[] dearmored = getSop().dearmor() + .data(message) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_MESSAGE_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_MESSAGE_BYTES); + assertAsciiArmorEquals(message, armored); + } + + @Test + public void dearmorArmorSignature() throws IOException { + byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wr0EABYKAG8FgmPBdRAJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + + "LnNlcXVvaWEtcGdwLm9yZ2un17fF3C46Adgzp0mU4RG8Txy/T/zOBcBw/NYaLGrQ\n" + + "FiEE64W7X6M6deFelE5j8jFVDE9H444AAMiEAP9LBQWLo4oP5IrFZPuSUQSPsUxB\n" + + "c+Qu1raXDKzS/8Q9IAD+LnHIjRHcqNPobNHXF/saXIYXeZR+LJKszTJozzwqdQE=\n" + + "=GHvQ\n" + + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); + + byte[] dearmored = getSop().dearmor() + .data(signature) + .getBytes(); + + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_SIGNATURE_BYTES)); + + byte[] armored = getSop().armor() + .data(dearmored) + .getBytes(); + + assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE_BYTES); + assertAsciiArmorEquals(signature, armored); + } + + @Test + public void testDearmoringTwiceIsIdempotent() throws IOException { + ignoreIf("sqop", Is.eq, "0.27.2"); // IO error because: EOF + + byte[] dearmored = getSop().dearmor() + .data(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .getBytes(); + + byte[] dearmoredAgain = getSop().dearmor() + .data(dearmored) + .getBytes(); + + assertArrayEquals(dearmored, dearmoredAgain); + } + + @Test + public void testArmoringTwiceIsIdempotent() throws IOException { + byte[] armored = ("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "wr0EABYKAG8FgmPBdRAJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + + "LnNlcXVvaWEtcGdwLm9yZ2un17fF3C46Adgzp0mU4RG8Txy/T/zOBcBw/NYaLGrQ\n" + + "FiEE64W7X6M6deFelE5j8jFVDE9H444AAMiEAP9LBQWLo4oP5IrFZPuSUQSPsUxB\n" + + "c+Qu1raXDKzS/8Q9IAD+LnHIjRHcqNPobNHXF/saXIYXeZR+LJKszTJozzwqdQE=\n" + + "=GHvQ\n" + + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); + + byte[] armoredAgain = getSop().armor() + .data(armored) + .getBytes(); + + assertAsciiArmorEquals(armored, armoredAgain); + } + } diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/external-sop/src/test/java/sop/external/JUtils.java index 36d6d85..d0d1601 100644 --- a/external-sop/src/test/java/sop/external/JUtils.java +++ b/external-sop/src/test/java/sop/external/JUtils.java @@ -4,8 +4,10 @@ package sop.external; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.fail; public class JUtils { @@ -50,4 +52,21 @@ public class JUtils { "Actual: <" + Arrays.toString(actual) + ">"); } } + + public static void assertAsciiArmorEquals(byte[] first, byte[] second) { + byte[] firstCleaned = removeArmorHeaders(first); + byte[] secondCleaned = removeArmorHeaders(second); + + assertArrayEquals(firstCleaned, secondCleaned); + } + + public static byte[] removeArmorHeaders(byte[] armor) { + String string = new String(armor, StandardCharsets.UTF_8); + string = string.replaceAll("Comment: .+\\R", "") + .replaceAll("Version: .+\\R", "") + .replaceAll("MessageID: .+\\R", "") + .replaceAll("Hash: .+\\R", "") + .replaceAll("Charset: .+\\R", ""); + return string.getBytes(StandardCharsets.UTF_8); + } } From eded55c259991ee4ed21b174065f5fdac1c2a2c4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 17:54:37 +0100 Subject: [PATCH 088/444] Add more tests --- ...ternalDetachedSignVerifyRoundTripTest.java | 24 ++++++ .../ExternalEncryptDecryptRoundTripTest.java | 74 +++++++++++++++++++ .../sop/external/ExternalGenerateKeyTest.java | 4 +- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index 4832770..efe4a68 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -71,4 +71,28 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertFalse(verificationList.isEmpty()); } + + @Test + public void signArmorVerifyWithBobKey() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().detachedSign() + .key(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .noArmor() + .data(message) + .toByteArrayAndResult() + .getBytes(); + + byte[] armored = getSop().armor() + .data(signature) + .getBytes(); + + List verificationList = getSop().detachedVerify() + .cert(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(armored) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertTrue(verificationList.get(0).toString().contains("D1A66E1A23B182C9980F788CFBFCC82A015E7330 D1A66E1A23B182C9980F788CFBFCC82A015E7330")); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 95bf15a..2405e66 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -9,15 +9,19 @@ import org.junit.jupiter.api.condition.EnabledIf; import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.Verification; +import sop.exception.SOPGPException; +import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") @@ -151,4 +155,74 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertFalse(bytesAndResult.getResult().getVerifications().isEmpty()); assertArrayEquals(message, bytesAndResult.getBytes()); } + + @Test + public void decryptVerifyNotAfterTest() { + ignoreIf("PGPainless-SOP", Is.le, "1.4.2"); // does not recognize --verify-not-after + ignoreIf("sqop", Is.leq, "0.27.2"); // does not throw NoSignature + + byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + + "2Hka48AFVfOezYh0OFn9R8+DMcpuE+e4nw3XnnX5nKs/j3AC2IW6zRHUkRcF3ZCq\n" + + "0sBNAfjnTYCMjuBmqdcCLzaZT4Hadnpg6neP1UecT/jP14maGfv8nwt0IDGR0Bik\n" + + "0WC/UJLpWyJ/6TgRrA5hNfANVnfiFBzIiThiVBRWPT2StHr2cOAvFxQK4Uk07rK9\n" + + "9aTUak8FpML+QA83U8I3qOk4QbzGVBP+IDJ+AKmvDz+0V+9kUhKp+8vyXsBmo9c3\n" + + "SAXjhFSiPQkU7ORsc6gQHL9+KPOU+W2poPK87H3cmaGiusnXMeLXLIUbkBUJTswd\n" + + "JNrA2yAkTTFP9QabsdcdTGoeYamq1c29kHF3GOTTcEqXw4WWXngcF7Kbcf435kkL\n" + + "4iSJnCaxTPftKUxmiGqMqLef7ICVnq/lz3HrH1VD54s=\n" + + "=Ebi3\n" + + "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:09:32Z"); + + Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date + + assertThrows(SOPGPException.NoSignature.class, () -> { + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .verifyNotAfter(beforeSignature) + .ciphertext(message) + .toByteArrayAndResult(); + + if (bytesAndResult.getResult().getVerifications().isEmpty()) { + throw new SOPGPException.NoSignature("No verifiable signature found."); + } + }); + } + + @Test + public void decryptVerifyNotBeforeTest() { + ignoreIf("PGPainless-SOP", Is.le, "1.4.2"); // does not recognize --verify-not-after + ignoreIf("sqop", Is.leq, "0.27.2"); // does not throw NoSignature + + byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + + "2Hka48AFVfOezYh0OFn9R8+DMcpuE+e4nw3XnnX5nKs/j3AC2IW6zRHUkRcF3ZCq\n" + + "0sBNAfjnTYCMjuBmqdcCLzaZT4Hadnpg6neP1UecT/jP14maGfv8nwt0IDGR0Bik\n" + + "0WC/UJLpWyJ/6TgRrA5hNfANVnfiFBzIiThiVBRWPT2StHr2cOAvFxQK4Uk07rK9\n" + + "9aTUak8FpML+QA83U8I3qOk4QbzGVBP+IDJ+AKmvDz+0V+9kUhKp+8vyXsBmo9c3\n" + + "SAXjhFSiPQkU7ORsc6gQHL9+KPOU+W2poPK87H3cmaGiusnXMeLXLIUbkBUJTswd\n" + + "JNrA2yAkTTFP9QabsdcdTGoeYamq1c29kHF3GOTTcEqXw4WWXngcF7Kbcf435kkL\n" + + "4iSJnCaxTPftKUxmiGqMqLef7ICVnq/lz3HrH1VD54s=\n" + + "=Ebi3\n" + + "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:09:32Z"); + + Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date + + assertThrows(SOPGPException.NoSignature.class, () -> { + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .verifyNotBefore(afterSignature) + .ciphertext(message) + .toByteArrayAndResult(); + + if (bytesAndResult.getResult().getVerifications().isEmpty()) { + throw new SOPGPException.NoSignature("No verifiable signature found."); + } + }); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index c492ebe..0e23992 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -81,7 +81,9 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { @Test public void generateKeyWithMultipleUserIdsAndPassword() throws IOException { ignoreIf("sqop", Is.le, "0.27.0"); - ignoreIf("pgpainless-cli", Is.le, "1.3.15"); + ignoreIf("PGPainless-SOP", Is.le, "1.3.15"); + ignoreIf("PGPainless-SOP", Is.eq, "1.4.0"); + ignoreIf("PGPainless-SOP", Is.eq, "1.4.1"); byte[] key = getSop().generateKey() .userId("Alice ") From 8fc88b5bab84bebce993a86d7c132656f2943e8f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 18:50:19 +0100 Subject: [PATCH 089/444] Increase test coverage --- .../main/java/sop/external/ExternalSOP.java | 8 +- ...ternalDetachedSignVerifyRoundTripTest.java | 71 +++++++++++++++ .../ExternalEncryptDecryptRoundTripTest.java | 53 ++++++++++++ ...alInlineSignDetachVerifyRoundTripTest.java | 42 +++++++++ .../ExternalInlineSignVerifyTest.java | 86 +++++++++++++++++++ 5 files changed, 258 insertions(+), 2 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index c3ecd00..555c856 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -302,8 +302,12 @@ public class ExternalSOP implements SOP { } standardIn.close(); - processOut.flush(); - processOut.close(); + try { + processOut.flush(); + processOut.close(); + } catch (IOException e) { + // ignore + } while ((r = processIn.read(buf)) > 0) { outputStream.write(buf, 0 , r); diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index efe4a68..3354389 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -7,12 +7,17 @@ package sop.external; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import sop.Verification; +import sop.enums.SignAs; +import sop.exception.SOPGPException; +import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static sop.external.JUtils.assertArrayStartsWith; @@ -41,8 +46,30 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); } + @Test + public void signVerifyTextModeWithAliceKey() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().detachedSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .mode(SignAs.Text) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + } + @Test public void signVerifyWithFreshEncryptedKey() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // --with-key-password not supported + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); byte[] key = getSop().generateKey() @@ -95,4 +122,48 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertFalse(verificationList.isEmpty()); assertTrue(verificationList.get(0).toString().contains("D1A66E1A23B182C9980F788CFBFCC82A015E7330 D1A66E1A23B182C9980F788CFBFCC82A015E7330")); } + + @Test + public void verifyNotAfterThrowsNoSignature() { + ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iHUEABYKACcFAmPBjZUJEPIxVQxPR+OOFiEE64W7X6M6deFelE5j8jFVDE9H444A\n" + + "ADI/AQC6Bux6WpGYf7HO+QPV/D5iIrqZt9xPLgfUVoNJBmMZZwD+Ib+tn5pSyWUw\n" + + "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + + "=bxPN\n" + + "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig + + assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .notAfter(beforeSignature) + .signatures(signature) + .data(message)); + } + + @Test + public void verifyNotBeforeThrowsNoSignature() { + ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iHUEABYKACcFAmPBjZUJEPIxVQxPR+OOFiEE64W7X6M6deFelE5j8jFVDE9H444A\n" + + "ADI/AQC6Bux6WpGYf7HO+QPV/D5iIrqZt9xPLgfUVoNJBmMZZwD+Ib+tn5pSyWUw\n" + + "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + + "=bxPN\n" + + "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after sig + + assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .notBefore(afterSignature) + .signatures(signature) + .data(message)); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 2405e66..90f08ba 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.condition.EnabledIf; import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.Verification; +import sop.enums.EncryptAs; import sop.exception.SOPGPException; import sop.util.UTCUtil; @@ -100,6 +101,30 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, plaintext); } + @Test + public void encryptNoArmorThenArmorThenDecryptRoundTrip() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // Invalid data type + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .noArmor() + .plaintext(message) + .getBytes(); + + byte[] armored = getSop().armor() + .data(ciphertext) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .ciphertext(armored) + .toByteArrayAndResult(); + + byte[] plaintext = bytesAndResult.getBytes(); + assertArrayEquals(message, plaintext); + } + @Test public void encryptSignDecryptVerifyRoundTripAliceTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); @@ -125,8 +150,36 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); } + @Test + public void encryptSignAsTextDecryptVerifyRoundTripAliceTest() throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] ciphertext = getSop().encrypt() + .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .mode(EncryptAs.Text) + .plaintext(message) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().decrypt() + .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .ciphertext(ciphertext) + .toByteArrayAndResult(); + + byte[] plaintext = bytesAndResult.getBytes(); + assertArrayEquals(message, plaintext); + + DecryptionResult result = bytesAndResult.getResult(); + assertNotNull(result.getSessionKey().get()); + List verificationList = result.getVerifications(); + assertEquals(1, verificationList.size()); + assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + } + @Test public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); + byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); byte[] key = getSop().generateKey() .withKeyPassword(keyPassword) diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java index 6a47f46..f49601d 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -16,12 +16,18 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static sop.external.JUtils.arrayStartsWith; +import static sop.external.JUtils.assertArrayStartsWith; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { + private static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); + @Test public void inlineSignThenDetachThenDetachedVerifyTest() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() @@ -46,4 +52,40 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna assertFalse(verifications.isEmpty()); } + + @Test + public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .getBytes(); + + ByteArrayAndResult bytesAndResult = getSop().inlineDetach() + .noArmor() + .message(inlineSigned) + .toByteArrayAndResult(); + + byte[] plaintext = bytesAndResult.getBytes(); + assertArrayEquals(message, plaintext); + + byte[] signatures = bytesAndResult.getResult() + .getBytes(); + assertFalse(arrayStartsWith(signatures, BEGIN_PGP_SIGNATURE)); + + byte[] armored = getSop().armor() + .data(signatures) + .getBytes(); + assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); + + List verifications = getSop().detachedVerify() + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(armored) + .data(plaintext); + + assertFalse(verifications.isEmpty()); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java index 31687e9..d99270c 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -9,13 +9,17 @@ import org.junit.jupiter.api.condition.EnabledIf; import sop.ByteArrayAndResult; import sop.Verification; import sop.enums.InlineSignAs; +import sop.exception.SOPGPException; +import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Date; import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @@ -27,6 +31,8 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @Test public void inlineSignVerifyAlice() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() @@ -47,6 +53,8 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @Test public void inlineSignVerifyAliceNoArmor() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() @@ -68,6 +76,8 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @Test public void clearsignVerifyAlice() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] clearsigned = getSop().inlineSign() @@ -86,4 +96,80 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertArrayEquals(message, bytesAndResult.getBytes()); assertFalse(bytesAndResult.getResult().isEmpty()); } + + @Test + public void assertNotBeforeThrowsNoSignature() { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) + + byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "owGbwMvMwCX2yTCUx9/9cR/jaZEkBhDwSM3JyddRCM8vyklR5OooZWEQ42JQZ2VK\n" + + "PjjpPacATLmYIsvr1t3xi61KH8ZN8UuGCTMwpPcw/E9jS+vcvPu2gmp4jcRbcSNP\n" + + "FYmW8hmLJdUVrdt1V8w6GM/IMEvN0tP339sNGX4swq8T5p62q3jUfLjpstmcI6Ie\n" + + "sfcfswMA\n" + + "=RDAo\n" + + "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig + + assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() + .notBefore(afterSignature) + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult()); + } + + @Test + public void assertNotAfterThrowsNoSignature() { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) + + byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "owGbwMvMwCX2yTCUx9/9cR/jaZEkBhDwSM3JyddRCM8vyklR5OooZWEQ42JQZ2VK\n" + + "PjjpPacATLmYIsvr1t3xi61KH8ZN8UuGCTMwpPcw/E9jS+vcvPu2gmp4jcRbcSNP\n" + + "FYmW8hmLJdUVrdt1V8w6GM/IMEvN0tP339sNGX4swq8T5p62q3jUfLjpstmcI6Ie\n" + + "sfcfswMA\n" + + "=RDAo\n" + + "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); + Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig + + assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() + .notAfter(beforeSignature) + .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult()); + } + + @Test + public void signVerifyWithPasswordProtectedKey() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); + byte[] key = getSop().generateKey() + .userId("Alice ") + .withKeyPassword(keyPassword) + .generate() + .getBytes(); + byte[] cert = getSop().extractCert() + .key(key) + .getBytes(); + + byte[] inlineSigned = getSop().inlineSign() + .withKeyPassword(keyPassword) + .key(key) + .mode(InlineSignAs.binary) + .data(message) + .getBytes(); + + assertFalse(getSop().inlineVerify() + .cert(cert) + .data(inlineSigned) + .toByteArrayAndResult() + .getResult() + .isEmpty()); + } } From b42d0e89a1cebefe8ba7f8f6e200e44dd9985624 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 19:03:33 +0100 Subject: [PATCH 090/444] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d73b04..ef37535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 # Changelog ## 4.0.8-SNAPSHOT +- Add module `external-sop` + - This module implements the `sop-java` interfaces and allows the use of an external SOP binary - `decrypt`: Rename `--not-before`, `--not-after` to `--verify-not-before`, `--verify-not-after` - `decrypt`: Throw `NoSignature` error if no verifiable signature found, but signature verification is requested using `--verify-with`. - `inline-sign`: Fix parameter label of `--as=clearsigned` From 78b5eea630fa6c3a7ac8e53a3b8c985d3c04d2b0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 19:06:19 +0100 Subject: [PATCH 091/444] SOP-Java 4.1.0 --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef37535..e6e8a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.0.8-SNAPSHOT +## 4.1.0 - Add module `external-sop` - This module implements the `sop-java` interfaces and allows the use of an external SOP binary - `decrypt`: Rename `--not-before`, `--not-after` to `--verify-not-before`, `--verify-not-after` diff --git a/version.gradle b/version.gradle index a069baf..fc14298 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.0.8' - isSnapshot = true + shortVersion = '4.1.0' + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 jsrVersion = '3.0.2' From 4fc8ffab42413e7d7a6d36953582fad793cd0041 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Jan 2023 19:08:18 +0100 Subject: [PATCH 092/444] SOP-Java 4.1.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index fc14298..d956fc9 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.1.0' - isSnapshot = false + shortVersion = '4.1.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 jsrVersion = '3.0.2' From 990d3147092e0b21a70804a269e15f14374b885b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Jan 2023 17:29:29 +0100 Subject: [PATCH 093/444] Add javadoc comments on top of external sop operations --- external-sop/src/main/java/sop/external/ExternalSOP.java | 3 +++ .../src/main/java/sop/external/operation/ArmorExternal.java | 3 +++ .../src/main/java/sop/external/operation/DearmorExternal.java | 3 +++ .../src/main/java/sop/external/operation/DecryptExternal.java | 3 +++ .../main/java/sop/external/operation/DetachedSignExternal.java | 3 +++ .../java/sop/external/operation/DetachedVerifyExternal.java | 3 +++ .../src/main/java/sop/external/operation/EncryptExternal.java | 3 +++ .../main/java/sop/external/operation/ExtractCertExternal.java | 3 +++ .../main/java/sop/external/operation/GenerateKeyExternal.java | 3 +++ .../main/java/sop/external/operation/InlineDetachExternal.java | 3 +++ .../main/java/sop/external/operation/InlineSignExternal.java | 3 +++ .../main/java/sop/external/operation/InlineVerifyExternal.java | 3 +++ .../src/main/java/sop/external/operation/VersionExternal.java | 3 +++ 13 files changed, 39 insertions(+) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 555c856..b18e438 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -43,6 +43,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link SOP} API using an external SOP binary. + */ public class ExternalSOP implements SOP { private final String binaryName; diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java index 68edc23..bf9ddcd 100644 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -16,6 +16,9 @@ import java.util.ArrayList; import java.util.Properties; import java.util.List; +/** + * Implementation of the {@link Armor} operation using an external SOP binary. + */ public class ArmorExternal implements Armor { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java index 11e0322..2406131 100644 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -15,6 +15,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link Dearmor} operation using an external SOP binary. + */ public class DearmorExternal implements Dearmor { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index b90e657..84da064 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -25,6 +25,9 @@ import java.util.Date; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link Decrypt} operation using an external SOP binary. + */ public class DecryptExternal implements Decrypt { private final ExternalSOP.TempDirProvider tempDirProvider; diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index e48bfa0..3deabb2 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -23,6 +23,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link DetachedSign} operation using an external SOP binary. + */ public class DetachedSignExternal implements DetachedSign { private final ExternalSOP.TempDirProvider tempDirProvider; diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java index d95c9dc..ed658c7 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -23,6 +23,9 @@ import java.util.List; import java.util.Properties; import java.util.Set; +/** + * Implementation of the {@link DetachedVerify} operation using an external SOP binary. + */ public class DetachedVerifyExternal implements DetachedVerify { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index 7096842..f45cd12 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -16,6 +16,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link Encrypt} operation using an external SOP binary. + */ public class EncryptExternal implements Encrypt { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index 68d0750..549b1ad 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -14,6 +14,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link ExtractCert} operation using an external SOP binary. + */ public class ExtractCertExternal implements ExtractCert { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index b348fa4..d125fc0 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -13,6 +13,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link GenerateKey} operation using an external SOP binary. + */ public class GenerateKeyExternal implements GenerateKey { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java index 0ed3e18..c03fe1b 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java @@ -20,6 +20,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link InlineDetach} operation using an external SOP binary. + */ public class InlineDetachExternal implements InlineDetach { private final ExternalSOP.TempDirProvider tempDirProvider; diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java index 2a49ad5..17e5b12 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -16,6 +16,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link InlineSign} operation using an external SOP binary. + */ public class InlineSignExternal implements InlineSign { private final List commandList = new ArrayList<>(); diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java index 17e3ff9..5fbdde6 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -23,6 +23,9 @@ import java.util.Date; import java.util.List; import java.util.Properties; +/** + * Implementation of the {@link InlineVerify} operation using an external SOP binary. + */ public class InlineVerifyExternal implements InlineVerify { private final ExternalSOP.TempDirProvider tempDirProvider; diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index 447078a..6848cab 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -12,6 +12,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.Properties; +/** + * Implementation of the {@link Version} operation using an external SOP binary. + */ public class VersionExternal implements Version { private final Runtime runtime = Runtime.getRuntime(); From 0616cde6fd5ebc72833066bced1c4225cc0db84f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Jan 2023 17:51:29 +0100 Subject: [PATCH 094/444] Add documentation of the ExternalSOP class --- .../main/java/sop/external/ExternalSOP.java | 115 +++++++++++++----- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index b18e438..432a16d 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -32,6 +32,7 @@ import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.Version; +import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -52,19 +53,47 @@ public class ExternalSOP implements SOP { private final Properties properties; private final TempDirProvider tempDirProvider; - public ExternalSOP(String binaryName) { + /** + * Instantiate an {@link ExternalSOP} object for the given binary and pass it empty environment variables, + * as well as a default {@link TempDirProvider}. + * + * @param binaryName name / path of the SOP binary + */ + public ExternalSOP(@Nonnull String binaryName) { this(binaryName, new Properties()); } - public ExternalSOP(String binaryName, Properties properties) { + /** + * Instantiate an {@link ExternalSOP} object for the given binary, and pass it the given properties as + * environment variables, as well as a default {@link TempDirProvider}. + * + * @param binaryName name / path of the SOP binary + * @param properties environment variables + */ + public ExternalSOP(@Nonnull String binaryName, @Nonnull Properties properties) { this(binaryName, properties, defaultTempDirProvider()); } - public ExternalSOP(String binaryName, TempDirProvider tempDirProvider) { + /** + * Instantiate an {@link ExternalSOP} object for the given binary and the given {@link TempDirProvider} + * using empty environment variables. + * + * @param binaryName name / path of the SOP binary + * @param tempDirProvider custom tempDirProvider + */ + public ExternalSOP(@Nonnull String binaryName, @Nonnull TempDirProvider tempDirProvider) { this(binaryName, new Properties(), tempDirProvider); } - public ExternalSOP(String binaryName, Properties properties, TempDirProvider tempDirProvider) { + /** + * Instantiate an {@link ExternalSOP} object for the given binary using the given properties and + * custom {@link TempDirProvider}. + * + * @param binaryName name / path of the SOP binary + * @param properties environment variables + * @param tempDirProvider tempDirProvider + */ + public ExternalSOP(@Nonnull String binaryName, @Nonnull Properties properties, @Nonnull TempDirProvider tempDirProvider) { this.binaryName = binaryName; this.properties = properties; this.tempDirProvider = tempDirProvider; @@ -130,27 +159,26 @@ public class ExternalSOP implements SOP { return new DearmorExternal(binaryName, properties); } - public static void finish(Process process) throws IOException { + public static void finish(@Nonnull Process process) throws IOException { try { mapExitCodeOrException(process); - } catch (SOPGPException e) { - InputStream errIn = process.getErrorStream(); - ByteArrayOutputStream errOut = new ByteArrayOutputStream(); - byte[] buf = new byte[512]; - int r; - while ((r = errIn.read(buf)) > 0) { - errOut.write(buf, 0, r); - } - - e.initCause(new IOException(errOut.toString())); - throw e; - } - catch (InterruptedException e) { + } catch (InterruptedException e) { throw new RuntimeException(e); } } - private static void mapExitCodeOrException(Process process) throws InterruptedException, IOException { + /** + * Wait for the {@link Process} to finish and read out its exit code. + * If the exit code is {@value "0"}, this method just returns. + * Otherwise, the exit code gets mapped to a {@link SOPGPException} which then gets thrown. + * If the exit code does not match any of the known exit codes defined in the SOP specification, + * this method throws a {@link RuntimeException} instead. + * + * @param process process + * @throws InterruptedException if the thread is interrupted before the process could exit + * @throws IOException in case of an IO error + */ + private static void mapExitCodeOrException(@Nonnull Process process) throws InterruptedException, IOException { int exitCode = process.waitFor(); if (exitCode == 0) { @@ -158,14 +186,7 @@ public class ExternalSOP implements SOP { } InputStream errIn = process.getErrorStream(); - ByteArrayOutputStream errOut = new ByteArrayOutputStream(); - byte[] buf = new byte[512]; - int r; - while ((r = errIn.read(buf)) > 0) { - errOut.write(buf, 0, r); - } - - String errorMessage = errOut.toString(); + String errorMessage = readFully(errIn); switch (exitCode) { case SOPGPException.NoSignature.EXIT_CODE: @@ -242,7 +263,14 @@ public class ExternalSOP implements SOP { } } - public static List propertiesToEnv(Properties properties) { + /** + * Return all key-value pairs from the given {@link Properties} object as a list with items of the form + *
key=value
. + * + * @param properties properties + * @return list of key=value strings + */ + public static List propertiesToEnv(@Nonnull Properties properties) { List env = new ArrayList<>(); for (Object key : properties.keySet()) { env.add(key + "=" + properties.get(key)); @@ -250,7 +278,14 @@ public class ExternalSOP implements SOP { return env; } - public static String readFully(InputStream inputStream) throws IOException { + /** + * Read the contents of the {@link InputStream} and return them as a {@link String}. + * + * @param inputStream input stream + * @return string + * @throws IOException in case of an IO error + */ + public static String readFully(@Nonnull InputStream inputStream) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; int r; @@ -260,7 +295,15 @@ public class ExternalSOP implements SOP { return bOut.toString(); } - public static Ready ready(Runtime runtime, List commandList, List envList) { + /** + * Execute the given command on the given {@link Runtime} with the given list of environment variables. + * + * @param runtime runtime + * @param commandList command + * @param envList environment variables + * @return ready to read the result from + */ + public static Ready ready(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList) { String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -287,7 +330,17 @@ public class ExternalSOP implements SOP { } } - public static Ready ready(Runtime runtime, List commandList, List envList, InputStream standardIn) { + /** + * Execute the given command on the given runtime using the given environment variables. + * The given input stream provides input for the process. + * + * @param runtime runtime + * @param commandList command + * @param envList environment variables + * @param standardIn stream of input data for the process + * @return ready to read the result from + */ + public static Ready ready(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList, @Nonnull InputStream standardIn) { String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); try { From 104b3a4ec4082632e3457c522beb18246af2f0fb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Jan 2023 18:00:41 +0100 Subject: [PATCH 095/444] Add documentation and fixes to AbstractExternalSOPTest --- .../sop/external/AbstractExternalSOPTest.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index d557fd8..bfdc334 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -16,6 +16,7 @@ import java.io.InputStream; import java.util.Properties; import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; public abstract class AbstractExternalSOPTest { @@ -25,6 +26,7 @@ public abstract class AbstractExternalSOPTest { public AbstractExternalSOPTest() { String backend = readSopBackendFromProperties(); + assumeTrue(backend != null); Properties environment = readBackendEnvironment(); sop = new ExternalSOP(backend, environment); } @@ -51,11 +53,33 @@ public abstract class AbstractExternalSOPTest { return new File(binary).exists(); } + /** + * Relational enum. + */ public enum Is { + /** + * Less than. + */ le("<"), + /** + * Less or equal than. + */ leq("<="), + /** + * Equal. + */ eq("=="), + /** + * Not equal. + */ + neq("!="), + /** + * Greater or equal than. + */ geq(">="), + /** + * Greater than. + */ ge(">"), ; @@ -105,6 +129,9 @@ public abstract class AbstractExternalSOPTest { case eq: assumeFalse(res == 0, msg); break; + case neq: + assumeFalse(res != 0, msg); + break; case geq: assumeFalse(res >= 0, msg); break; @@ -127,8 +154,7 @@ public abstract class AbstractExternalSOPTest { } properties.load(resourceIn); - String backend = properties.getProperty("sop.backend"); - return backend; + return properties.getProperty("sop.backend"); } catch (IOException e) { return null; } From 61c5eb2962f3d6ffb9bf432dd7997afb95924c1d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Jan 2023 18:10:31 +0100 Subject: [PATCH 096/444] Add javadoc to JUtils --- .../src/test/java/sop/external/JUtils.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/external-sop/src/test/java/sop/external/JUtils.java index d0d1601..38a5829 100644 --- a/external-sop/src/test/java/sop/external/JUtils.java +++ b/external-sop/src/test/java/sop/external/JUtils.java @@ -12,10 +12,25 @@ import static org.junit.jupiter.api.Assertions.fail; public class JUtils { + /** + * Return true, if the given
array
starts with
start
. + * + * @param array array + * @param start start + * @return true if array starts with start, false otherwise + */ public static boolean arrayStartsWith(byte[] array, byte[] start) { return arrayStartsWith(array, start, 0); } + /** + * Return true, if the given
array
contains the given
start
at offset
offset
. + * + * @param array array + * @param start start + * @param offset offset + * @return true, if array contains start at offset, false otherwise + */ public static boolean arrayStartsWith(byte[] array, byte[] start, int offset) { if (offset < 0) { throw new IllegalArgumentException("Offset cannot be negative"); @@ -33,6 +48,12 @@ public class JUtils { return true; } + /** + * Assert that the given
array
starts with
start
. + * + * @param array array + * @param start start + */ public static void assertArrayStartsWith(byte[] array, byte[] start) { if (!arrayStartsWith(array, start)) { byte[] actual = new byte[Math.min(start.length, array.length)]; @@ -43,6 +64,13 @@ public class JUtils { } } + /** + * Assert that the given
array
contains
start
at
offset
. + * + * @param array array + * @param start start + * @param offset offset + */ public static void assertArrayStartsWith(byte[] array, byte[] start, int offset) { if (!arrayStartsWith(array, start, offset)) { byte[] actual = new byte[Math.min(start.length, array.length - offset)]; @@ -53,6 +81,12 @@ public class JUtils { } } + /** + * Assert equality of the given two ascii armored byte arrays, ignoring armor header lines. + * + * @param first first ascii armored bytes + * @param second second ascii armored bytes + */ public static void assertAsciiArmorEquals(byte[] first, byte[] second) { byte[] firstCleaned = removeArmorHeaders(first); byte[] secondCleaned = removeArmorHeaders(second); @@ -60,6 +94,13 @@ public class JUtils { assertArrayEquals(firstCleaned, secondCleaned); } + /** + * Remove armor headers "Comment:", "Version:", "MessageID:", "Hash:" and "Charset:" along with their values + * from the given ascii armored byte array. + * + * @param armor ascii armored byte array + * @return ascii armored byte array with header lines removed + */ public static byte[] removeArmorHeaders(byte[] armor) { String string = new String(armor, StandardCharsets.UTF_8); string = string.replaceAll("Comment: .+\\R", "") From ffc5b26c0dc78cde7838fc3635fd419409ca38f4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Jan 2023 14:30:41 +0100 Subject: [PATCH 097/444] Add test for unsupported subcommands --- .../sop/external/AbstractExternalSOPTest.java | 2 +- .../external/UnsupportedSubcommandTest.java | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index bfdc334..2781feb 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -141,7 +141,7 @@ public abstract class AbstractExternalSOPTest { } } - private static String readSopBackendFromProperties() { + static String readSopBackendFromProperties() { Properties properties = new Properties(); try { InputStream resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.local.properties"); diff --git a/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java b/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java new file mode 100644 index 0000000..8196871 --- /dev/null +++ b/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import sop.exception.SOPGPException; + +import java.io.IOException; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") +public class UnsupportedSubcommandTest extends AbstractExternalSOPTest { + + private final UnsupportedSubcommandExternal unsupportedSubcommand; + + public UnsupportedSubcommandTest() { + String backend = readSopBackendFromProperties(); + assumeTrue(backend != null); + Properties environment = readBackendEnvironment(); + unsupportedSubcommand = new UnsupportedSubcommandExternal(backend, environment); + } + + @Test + public void testUnsupportedSubcommand() { + // "sop unsupported" returns error code UNSUPPORTED_SUBCOMMAND + assertThrows(SOPGPException.UnsupportedSubcommand.class, + unsupportedSubcommand::executeUnsupportedSubcommand); + } + + private static class UnsupportedSubcommandExternal { + + private final Runtime runtime = Runtime.getRuntime(); + private final String binary; + private final Properties environment; + + public UnsupportedSubcommandExternal(String binaryName, Properties environment) { + this.binary = binaryName; + this.environment = environment; + } + + public void executeUnsupportedSubcommand() { + String[] command = new String[] {binary, "unsupported"}; // ~$ sop unsupported + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); + try { + Process process = runtime.exec(command, env); + ExternalSOP.finish(process); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} From 0d9db2bdd39bedf108d3b98f93e0fac5d45b7894 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Jan 2023 14:37:58 +0100 Subject: [PATCH 098/444] Add tests for extracting certs from known keys --- .../sop/external/ExternalExtractCertTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 559316f..98111f2 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; +import static sop.external.JUtils.assertAsciiArmorEquals; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalExtractCertTest extends AbstractExternalSOPTest { @@ -32,6 +33,30 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); } + @Test + public void extractAliceCertFromAliceKeyTest() throws IOException { + byte[] armoredCert = getSop().extractCert() + .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .getBytes(); + assertAsciiArmorEquals(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + } + + @Test + public void extractBobsCertFromBobsKeyTest() throws IOException { + byte[] armoredCert = getSop().extractCert() + .key(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .getBytes(); + assertAsciiArmorEquals(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + } + + @Test + public void extractCarolsCertFromCarolsKeyTest() throws IOException { + byte[] armoredCert = getSop().extractCert() + .key(TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .getBytes(); + assertAsciiArmorEquals(TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + } + @Test public void extractUnarmoredCertFromArmoredKeyTest() throws IOException { InputStream keyIn = getSop().generateKey() From c95ca8fedc4ee404e3e24521ecfe565b892b60c1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Jan 2023 14:58:21 +0100 Subject: [PATCH 099/444] Add test for signing with protected key without password --- .../operation/DetachedSignExternal.java | 6 ++++- ...ternalDetachedSignVerifyRoundTripTest.java | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 3deabb2..1f9aa81 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -96,7 +96,11 @@ public class DetachedSignExternal implements DetachedSign { } data.close(); - processOut.close(); + try { + processOut.close(); + } catch (IOException e) { + // Ignore Stream closed + } while ((r = processIn.read(buf)) > 0) { outputStream.write(buf, 0 , r); diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index 3354389..5dca76d 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -166,4 +166,27 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP .signatures(signature) .data(message)); } + + + @Test + public void signVerifyWithFreshEncryptedKeyWithoutPassphraseFails() throws IOException { + ignoreIf("sqop", Is.leq, "0.27.2"); // does not return exit code 67 for encrypted keys without passphrase + + byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); + byte[] key = getSop().generateKey() + .userId("Alice ") + .withKeyPassword(keyPassword) + .generate() + .getBytes(); + + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + assertThrows(SOPGPException.KeyIsProtected.class, () -> + getSop().detachedSign() + .key(key) + .data(message) + .toByteArrayAndResult() + .getBytes()); + } + } From d09626782dcdf0c84d531ca1ff0c0951c8a4e349 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 20 Jan 2023 15:16:47 +0100 Subject: [PATCH 100/444] Unify tests by turning password-protected keys into variable --- ...ternalDetachedSignVerifyRoundTripTest.java | 56 +++++++-------- .../src/test/java/sop/external/TestKeys.java | 71 +++++++++++++++++++ .../external/UnsupportedSubcommandTest.java | 2 +- 3 files changed, 100 insertions(+), 29 deletions(-) diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index 5dca76d..63638cc 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -67,24 +67,12 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP } @Test - public void signVerifyWithFreshEncryptedKey() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // --with-key-password not supported - + public void signVerifyWithEncryptedKey() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); - byte[] key = getSop().generateKey() - .userId("Alice ") - .withKeyPassword(keyPassword) - .generate() - .getBytes(); - - byte[] cert = getSop().extractCert() - .key(key) - .getBytes(); byte[] signature = getSop().detachedSign() - .key(key) - .withKeyPassword(keyPassword) + .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword(TestKeys.PASSWORD) .data(message) .toByteArrayAndResult() .getBytes(); @@ -92,7 +80,7 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE_BYTES); List verificationList = getSop().detachedVerify() - .cert(cert) + .cert(TestKeys.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -169,24 +157,36 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @Test - public void signVerifyWithFreshEncryptedKeyWithoutPassphraseFails() throws IOException { + public void signVerifyWithEncryptedKeyWithoutPassphraseFails() { ignoreIf("sqop", Is.leq, "0.27.2"); // does not return exit code 67 for encrypted keys without passphrase - byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); - byte[] key = getSop().generateKey() - .userId("Alice ") - .withKeyPassword(keyPassword) - .generate() - .getBytes(); - - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - assertThrows(SOPGPException.KeyIsProtected.class, () -> getSop().detachedSign() - .key(key) - .data(message) + .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .data("Hello, World!\n".getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() .getBytes()); } + @Test + public void signWithProtectedKeyAndMultiplePassphrasesTest() + throws IOException { + byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().sign() + .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword("wrong") + .withKeyPassword(TestKeys.PASSWORD) // correct + .withKeyPassword("wrong2") + .data(message) + .toByteArrayAndResult() + .getBytes(); + + assertFalse(getSop().verify() + .cert(TestKeys.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message) + .isEmpty()); + } + } diff --git a/external-sop/src/test/java/sop/external/TestKeys.java b/external-sop/src/test/java/sop/external/TestKeys.java index 1a2bb94..b7f73c3 100644 --- a/external-sop/src/test/java/sop/external/TestKeys.java +++ b/external-sop/src/test/java/sop/external/TestKeys.java @@ -304,4 +304,75 @@ public class TestKeys { "1FkOSekLi8WNMdUx3XMyvP8nJ65P2Q==\n" + "=Xj8h\n" + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + public static final String PASSWORD_PROTECTED_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: FC63 688A 5E69 8C29 40AF 7029 7C62 2B00 D459 2657\n" + + "Comment: Password Protected \n" + + "\n" + + "xYYEY8qfmxYJKwYBBAHaRw8BAQdAv5atAPgP3WOvjoeEGAXIpX+k9LbX1+roEQQE\n" + + "WaQfbMv+CQMI7d4yuArkBqz/J/UllaSoHN2kYdJE4Biiqgto2d39B8JRCrb0LSeX\n" + + "25TolXynV3bdiTsVKtnNOOcCzP09kDMu8uCMpregFrMdI511iR+dysLAEQQfFgoA\n" + + "gwWCY8qfmwWJBZ+mAAMLCQcJEHxiKwDUWSZXRxQAAAAAAB4AIHNhbHRAbm90YXRp\n" + + "b25zLnNlcXVvaWEtcGdwLm9yZ5Rt+kxLFFiFbTaZO2Rbf52K6FEcetqiht8jk9Vt\n" + + "DObSAxUKCAKbAQIeARYhBPxjaIpeaYwpQK9wKXxiKwDUWSZXAABTzQEA9Vy2e5eU\n" + + "dFj+gfwPULtwEJqMpj29eN37J0VfwF1RdW0BAMeXutE1dzL5PdIIX8VJAIv9RXVR\n" + + "lw5TujtjLhr8uzEKzSpQYXNzd29yZCBQcm90ZWN0ZWQgPHByb3RlY3RlZEBvcGVu\n" + + "cGdwLm9yZz7CwBQEExYKAIYFgmPKn5sFiQWfpgADCwkHCRB8YisA1FkmV0cUAAAA\n" + + "AAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmdTOuFjGL7cyOIVEfem\n" + + "5b/gCJLP6LFKTy3P/gFGRB7VEAMVCggCmQECmwECHgEWIQT8Y2iKXmmMKUCvcCl8\n" + + "YisA1FkmVwAARXsBAP4jwRWnAqEe59BV+0WviYzC8NhKpIjXwRQIM5yD6E90AQCQ\n" + + "wfhqsexB2rVQGw0siW2c/3DUhmnK8osNK5f8iLv5BceGBGPKn5sWCSsGAQQB2kcP\n" + + "AQEHQM/fv1zxwMjruKiq9W7PcMUbcMKQ3lbFdqPtwEJ16LxY/gkDCA3yM1VPvA6b\n" + + "/1vqf8sxU96j7CAMZaQRutdRd1xwFxx9ZIvhrPjm23nCcURzmnPflnKdx/p8/QVj\n" + + "jTQufQbnZkrmo/fg+eZURLX6O3Op2svCwMUEGBYKATcFgmPKn5sFiQWfpgAJEHxi\n" + + "KwDUWSZXRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ0X3\n" + + "iVnya1OCsmkt7OijGLXSTv9FRbFVf+fcQGSMzViBApsCvqAEGRYKAG8FgmPKn5sJ\n" + + "EGiGL7kPBxZbRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNlcXVvaWEtcGdwLm9y\n" + + "Z62UKDMX/Uh2ywrGJjKi3Ex6jpghyED+kPlNuxS8uMs6FiEE2PHLwmEzUNGnZtNf\n" + + "aIYvuQ8HFlsAAPgiAPwIlVOxTF7J80KAiHrApEgfLHsEeGivjEtnkKO6eUa2awEA\n" + + "5qlATwB3bQVkMFYa893MxrjVmmasil81uwMiU8gtRQoWIQT8Y2iKXmmMKUCvcCl8\n" + + "YisA1FkmVwAAktwBAOEXjAXOZaFM8PoSNtrKVLakPXCadY8zduAqqgmp5PBwAP0R\n" + + "EpO9g0mQuCCmg6eeXm2GxChWORWArh9of7l/epycAceLBGPKn5sSCisGAQQBl1UB\n" + + "BQEBB0DDEzY37G8GNXIJqbVsawutIqNTZcizObXrau9F0H5wHQMBCAf+CQMI9ppA\n" + + "+RYt5Sv/gIPNmVm7UraBpK75qOC/tN9h/uNaaadcgrWEXMr6+YWjvBmH+iCV61/y\n" + + "b9Gkfxn2V/lw8asgch86Y6tN0Rhy+uXTFKMHecLABgQYFgoAeAWCY8qfmwWJBZ+m\n" + + "AAkQfGIrANRZJldHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Au\n" + + "b3JnYeL3YzJjnG3vwSjzVnzgbFCe5QyC0/mFnqML7+hQi0kCmwwWIQT8Y2iKXmmM\n" + + "KUCvcCl8YisA1FkmVwAAbRcA/3haEwnnHhitQNbvDs2DqzVvz0QtjEW59ZKFgzX2\n" + + "PUMXAQDJzcz9GoPTqU8hioiSBoQUjN883qv6sJHiEveRyDbMDQ==\n" + + "=xHUd\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + public static final String PASSWORD_PROTECTED_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: FC63 688A 5E69 8C29 40AF 7029 7C62 2B00 D459 2657\n" + + "Comment: Password Protected \n" + + "\n" + + "xjMEY8qfmxYJKwYBBAHaRw8BAQdAv5atAPgP3WOvjoeEGAXIpX+k9LbX1+roEQQE\n" + + "WaQfbMvCwBEEHxYKAIMFgmPKn5sFiQWfpgADCwkHCRB8YisA1FkmV0cUAAAAAAAe\n" + + "ACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmeUbfpMSxRYhW02mTtkW3+d\n" + + "iuhRHHraoobfI5PVbQzm0gMVCggCmwECHgEWIQT8Y2iKXmmMKUCvcCl8YisA1Fkm\n" + + "VwAAU80BAPVctnuXlHRY/oH8D1C7cBCajKY9vXjd+ydFX8BdUXVtAQDHl7rRNXcy\n" + + "+T3SCF/FSQCL/UV1UZcOU7o7Yy4a/LsxCs0qUGFzc3dvcmQgUHJvdGVjdGVkIDxw\n" + + "cm90ZWN0ZWRAb3BlbnBncC5vcmc+wsAUBBMWCgCGBYJjyp+bBYkFn6YAAwsJBwkQ\n" + + "fGIrANRZJldHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn\n" + + "UzrhYxi+3MjiFRH3puW/4AiSz+ixSk8tz/4BRkQe1RADFQoIApkBApsBAh4BFiEE\n" + + "/GNoil5pjClAr3ApfGIrANRZJlcAAEV7AQD+I8EVpwKhHufQVftFr4mMwvDYSqSI\n" + + "18EUCDOcg+hPdAEAkMH4arHsQdq1UBsNLIltnP9w1IZpyvKLDSuX/Ii7+QXOMwRj\n" + + "yp+bFgkrBgEEAdpHDwEBB0DP379c8cDI67ioqvVuz3DFG3DCkN5WxXaj7cBCdei8\n" + + "WMLAxQQYFgoBNwWCY8qfmwWJBZ+mAAkQfGIrANRZJldHFAAAAAAAHgAgc2FsdEBu\n" + + "b3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnRfeJWfJrU4KyaS3s6KMYtdJO/0VFsVV/\n" + + "59xAZIzNWIECmwK+oAQZFgoAbwWCY8qfmwkQaIYvuQ8HFltHFAAAAAAAHgAgc2Fs\n" + + "dEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnrZQoMxf9SHbLCsYmMqLcTHqOmCHI\n" + + "QP6Q+U27FLy4yzoWIQTY8cvCYTNQ0adm019ohi+5DwcWWwAA+CIA/AiVU7FMXsnz\n" + + "QoCIesCkSB8sewR4aK+MS2eQo7p5RrZrAQDmqUBPAHdtBWQwVhrz3czGuNWaZqyK\n" + + "XzW7AyJTyC1FChYhBPxjaIpeaYwpQK9wKXxiKwDUWSZXAACS3AEA4ReMBc5loUzw\n" + + "+hI22spUtqQ9cJp1jzN24CqqCank8HAA/RESk72DSZC4IKaDp55ebYbEKFY5FYCu\n" + + "H2h/uX96nJwBzjgEY8qfmxIKKwYBBAGXVQEFAQEHQMMTNjfsbwY1cgmptWxrC60i\n" + + "o1NlyLM5tetq70XQfnAdAwEIB8LABgQYFgoAeAWCY8qfmwWJBZ+mAAkQfGIrANRZ\n" + + "JldHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnYeL3YzJj\n" + + "nG3vwSjzVnzgbFCe5QyC0/mFnqML7+hQi0kCmwwWIQT8Y2iKXmmMKUCvcCl8YisA\n" + + "1FkmVwAAbRcA/3haEwnnHhitQNbvDs2DqzVvz0QtjEW59ZKFgzX2PUMXAQDJzcz9\n" + + "GoPTqU8hioiSBoQUjN883qv6sJHiEveRyDbMDQ==\n" + + "=xlgc\n" + + "-----END PGP PUBLIC KEY BLOCK-----\n"; + public static final String PASSWORD = "sw0rdf1sh"; } diff --git a/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java b/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java index 8196871..55e13e2 100644 --- a/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java +++ b/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java @@ -39,7 +39,7 @@ public class UnsupportedSubcommandTest extends AbstractExternalSOPTest { private final String binary; private final Properties environment; - public UnsupportedSubcommandExternal(String binaryName, Properties environment) { + UnsupportedSubcommandExternal(String binaryName, Properties environment) { this.binary = binaryName; this.environment = environment; } From 9cf6301b8c3e2ac4a7e84b5bd94de9f1b0dc7899 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 21 Jan 2023 20:31:49 +0100 Subject: [PATCH 101/444] More tests --- .../ExternalArmorDearmorRoundTripTest.java | 14 +- .../ExternalDecryptWithSessionKeyTest.java | 2 +- ...ternalDetachedSignVerifyRoundTripTest.java | 127 +++++++++------ .../ExternalEncryptDecryptRoundTripTest.java | 40 ++--- .../sop/external/ExternalExtractCertTest.java | 12 +- ...alInlineSignDetachVerifyRoundTripTest.java | 8 +- .../ExternalInlineSignVerifyTest.java | 146 ++++++++++++------ .../src/test/java/sop/external/JUtils.java | 68 ++++++++ .../external/{TestKeys.java => TestData.java} | 36 ++++- 9 files changed, 319 insertions(+), 134 deletions(-) rename external-sop/src/test/java/sop/external/{TestKeys.java => TestData.java} (93%) diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index 4079fd0..c4cd4ae 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -30,7 +30,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorAliceKey() throws IOException { - byte[] aliceKey = TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8); + byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(aliceKey) @@ -48,7 +48,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorAliceCert() throws IOException { - byte[] aliceCert = TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8); + byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(aliceCert) @@ -66,7 +66,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorBobKey() throws IOException { - byte[] bobKey = TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8); + byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(bobKey) @@ -84,7 +84,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorBobCert() throws IOException { - byte[] bobCert = TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8); + byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(bobCert) @@ -102,7 +102,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorCarolKey() throws IOException { - byte[] carolKey = TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8); + byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(carolKey) @@ -120,7 +120,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { @Test public void dearmorArmorCarolCert() throws IOException { - byte[] carolCert = TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8); + byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8); byte[] dearmored = getSop().dearmor() .data(carolCert) @@ -191,7 +191,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { ignoreIf("sqop", Is.eq, "0.27.2"); // IO error because: EOF byte[] dearmored = getSop().dearmor() - .data(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); byte[] dearmoredAgain = getSop().dearmor() diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java index fcff3e9..0c1f7c7 100644 --- a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java @@ -32,7 +32,7 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { @Test public void testDecryptAndExtractSessionKey() throws IOException { ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult(); diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index 63638cc..bdab4b5 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.condition.EnabledIf; import sop.Verification; import sop.enums.SignAs; import sop.exception.SOPGPException; -import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -18,8 +17,8 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static sop.external.JUtils.assertArrayStartsWith; +import static sop.external.JUtils.assertSignedBy; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { @@ -29,50 +28,102 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @Test public void signVerifyWithAliceKey() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = getSop().detachedSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @Test public void signVerifyTextModeWithAliceKey() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = getSop().detachedSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(SignAs.Text) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + } + + @Test + public void verifyKnownMessageWithAliceCert() throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + + List verificationList = getSop().detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE); + } + + @Test + public void signVerifyWithBobKey() throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().detachedSign() + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = getSop().detachedVerify() + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + } + + @Test + public void signVerifyWithCarolKey() throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = getSop().detachedSign() + .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = getSop().detachedVerify() + .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } @Test public void signVerifyWithEncryptedKey() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = getSop().detachedSign() - .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .withKeyPassword(TestKeys.PASSWORD) + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword(TestData.PASSWORD) .data(message) .toByteArrayAndResult() .getBytes(); @@ -80,7 +131,7 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE_BYTES); List verificationList = getSop().detachedVerify() - .cert(TestKeys.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -89,10 +140,10 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @Test public void signArmorVerifyWithBobKey() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = getSop().detachedSign() - .key(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .toByteArrayAndResult() @@ -103,31 +154,25 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP .getBytes(); List verificationList = getSop().detachedVerify() - .cert(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(message); assertFalse(verificationList.isEmpty()); - assertTrue(verificationList.get(0).toString().contains("D1A66E1A23B182C9980F788CFBFCC82A015E7330 D1A66E1A23B182C9980F788CFBFCC82A015E7330")); + assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } @Test public void verifyNotAfterThrowsNoSignature() { ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + - "\n" + - "iHUEABYKACcFAmPBjZUJEPIxVQxPR+OOFiEE64W7X6M6deFelE5j8jFVDE9H444A\n" + - "ADI/AQC6Bux6WpGYf7HO+QPV/D5iIrqZt9xPLgfUVoNJBmMZZwD+Ib+tn5pSyWUw\n" + - "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + - "=bxPN\n" + - "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); - Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notAfter(beforeSignature) .signatures(signature) .data(message)); @@ -137,19 +182,13 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP public void verifyNotBeforeThrowsNoSignature() { ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + - "\n" + - "iHUEABYKACcFAmPBjZUJEPIxVQxPR+OOFiEE64W7X6M6deFelE5j8jFVDE9H444A\n" + - "ADI/AQC6Bux6WpGYf7HO+QPV/D5iIrqZt9xPLgfUVoNJBmMZZwD+Ib+tn5pSyWUw\n" + - "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + - "=bxPN\n" + - "-----END PGP SIGNATURE-----").getBytes(StandardCharsets.UTF_8); - Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after sig assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notBefore(afterSignature) .signatures(signature) .data(message)); @@ -162,8 +201,8 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertThrows(SOPGPException.KeyIsProtected.class, () -> getSop().detachedSign() - .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .data("Hello, World!\n".getBytes(StandardCharsets.UTF_8)) + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() .getBytes()); } @@ -171,19 +210,19 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @Test public void signWithProtectedKeyAndMultiplePassphrasesTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = getSop().sign() - .key(TestKeys.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword("wrong") - .withKeyPassword(TestKeys.PASSWORD) // correct + .withKeyPassword(TestData.PASSWORD) // correct .withKeyPassword("wrong2") .data(message) .toByteArrayAndResult() .getBytes(); assertFalse(getSop().verify() - .cert(TestKeys.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message) .isEmpty()); diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 90f08ba..41bb861 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -49,12 +49,12 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptDecryptRoundTripAliceTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -69,12 +69,12 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptDecryptRoundTripBobTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = getSop().decrypt() - .withKey(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -88,12 +88,12 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = getSop().decrypt() - .withKey(TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -107,7 +107,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) .getBytes(); @@ -117,7 +117,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest .getBytes(); ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(armored) .toByteArrayAndResult(); @@ -129,14 +129,14 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptSignDecryptVerifyRoundTripAliceTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -154,15 +154,15 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptSignAsTextDecryptVerifyRoundTripAliceTest() throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() - .withCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.Text) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -232,8 +232,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotAfter(beforeSignature) .ciphertext(message) .toByteArrayAndResult(); @@ -267,8 +267,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = getSop().decrypt() - .withKey(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotBefore(afterSignature) .ciphertext(message) .toByteArrayAndResult(); diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 98111f2..93357a5 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -36,25 +36,25 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { @Test public void extractAliceCertFromAliceKeyTest() throws IOException { byte[] armoredCert = getSop().extractCert() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @Test public void extractBobsCertFromBobsKeyTest() throws IOException { byte[] armoredCert = getSop().extractCert() - .key(TestKeys.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestKeys.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @Test public void extractCarolsCertFromCarolsKeyTest() throws IOException { byte[] armoredCert = getSop().extractCert() - .key(TestKeys.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestKeys.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @Test diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java index f49601d..41bc67c 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -31,7 +31,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -46,7 +46,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna .getBytes(); List verifications = getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(plaintext); @@ -60,7 +60,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -82,7 +82,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); List verifications = getSop().detachedVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(plaintext); diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java index d99270c..f0a632e 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -10,7 +10,6 @@ import sop.ByteArrayAndResult; import sop.Verification; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; -import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -20,6 +19,7 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static sop.external.JUtils.assertSignedBy; @EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @@ -33,32 +33,33 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { public void inlineSignVerifyAlice() throws IOException { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); - assertFalse(bytesAndResult.getResult().isEmpty()); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @Test public void inlineSignVerifyAliceNoArmor() throws IOException { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .getBytes(); @@ -66,22 +67,23 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES)); ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); - assertFalse(bytesAndResult.getResult().isEmpty()); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @Test public void clearsignVerifyAlice() throws IOException { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] clearsigned = getSop().inlineSign() - .key(TestKeys.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.clearsigned) .data(message) .getBytes(); @@ -89,12 +91,29 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE_BYTES); ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(clearsigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); - assertFalse(bytesAndResult.getResult().isEmpty()); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + } + + @Test + public void inlineVerifyCompareSignatureDate() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) + + byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult(); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate); } @Test @@ -102,20 +121,13 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "owGbwMvMwCX2yTCUx9/9cR/jaZEkBhDwSM3JyddRCM8vyklR5OooZWEQ42JQZ2VK\n" + - "PjjpPacATLmYIsvr1t3xi61KH8ZN8UuGCTMwpPcw/E9jS+vcvPu2gmp4jcRbcSNP\n" + - "FYmW8hmLJdUVrdt1V8w6GM/IMEvN0tP339sNGX4swq8T5p62q3jUfLjpstmcI6Ie\n" + - "sfcfswMA\n" + - "=RDAo\n" + - "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); - Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() .notBefore(afterSignature) - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @@ -125,51 +137,83 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "owGbwMvMwCX2yTCUx9/9cR/jaZEkBhDwSM3JyddRCM8vyklR5OooZWEQ42JQZ2VK\n" + - "PjjpPacATLmYIsvr1t3xi61KH8ZN8UuGCTMwpPcw/E9jS+vcvPu2gmp4jcRbcSNP\n" + - "FYmW8hmLJdUVrdt1V8w6GM/IMEvN0tP339sNGX4swq8T5p62q3jUfLjpstmcI6Ie\n" + - "sfcfswMA\n" + - "=RDAo\n" + - "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8); - Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() .notAfter(beforeSignature) - .cert(TestKeys.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @Test - public void signVerifyWithPasswordProtectedKey() throws IOException { + public void inlineSignVerifyBob() throws IOException { ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); - byte[] key = getSop().generateKey() - .userId("Alice ") - .withKeyPassword(keyPassword) - .generate() - .getBytes(); - byte[] cert = getSop().extractCert() - .key(key) - .getBytes(); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = getSop().inlineSign() - .withKeyPassword(keyPassword) - .key(key) + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .getBytes(); + + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .data(inlineSigned) + .toByteArrayAndResult(); + + assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + } + + @Test + public void inlineSignVerifyCarol() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .getBytes(); + + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .data(inlineSigned) + .toByteArrayAndResult(); + + assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + } + + @Test + public void inlineSignVerifyProtectedKey() throws IOException { + ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported + + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = getSop().inlineSign() + .withKeyPassword(TestData.PASSWORD) + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.binary) .data(message) .getBytes(); - assertFalse(getSop().inlineVerify() - .cert(cert) + ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) - .toByteArrayAndResult() - .getResult() - .isEmpty()); + .toByteArrayAndResult(); + + List verificationList = bytesAndResult.getResult(); + assertSignedBy(verificationList, TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); } + } diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/external-sop/src/test/java/sop/external/JUtils.java index 38a5829..0b15142 100644 --- a/external-sop/src/test/java/sop/external/JUtils.java +++ b/external-sop/src/test/java/sop/external/JUtils.java @@ -4,8 +4,13 @@ package sop.external; +import sop.Verification; +import sop.util.UTCUtil; + import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.Date; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -110,4 +115,67 @@ public class JUtils { .replaceAll("Charset: .+\\R", ""); return string.getBytes(StandardCharsets.UTF_8); } + + public static void assertSignedBy(List verifications, String primaryFingerprint) { + for (Verification verification : verifications) { + if (verification.getSigningCertFingerprint().equals(primaryFingerprint)) { + return; + } + } + + if (verifications.isEmpty()) { + fail("Verification list is empty."); + } + + fail("Verification list does not contain verification by cert " + primaryFingerprint + ":\n" + + Arrays.toString(verifications.toArray(new Verification[0]))); + } + + public static void assertSignedBy(List verifications, String signingFingerprint, String primaryFingerprint) { + for (Verification verification : verifications) { + if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && verification.getSigningKeyFingerprint().equals(signingFingerprint)) { + return; + } + } + + if (verifications.isEmpty()) { + fail("Verification list is empty."); + } + + fail("Verification list does not contain verification by key " + signingFingerprint + " on cert " + primaryFingerprint + ":\n" + + Arrays.toString(verifications.toArray(new Verification[0]))); + } + + public static void assertSignedBy(List verifications, String primaryFingerprint, Date signatureDate) { + for (Verification verification : verifications) { + if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && + verification.getCreationTime().equals(signatureDate)) { + return; + } + } + + if (verifications.isEmpty()) { + fail("Verification list is empty."); + } + + fail("Verification list does not contain verification by cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" + + Arrays.toString(verifications.toArray(new Verification[0]))); + } + + public static void assertSignedBy(List verifications, String signingFingerprint, String primaryFingerprint, Date signatureDate) { + for (Verification verification : verifications) { + if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && + verification.getSigningKeyFingerprint().equals(signingFingerprint) && + verification.getCreationTime().equals(signatureDate)) { + return; + } + } + + if (verifications.isEmpty()) { + fail("Verification list is empty."); + } + + fail("Verification list does not contain verification by key" + signingFingerprint + " on cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" + + Arrays.toString(verifications.toArray(new Verification[0]))); + } } diff --git a/external-sop/src/test/java/sop/external/TestKeys.java b/external-sop/src/test/java/sop/external/TestData.java similarity index 93% rename from external-sop/src/test/java/sop/external/TestKeys.java rename to external-sop/src/test/java/sop/external/TestData.java index b7f73c3..640c70f 100644 --- a/external-sop/src/test/java/sop/external/TestKeys.java +++ b/external-sop/src/test/java/sop/external/TestData.java @@ -4,7 +4,14 @@ package sop.external; -public class TestKeys { +import sop.util.UTCUtil; + +import java.util.Date; + +public class TestData { + + + public static final String PLAINTEXT = "Hello, World!\n"; // 'Alice' key from draft-bre-openpgp-samples-00 public static final String ALICE_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -40,6 +47,27 @@ public class TestKeys { "Pnn+We1aTBhaGa86AQ==\n" + "=3GfK\n" + "-----END PGP PRIVATE KEY BLOCK-----\n"; + public static final String ALICE_PRIMARY_FINGERPRINT = "EB85BB5FA33A75E15E944E63F231550C4F47E38E"; + public static final String ALICE_SIGNING_FINGERPRINT = "EB85BB5FA33A75E15E944E63F231550C4F47E38E"; + + public static final String ALICE_INLINE_SIGNED_MESSAGE = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "owGbwMvMwCX2yTCUx9/9cR/jaZEkBhDwSM3JyddRCM8vyklR5OooZWEQ42JQZ2VK\n" + + "PjjpPacATLmYIsvr1t3xi61KH8ZN8UuGCTMwpPcw/E9jS+vcvPu2gmp4jcRbcSNP\n" + + "FYmW8hmLJdUVrdt1V8w6GM/IMEvN0tP339sNGX4swq8T5p62q3jUfLjpstmcI6Ie\n" + + "sfcfswMA\n" + + "=RDAo\n" + + "-----END PGP MESSAGE-----"; + public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + // signature over PLAINTEXT + public static final String ALICE_DETACHED_SIGNED_MESSAGE = "-----BEGIN PGP SIGNATURE-----\n" + + "\n" + + "iHUEABYKACcFAmPBjZUJEPIxVQxPR+OOFiEE64W7X6M6deFelE5j8jFVDE9H444A\n" + + "ADI/AQC6Bux6WpGYf7HO+QPV/D5iIrqZt9xPLgfUVoNJBmMZZwD+Ib+tn5pSyWUw\n" + + "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + + "=bxPN\n" + + "-----END PGP SIGNATURE-----"; + public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); // 'Bob' key from draft-bre-openpgp-samples-00 public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -169,6 +197,8 @@ public class TestKeys { "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" + "=FAzO\n" + "-----END PGP PRIVATE KEY BLOCK-----\n"; + public static final String BOB_PRIMARY_FINGERPRINT = "D1A66E1A23B182C9980F788CFBFCC82A015E7330"; + public static final String BOB_SIGNING_FINGERPRINT = "D1A66E1A23B182C9980F788CFBFCC82A015E7330"; // 'Carol' key from draft-bre-openpgp-samples-00 public static final String CAROL_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -304,6 +334,8 @@ public class TestKeys { "1FkOSekLi8WNMdUx3XMyvP8nJ65P2Q==\n" + "=Xj8h\n" + "-----END PGP PRIVATE KEY BLOCK-----\n"; + public static final String CAROL_PRIMARY_FINGERPRINT = "71FFDA004409E5DDB0C3E8F19BA789DC76D6849A"; + public static final String CAROL_SIGNING_FINGERPRINT = "71FFDA004409E5DDB0C3E8F19BA789DC76D6849A"; public static final String PASSWORD_PROTECTED_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + "Comment: FC63 688A 5E69 8C29 40AF 7029 7C62 2B00 D459 2657\n" + @@ -375,4 +407,6 @@ public class TestKeys { "=xlgc\n" + "-----END PGP PUBLIC KEY BLOCK-----\n"; public static final String PASSWORD = "sw0rdf1sh"; + public static final String PASSWORD_PROTECTED_PRIMARY_FINGERPRINT = "FC63688A5E698C2940AF70297C622B00D4592657"; + public static final String PASSWORD_PROTECTED_SIGNING_FINGERPRINT = "D8F1CBC2613350D1A766D35F68862FB90F07165B"; } From e73c7e5f91bcd04f262ad0acd1ac5f1074131cbf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 21 Jan 2023 21:17:57 +0100 Subject: [PATCH 102/444] Even more tests --- ...ternalDetachedSignVerifyRoundTripTest.java | 12 ++++++++++ .../ExternalEncryptDecryptRoundTripTest.java | 23 +++++++++++++------ .../sop/external/ExternalExtractCertTest.java | 3 +++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index bdab4b5..cf04bd5 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -228,4 +228,16 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP .isEmpty()); } + @Test + public void verifyMissingCertCausesMissingArg() { + ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses picocli which throws + // UNSUPPORTED_OPTION for missing arg + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + assertThrows(SOPGPException.MissingArg.class, () -> + getSop().verify() + .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) + .data(message)); + } + } diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 41bb861..3011a03 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -30,7 +30,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void encryptDecryptRoundTripPasswordTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withPassword("sw0rdf1sh") .plaintext(message) @@ -47,7 +47,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void encryptDecryptRoundTripAliceTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) @@ -67,7 +67,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void encryptDecryptRoundTripBobTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) @@ -86,7 +86,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptDecryptRoundTripCarolTest() throws IOException { ignoreIf("sqop", Is.geq, "0.0.0"); // sqop reports cert not encryption capable - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) @@ -105,7 +105,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest public void encryptNoArmorThenArmorThenDecryptRoundTrip() throws IOException { ignoreIf("sqop", Is.leq, "0.26.1"); // Invalid data type - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() @@ -127,7 +127,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void encryptSignDecryptVerifyRoundTripAliceTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) @@ -152,7 +152,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void encryptSignAsTextDecryptVerifyRoundTripAliceTest() throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = getSop().encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) @@ -278,4 +278,13 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } }); } + + @Test + public void missingArgsTest() throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + assertThrows(SOPGPException.MissingArg.class, () -> getSop().encrypt() + .plaintext(message) + .getBytes()); + } } diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 93357a5..eca144b 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -35,6 +35,7 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { @Test public void extractAliceCertFromAliceKeyTest() throws IOException { + ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB byte[] armoredCert = getSop().extractCert() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); @@ -43,6 +44,7 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { @Test public void extractBobsCertFromBobsKeyTest() throws IOException { + ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB byte[] armoredCert = getSop().extractCert() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); @@ -51,6 +53,7 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { @Test public void extractCarolsCertFromCarolsKeyTest() throws IOException { + ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB byte[] armoredCert = getSop().extractCert() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); From 8cacf7dd57c9dd2abe009356439ebb9cd6e81b52 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 15:07:17 +0100 Subject: [PATCH 103/444] Code cleanup --- .../main/java/sop/external/ExternalSOP.java | 21 ++++++++++++++----- .../sop/external/operation/ArmorExternal.java | 2 +- .../external/operation/DearmorExternal.java | 2 +- .../external/operation/DecryptExternal.java | 6 +++--- .../operation/DetachedSignExternal.java | 2 +- .../operation/DetachedVerifyExternal.java | 4 ++-- .../external/operation/EncryptExternal.java | 6 +++--- .../operation/ExtractCertExternal.java | 2 +- .../operation/GenerateKeyExternal.java | 2 +- .../operation/InlineSignExternal.java | 4 ++-- .../operation/InlineVerifyExternal.java | 2 +- ...ternalDetachedSignVerifyRoundTripTest.java | 1 + .../ExternalEncryptDecryptRoundTripTest.java | 1 + 13 files changed, 34 insertions(+), 21 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 432a16d..add37c5 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -179,14 +179,17 @@ public class ExternalSOP implements SOP { * @throws IOException in case of an IO error */ private static void mapExitCodeOrException(@Nonnull Process process) throws InterruptedException, IOException { + // wait for process termination int exitCode = process.waitFor(); if (exitCode == 0) { + // we're good, bye return; } + // Read error message InputStream errIn = process.getErrorStream(); - String errorMessage = readFully(errIn); + String errorMessage = readString(errIn); switch (exitCode) { case SOPGPException.NoSignature.EXIT_CODE: @@ -285,7 +288,7 @@ public class ExternalSOP implements SOP { * @return string * @throws IOException in case of an IO error */ - public static String readFully(@Nonnull InputStream inputStream) throws IOException { + public static String readString(@Nonnull InputStream inputStream) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; int r; @@ -297,13 +300,16 @@ public class ExternalSOP implements SOP { /** * Execute the given command on the given {@link Runtime} with the given list of environment variables. + * This command does not transform any input data, and instead is purely a producer. * * @param runtime runtime * @param commandList command * @param envList environment variables * @return ready to read the result from */ - public static Ready ready(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList) { + public static Ready executeProducingOperation(@Nonnull Runtime runtime, + @Nonnull List commandList, + @Nonnull List envList) { String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); @@ -322,6 +328,7 @@ public class ExternalSOP implements SOP { outputStream.flush(); outputStream.close(); + ExternalSOP.finish(process); } }; @@ -333,6 +340,7 @@ public class ExternalSOP implements SOP { /** * Execute the given command on the given runtime using the given environment variables. * The given input stream provides input for the process. + * This command is a transformation, meaning it is given input data and transforms it into output data. * * @param runtime runtime * @param commandList command @@ -340,7 +348,7 @@ public class ExternalSOP implements SOP { * @param standardIn stream of input data for the process * @return ready to read the result from */ - public static Ready ready(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList, @Nonnull InputStream standardIn) { + public static Ready executeTransformingOperation(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList, @Nonnull InputStream standardIn) { String[] command = commandList.toArray(new String[0]); String[] env = envList.toArray(new String[0]); try { @@ -362,7 +370,10 @@ public class ExternalSOP implements SOP { processOut.flush(); processOut.close(); } catch (IOException e) { - // ignore + // Perhaps the stream is already closed, in which case we ignore the exception. + if (!"Stream closed".equals(e.getMessage())) { + throw e; + } } while ((r = processIn.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java index bf9ddcd..cb53573 100644 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -38,6 +38,6 @@ public class ArmorExternal implements Armor { @Override public Ready data(InputStream data) throws SOPGPException.BadData, IOException { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java index 2406131..7fa1fdc 100644 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -31,6 +31,6 @@ public class DearmorExternal implements Dearmor { @Override public Ready data(InputStream data) throws SOPGPException.BadData, IOException { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index 84da064..0b91d5b 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -66,7 +66,7 @@ public class DecryptExternal implements Decrypt { throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "VERIFY_WITH_" + verifyWithCounter++; commandList.add("--verify-with=@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + envList.add(envVar + "=" + ExternalSOP.readString(cert)); return this; } @@ -93,7 +93,7 @@ public class DecryptExternal implements Decrypt { throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(key)); + envList.add(envVar + "=" + ExternalSOP.readString(key)); return this; } @@ -151,7 +151,7 @@ public class DecryptExternal implements Decrypt { ExternalSOP.finish(process); FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); - String line = ExternalSOP.readFully(sessionKeyOutIn); + String line = ExternalSOP.readString(sessionKeyOutIn); SessionKey sessionKey = SessionKey.fromString(line.trim()); sessionKeyOutIn.close(); sessionKeyOut.delete(); diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 1f9aa81..3e369f1 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -52,7 +52,7 @@ public class DetachedSignExternal implements DetachedSign { public DetachedSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(key)); + envList.add(envVar + "=" + ExternalSOP.readString(key)); return this; } diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java index ed658c7..05318d4 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -68,12 +68,12 @@ public class DetachedVerifyExternal implements DetachedVerify { @Override public List data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { commandList.add("@ENV:SIGNATURE"); - envList.add("SIGNATURE=" + ExternalSOP.readFully(signatures)); + envList.add("SIGNATURE=" + ExternalSOP.readString(signatures)); for (InputStream cert : certs) { String envVar = "CERT_" + certCounter++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + envList.add(envVar + "=" + ExternalSOP.readString(cert)); } String[] command = commandList.toArray(new String[0]); diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index f45cd12..ca8ddf1 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -53,7 +53,7 @@ public class EncryptExternal implements Encrypt { IOException { String envVar = "SIGN_WITH_" + SIGN_WITH_COUNTER++; commandList.add("--sign-with=@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(key)); + envList.add(envVar + "=" + ExternalSOP.readString(key)); return this; } @@ -81,13 +81,13 @@ public class EncryptExternal implements Encrypt { IOException { String envVar = "CERT_" + CERT_COUNTER++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + envList.add(envVar + "=" + ExternalSOP.readString(cert)); return this; } @Override public Ready plaintext(InputStream plaintext) throws IOException, SOPGPException.KeyIsProtected { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, plaintext); + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, plaintext); } } diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index 549b1ad..5fdcdc1 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -36,6 +36,6 @@ public class ExtractCertExternal implements ExtractCert { @Override public Ready key(InputStream keyInputStream) throws SOPGPException.BadData { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, keyInputStream); + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keyInputStream); } } diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index d125fc0..95e86b8 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -54,6 +54,6 @@ public class GenerateKeyExternal implements GenerateKey { @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList); + return ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java index 17e5b12..d78dd7b 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -43,7 +43,7 @@ public class InlineSignExternal implements InlineSign { public InlineSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(key)); + envList.add(envVar + "=" + ExternalSOP.readString(key)); return this; } @@ -63,6 +63,6 @@ public class InlineSignExternal implements InlineSign { @Override public Ready data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - return ExternalSOP.ready(Runtime.getRuntime(), commandList, envList, data); + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java index 5fbdde6..8010367 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -57,7 +57,7 @@ public class InlineVerifyExternal implements InlineVerify { public InlineVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { String envVar = "CERT_" + certCounter++; commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readFully(cert)); + envList.add(envVar + "=" + ExternalSOP.readString(cert)); return this; } diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index cf04bd5..aaebe39 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -230,6 +230,7 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @Test public void verifyMissingCertCausesMissingArg() { + ignoreIf("sqop", Is.leq, "0.27.3"); ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses picocli which throws // UNSUPPORTED_OPTION for missing arg byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 3011a03..2d6077e 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -281,6 +281,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @Test public void missingArgsTest() throws IOException { + ignoreIf("sqop", Is.leq, "0.27.3"); byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> getSop().encrypt() From 0c8f6baf982504f939632e552ca46a311ac62512 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 15:37:27 +0100 Subject: [PATCH 104/444] Only swallow 'Stream closed' IOExceptions --- .../main/java/sop/external/operation/DetachedSignExternal.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 3e369f1..7c579d4 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -100,6 +100,9 @@ public class DetachedSignExternal implements DetachedSign { processOut.close(); } catch (IOException e) { // Ignore Stream closed + if (!"Stream closed".equals(e.getMessage())) { + throw e; + } } while ((r = processIn.read(buf)) > 0) { From 0b96a5314f9a4272b36ae2efe777b0df569594dd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 16:47:44 +0100 Subject: [PATCH 105/444] Restructure external-sop tests into flexible test suite --- external-sop/build.gradle | 4 + .../main/resources/sop/external/.gitignore | 3 +- .../resources/sop/external/backend.properties | 7 - .../sop/external/config.json.example | 17 ++ .../sop/external/AbstractExternalSOPTest.java | 204 +++++------------- .../ExternalArmorDearmorRoundTripTest.java | 97 +++++---- .../ExternalDecryptWithSessionKeyTest.java | 20 +- ...ternalDetachedSignVerifyRoundTripTest.java | 115 +++++----- .../ExternalEncryptDecryptRoundTripTest.java | 118 +++++----- .../sop/external/ExternalExtractCertTest.java | 66 +++--- .../sop/external/ExternalGenerateKeyTest.java | 58 +++-- ...alInlineSignDetachVerifyRoundTripTest.java | 34 ++- .../ExternalInlineSignVerifyTest.java | 102 ++++----- .../sop/external/ExternalVersionTest.java | 34 +-- .../external/UnsupportedSubcommandTest.java | 58 ----- 15 files changed, 398 insertions(+), 539 deletions(-) delete mode 100644 external-sop/src/main/resources/sop/external/backend.properties create mode 100644 external-sop/src/main/resources/sop/external/config.json.example delete mode 100644 external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 87b8e04..0f5055b 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -14,7 +14,9 @@ repositories { dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + testImplementation "com.google.code.gson:gson:2.10.1" api project(":sop-java") @@ -30,4 +32,6 @@ dependencies { test { useJUnitPlatform() + // since we test external backends, we ignore test failures in this module + ignoreFailures = true } \ No newline at end of file diff --git a/external-sop/src/main/resources/sop/external/.gitignore b/external-sop/src/main/resources/sop/external/.gitignore index b1790be..098eead 100644 --- a/external-sop/src/main/resources/sop/external/.gitignore +++ b/external-sop/src/main/resources/sop/external/.gitignore @@ -2,5 +2,4 @@ # # SPDX-License-Identifier: CC0-1.0 -backend.local.properties -backend.env \ No newline at end of file +config.json \ No newline at end of file diff --git a/external-sop/src/main/resources/sop/external/backend.properties b/external-sop/src/main/resources/sop/external/backend.properties deleted file mode 100644 index 0cf721f..0000000 --- a/external-sop/src/main/resources/sop/external/backend.properties +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Paul Schaub -# -# SPDX-License-Identifier: CC0-1.0 - -## Do not change this file. To overwrite the SOP backend used during testing, -## simply create a file 'backend.local.properties' in this directory and override sop.backend in there. -sop.backend=/path/to/backend \ No newline at end of file diff --git a/external-sop/src/main/resources/sop/external/config.json.example b/external-sop/src/main/resources/sop/external/config.json.example new file mode 100644 index 0000000..a70980b --- /dev/null +++ b/external-sop/src/main/resources/sop/external/config.json.example @@ -0,0 +1,17 @@ +{ + "backends": [ + { + "name": "Example-SOP", + "sop": "/usr/bin/example-sop" + }, + { + "name": "Awesome-SOP", + "sop": "/usr/local/bin/awesome-sop", + "env": [ + { + "key": "myEnvironmentVariable", "value": "FooBar" + } + ] + } + ] +} \ No newline at end of file diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index 2781feb..3661d2a 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -4,174 +4,78 @@ package sop.external; -import org.apache.maven.artifact.versioning.ComparableVersion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.google.gson.Gson; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.provider.Arguments; import sop.SOP; import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Assumptions.assumeTrue; public abstract class AbstractExternalSOPTest { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExternalSOPTest.class); + private static final List backends = new ArrayList<>(); - private final SOP sop; + static { + TestSuite suite = readConfiguration(); + assumeTrue(suite != null); + assumeFalse(suite.backends.isEmpty()); - public AbstractExternalSOPTest() { - String backend = readSopBackendFromProperties(); - assumeTrue(backend != null); - Properties environment = readBackendEnvironment(); - sop = new ExternalSOP(backend, environment); - } - - /** - * Return the SOP backend. - * - * @return SOP backend - */ - public SOP getSop() { - return sop; - } - - /** - * Return
true
iff the specified SOP backend binary is available and accessible. - * - * @return true if external SOP backend is usable - */ - public static boolean isExternalSopInstalled() { - String binary = readSopBackendFromProperties(); - if (binary == null) { - return false; - } - return new File(binary).exists(); - } - - /** - * Relational enum. - */ - public enum Is { - /** - * Less than. - */ - le("<"), - /** - * Less or equal than. - */ - leq("<="), - /** - * Equal. - */ - eq("=="), - /** - * Not equal. - */ - neq("!="), - /** - * Greater or equal than. - */ - geq(">="), - /** - * Greater than. - */ - ge(">"), - ; - - private final String display; - - Is(String display) { - this.display = display; - } - - public String toDisplay() { - return display; - } - } - - /** - * Ignore a test if the tested binary version matches a version criterion. - * Example: - * If the installed version of example-sop is 0.1.3,
ignoreIf("example-sop", Is.le, "0.1.4")
will - * make the test be ignored. - *
ignoreIf("example-sop", Is.eq, "0.1.3")
will skip the test as well. - *
ignoreIf("another-sop", Is.gt, "0.0.0")
will not skip the test, since the binary name does not match. - * - * @param name name of the binary - * @param is relation of the version - * @param version the reference version - */ - public void ignoreIf(String name, Is is, String version) { - String actualName = getSop().version().getName(); - String actualVersion = getSop().version().getVersion(); - - if (!name.matches(actualName)) { - // Name mismatch, do not ignore - return; - } - - ComparableVersion reference = new ComparableVersion(version); - ComparableVersion actual = new ComparableVersion(actualVersion); - - int res = actual.compareTo(reference); - String msg = "Skip since installed " + name + " " + actual + " " + is.toDisplay() + " " + reference; - switch (is) { - case le: - assumeFalse(res < 0, msg); - break; - case leq: - assumeFalse(res <= 0, msg); - case eq: - assumeFalse(res == 0, msg); - break; - case neq: - assumeFalse(res != 0, msg); - break; - case geq: - assumeFalse(res >= 0, msg); - break; - case ge: - assumeFalse(res > 0, msg); - break; - } - } - - static String readSopBackendFromProperties() { - Properties properties = new Properties(); - try { - InputStream resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.local.properties"); - if (resourceIn == null) { - LOGGER.info("Could not find backend.local.properties file. Try backend.properties instead."); - resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.properties"); - } - if (resourceIn == null) { - throw new FileNotFoundException("Could not find backend.properties file."); + for (TestSubject subject : suite.backends) { + if (!new File(subject.sop).exists()) { + continue; } - properties.load(resourceIn); - return properties.getProperty("sop.backend"); - } catch (IOException e) { + Properties env = new Properties(); + if (subject.env != null) { + for (Var var : subject.env) { + env.put(var.key, var.value); + } + } + + SOP sop = new ExternalSOP(subject.sop, env); + backends.add(Arguments.of(Named.of(subject.name, sop))); + } + } + + public static Stream provideBackends() { + return backends.stream(); + } + + public static TestSuite readConfiguration() { + Gson gson = new Gson(); + InputStream inputStream = AbstractExternalSOPTest.class.getResourceAsStream("config.json"); + if (inputStream == null) { return null; } + + InputStreamReader reader = new InputStreamReader(inputStream); + TestSuite suite = gson.fromJson(reader, TestSuite.class); + return suite; } - protected static Properties readBackendEnvironment() { - Properties properties = new Properties(); - try { - InputStream resourceIn = AbstractExternalSOPTest.class.getResourceAsStream("backend.env"); - if (resourceIn == null) { - LOGGER.info("Could not read backend.env file."); - } else { - properties.load(resourceIn); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return properties; + // JSON DTOs + + public static class TestSuite { + List backends; + } + + public static class TestSubject { + String name; + String sop; + List env; + } + + public static class Var { + String key; + String value; } } diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index c4cd4ae..d6e4402 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -4,8 +4,9 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -16,7 +17,6 @@ import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertAsciiArmorEquals; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; @@ -28,17 +28,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); - @Test - public void dearmorArmorAliceKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorAliceKey(SOP sop) throws IOException { byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(aliceKey) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -46,17 +47,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(aliceKey, armored); } - @Test - public void dearmorArmorAliceCert() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorAliceCert(SOP sop) throws IOException { byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(aliceCert) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -64,17 +66,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(aliceCert, armored); } - @Test - public void dearmorArmorBobKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorBobKey(SOP sop) throws IOException { byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(bobKey) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -82,17 +85,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(bobKey, armored); } - @Test - public void dearmorArmorBobCert() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorBobCert(SOP sop) throws IOException { byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(bobCert) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -100,17 +104,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(bobCert, armored); } - @Test - public void dearmorArmorCarolKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorCarolKey(SOP sop) throws IOException { byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(carolKey) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -118,17 +123,18 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(carolKey, armored); } - @Test - public void dearmorArmorCarolCert() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorCarolCert(SOP sop) throws IOException { byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(carolCert) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -136,9 +142,9 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(carolCert, armored); } - @Test - public void dearmorArmorMessage() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // falsely reports Invalid Data Type + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorMessage(SOP sop) throws IOException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAMZy9Iqb1IxszjI3v+TsfK//0lnJ9PKHDqVAB5ohp+RMw\n" + @@ -147,13 +153,13 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { "CePQFpprprnGEzpE3flQLUc=\n" + "=ZiFR\n" + "-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(message) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_MESSAGE_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -161,8 +167,9 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(message, armored); } - @Test - public void dearmorArmorSignature() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void dearmorArmorSignature(SOP sop) throws IOException { byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + "\n" + "wr0EABYKAG8FgmPBdRAJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + @@ -172,13 +179,13 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { "=GHvQ\n" + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] dearmored = getSop().dearmor() + byte[] dearmored = sop.dearmor() .data(signature) .getBytes(); assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_SIGNATURE_BYTES)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(dearmored) .getBytes(); @@ -186,23 +193,23 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { assertAsciiArmorEquals(signature, armored); } - @Test - public void testDearmoringTwiceIsIdempotent() throws IOException { - ignoreIf("sqop", Is.eq, "0.27.2"); // IO error because: EOF - - byte[] dearmored = getSop().dearmor() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void testDearmoringTwiceIsIdempotent(SOP sop) throws IOException { + byte[] dearmored = sop.dearmor() .data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - byte[] dearmoredAgain = getSop().dearmor() + byte[] dearmoredAgain = sop.dearmor() .data(dearmored) .getBytes(); assertArrayEquals(dearmored, dearmoredAgain); } - @Test - public void testArmoringTwiceIsIdempotent() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void testArmoringTwiceIsIdempotent(SOP sop) throws IOException { byte[] armored = ("-----BEGIN PGP SIGNATURE-----\n" + "\n" + "wr0EABYKAG8FgmPBdRAJEPIxVQxPR+OORxQAAAAAAB4AIHNhbHRAbm90YXRpb25z\n" + @@ -212,7 +219,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { "=GHvQ\n" + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] armoredAgain = getSop().armor() + byte[] armoredAgain = sop.armor() .data(armored) .getBytes(); diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java index 0c1f7c7..7a5e4f6 100644 --- a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java @@ -4,10 +4,11 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; +import sop.SOP; import sop.SessionKey; import java.io.IOException; @@ -16,7 +17,6 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { private static final String CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + @@ -29,9 +29,10 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { "-----END PGP MESSAGE-----\n"; private static final String SESSION_KEY = "9:ED682800F5FEA829A82E8B7DDF8CE9CF4BF9BB45024B017764462EE53101C36A"; - @Test - public void testDecryptAndExtractSessionKey() throws IOException { - ByteArrayAndResult bytesAndResult = getSop().decrypt() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void testDecryptAndExtractSessionKey(SOP sop) throws IOException { + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult(); @@ -41,9 +42,10 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { assertArrayEquals("Hello, World!\n".getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); } - @Test - public void testDecryptWithSessionKey() throws IOException { - byte[] decrypted = getSop().decrypt() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void testDecryptWithSessionKey(SOP sop) throws IOException { + byte[] decrypted = sop.decrypt() .withSessionKey(SessionKey.fromString(SESSION_KEY)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index aaebe39..8e87b00 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -4,8 +4,9 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; import sop.Verification; import sop.enums.SignAs; import sop.exception.SOPGPException; @@ -20,23 +21,23 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertSignedBy; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); - @Test - public void signVerifyWithAliceKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyWithAliceKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -45,18 +46,19 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } - @Test - public void signVerifyTextModeWithAliceKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(SignAs.Text) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -65,12 +67,13 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } - @Test - public void verifyKnownMessageWithAliceCert() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void verifyKnownMessageWithAliceCert(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -79,17 +82,18 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE); } - @Test - public void signVerifyWithBobKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyWithBobKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -98,17 +102,18 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } - @Test - public void signVerifyWithCarolKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyWithCarolKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -117,11 +122,12 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } - @Test - public void signVerifyWithEncryptedKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyWithEncryptedKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword(TestData.PASSWORD) .data(message) @@ -130,7 +136,7 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE_BYTES); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -138,22 +144,23 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertFalse(verificationList.isEmpty()); } - @Test - public void signArmorVerifyWithBobKey() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signArmorVerifyWithBobKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().detachedSign() + byte[] signature = sop.detachedSign() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .toByteArrayAndResult() .getBytes(); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(signature) .getBytes(); - List verificationList = getSop().detachedVerify() + List verificationList = sop.detachedVerify() .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(message); @@ -162,32 +169,30 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } - @Test - public void verifyNotAfterThrowsNoSignature() { - ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void verifyNotAfterThrowsNoSignature(SOP sop) { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() + assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notAfter(beforeSignature) .signatures(signature) .data(message)); } - @Test - public void verifyNotBeforeThrowsNoSignature() { - ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void verifyNotBeforeThrowsNoSignature(SOP sop) { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after sig - assertThrows(SOPGPException.NoSignature.class, () -> getSop().detachedVerify() + assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notBefore(afterSignature) .signatures(signature) @@ -195,24 +200,24 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP } - @Test - public void signVerifyWithEncryptedKeyWithoutPassphraseFails() { - ignoreIf("sqop", Is.leq, "0.27.2"); // does not return exit code 67 for encrypted keys without passphrase - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { assertThrows(SOPGPException.KeyIsProtected.class, () -> - getSop().detachedSign() + sop.detachedSign() .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() .getBytes()); } - @Test - public void signWithProtectedKeyAndMultiplePassphrasesTest() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void signWithProtectedKeyAndMultiplePassphrasesTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = getSop().sign() + byte[] signature = sop.sign() .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword("wrong") .withKeyPassword(TestData.PASSWORD) // correct @@ -221,22 +226,20 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP .toByteArrayAndResult() .getBytes(); - assertFalse(getSop().verify() + assertFalse(sop.verify() .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message) .isEmpty()); } - @Test - public void verifyMissingCertCausesMissingArg() { - ignoreIf("sqop", Is.leq, "0.27.3"); - ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses picocli which throws - // UNSUPPORTED_OPTION for missing arg + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void verifyMissingCertCausesMissingArg(SOP sop) { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> - getSop().verify() + sop.verify() .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) .data(message)); } diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 2d6077e..2b505a4 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -4,10 +4,11 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; +import sop.SOP; import sop.Verification; import sop.enums.EncryptAs; import sop.exception.SOPGPException; @@ -25,18 +26,18 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest { - @Test - public void encryptDecryptRoundTripPasswordTest() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withPassword("sw0rdf1sh") .plaintext(message) .getBytes(); - byte[] plaintext = getSop().decrypt() + byte[] plaintext = sop.decrypt() .withPassword("sw0rdf1sh") .ciphertext(ciphertext) .toByteArrayAndResult() @@ -45,15 +46,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, plaintext); } - @Test - public void encryptDecryptRoundTripAliceTest() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -65,15 +67,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertNotNull(result.getSessionKey().get()); } - @Test - public void encryptDecryptRoundTripBobTest() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); - byte[] plaintext = getSop().decrypt() + byte[] plaintext = sop.decrypt() .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() @@ -82,17 +85,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, plaintext); } - @Test - public void encryptDecryptRoundTripCarolTest() throws IOException { - ignoreIf("sqop", Is.geq, "0.0.0"); // sqop reports cert not encryption capable - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); - byte[] plaintext = getSop().decrypt() + byte[] plaintext = sop.decrypt() .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() @@ -101,22 +103,21 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, plaintext); } - @Test - public void encryptNoArmorThenArmorThenDecryptRoundTrip() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // Invalid data type - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) .getBytes(); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(ciphertext) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(armored) .toByteArrayAndResult(); @@ -125,16 +126,17 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, plaintext); } - @Test - public void encryptSignDecryptVerifyRoundTripAliceTest() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) @@ -150,17 +152,18 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); } - @Test - public void encryptSignAsTextDecryptVerifyRoundTripAliceTest() throws IOException { + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.Text) .plaintext(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) @@ -176,29 +179,28 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); } - @Test - public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException { byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); - byte[] key = getSop().generateKey() + byte[] key = sop.generateKey() .withKeyPassword(keyPassword) .userId("Alice ") .generate() .getBytes(); - byte[] cert = getSop().extractCert() + byte[] cert = sop.extractCert() .key(key) .getBytes(); byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = getSop().encrypt() + byte[] ciphertext = sop.encrypt() .withCert(cert) .signWith(key) .withKeyPassword(keyPassword) .plaintext(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(key) .withKeyPassword(keyPassword) .verifyWithCert(cert) @@ -209,11 +211,9 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertArrayEquals(message, bytesAndResult.getBytes()); } - @Test - public void decryptVerifyNotAfterTest() { - ignoreIf("PGPainless-SOP", Is.le, "1.4.2"); // does not recognize --verify-not-after - ignoreIf("sqop", Is.leq, "0.27.2"); // does not throw NoSignature - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void decryptVerifyNotAfterTest(SOP sop) { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + @@ -231,7 +231,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date assertThrows(SOPGPException.NoSignature.class, () -> { - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotAfter(beforeSignature) @@ -244,11 +244,9 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest }); } - @Test - public void decryptVerifyNotBeforeTest() { - ignoreIf("PGPainless-SOP", Is.le, "1.4.2"); // does not recognize --verify-not-after - ignoreIf("sqop", Is.leq, "0.27.2"); // does not throw NoSignature - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void decryptVerifyNotBeforeTest(SOP sop) { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + @@ -266,7 +264,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date assertThrows(SOPGPException.NoSignature.class, () -> { - ByteArrayAndResult bytesAndResult = getSop().decrypt() + ByteArrayAndResult bytesAndResult = sop.decrypt() .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotBefore(afterSignature) @@ -279,12 +277,12 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest }); } - @Test - public void missingArgsTest() throws IOException { - ignoreIf("sqop", Is.leq, "0.27.3"); + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void missingArgsTest(SOP sop) { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - assertThrows(SOPGPException.MissingArg.class, () -> getSop().encrypt() + assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() .plaintext(message) .getBytes()); } diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index eca144b..39a661b 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -4,8 +4,9 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; import java.io.IOException; import java.io.InputStream; @@ -16,58 +17,59 @@ import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertAsciiArmorEquals; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalExtractCertTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); - @Test - public void extractArmoredCertFromArmoredKeyTest() throws IOException { - InputStream keyIn = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException { + InputStream keyIn = sop.generateKey() .userId("Alice ") .generate() .getInputStream(); - byte[] cert = getSop().extractCert().key(keyIn).getBytes(); + byte[] cert = sop.extractCert().key(keyIn).getBytes(); assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); } - @Test - public void extractAliceCertFromAliceKeyTest() throws IOException { - ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB - byte[] armoredCert = getSop().extractCert() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractAliceCertFromAliceKeyTest(SOP sop) throws IOException { + byte[] armoredCert = sop.extractCert() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } - @Test - public void extractBobsCertFromBobsKeyTest() throws IOException { - ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB - byte[] armoredCert = getSop().extractCert() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractBobsCertFromBobsKeyTest(SOP sop) throws IOException { + byte[] armoredCert = sop.extractCert() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } - @Test - public void extractCarolsCertFromCarolsKeyTest() throws IOException { - ignoreIf("PGPainless-SOP", Is.geq, "0.0.0"); // PGPainless uses old CTB - byte[] armoredCert = getSop().extractCert() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractCarolsCertFromCarolsKeyTest(SOP sop) throws IOException { + byte[] armoredCert = sop.extractCert() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } - @Test - public void extractUnarmoredCertFromArmoredKeyTest() throws IOException { - InputStream keyIn = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractUnarmoredCertFromArmoredKeyTest(SOP sop) throws IOException { + InputStream keyIn = sop.generateKey() .userId("Alice ") .generate() .getInputStream(); - byte[] cert = getSop().extractCert() + byte[] cert = sop.extractCert() .noArmor() .key(keyIn) .getBytes(); @@ -75,30 +77,32 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); } - @Test - public void extractArmoredCertFromUnarmoredKeyTest() throws IOException { - InputStream keyIn = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractArmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { + InputStream keyIn = sop.generateKey() .userId("Alice ") .noArmor() .generate() .getInputStream(); - byte[] cert = getSop().extractCert() + byte[] cert = sop.extractCert() .key(keyIn) .getBytes(); assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); } - @Test - public void extractUnarmoredCertFromUnarmoredKeyTest() throws IOException { - InputStream keyIn = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extractUnarmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { + InputStream keyIn = sop.generateKey() .noArmor() .userId("Alice ") .generate() .getInputStream(); - byte[] cert = getSop().extractCert() + byte[] cert = sop.extractCert() .noArmor() .key(keyIn) .getBytes(); diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index 0e23992..7839c60 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -4,8 +4,9 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; import java.io.IOException; import java.nio.charset.Charset; @@ -14,16 +15,16 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.assertArrayStartsWith; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { private static final Charset UTF8 = StandardCharsets.UTF_8; private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(UTF8); - @Test - public void generateKeyTest() throws IOException { - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyTest(SOP sop) throws IOException { + byte[] key = sop.generateKey() .userId("Alice ") .generate() .getBytes(); @@ -31,9 +32,10 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } - @Test - public void generateKeyNoArmor() throws IOException { - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyNoArmor(SOP sop) throws IOException { + byte[] key = sop.generateKey() .userId("Alice ") .noArmor() .generate() @@ -42,9 +44,10 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); } - @Test - public void generateKeyWithMultipleUserIdsTest() throws IOException { - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyWithMultipleUserIdsTest(SOP sop) throws IOException { + byte[] key = sop.generateKey() .userId("Alice ") .userId("Bob ") .generate() @@ -53,23 +56,20 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } - @Test - public void generateKeyWithoutUserIdTest() throws IOException { - ignoreIf("pgpainless-cli", Is.le, "1.3.15"); - - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyWithoutUserIdTest(SOP sop) throws IOException { + byte[] key = sop.generateKey() .generate() .getBytes(); assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } - @Test - public void generateKeyWithPasswordTest() throws IOException { - ignoreIf("sqop", Is.le, "0.27.0"); - ignoreIf("pgpainless-cli", Is.le, "1.3.0"); - - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyWithPasswordTest(SOP sop) throws IOException { + byte[] key = sop.generateKey() .userId("Alice ") .withKeyPassword("sw0rdf1sh") .generate() @@ -78,14 +78,10 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); } - @Test - public void generateKeyWithMultipleUserIdsAndPassword() throws IOException { - ignoreIf("sqop", Is.le, "0.27.0"); - ignoreIf("PGPainless-SOP", Is.le, "1.3.15"); - ignoreIf("PGPainless-SOP", Is.eq, "1.4.0"); - ignoreIf("PGPainless-SOP", Is.eq, "1.4.1"); - - byte[] key = getSop().generateKey() + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void generateKeyWithMultipleUserIdsAndPassword(SOP sop) throws IOException { + byte[] key = sop.generateKey() .userId("Alice ") .userId("Bob ") .withKeyPassword("sw0rdf1sh") diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java index 41bc67c..a1e84b1 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -4,9 +4,10 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; +import sop.SOP; import sop.Signatures; import sop.Verification; @@ -19,23 +20,21 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { private static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); - @Test - public void inlineSignThenDetachThenDetachedVerifyTest() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().inlineDetach() + ByteArrayAndResult bytesAndResult = sop.inlineDetach() .message(inlineSigned) .toByteArrayAndResult(); @@ -45,7 +44,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna byte[] signatures = bytesAndResult.getResult() .getBytes(); - List verifications = getSop().detachedVerify() + List verifications = sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(plaintext); @@ -53,18 +52,17 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna assertFalse(verifications.isEmpty()); } - @Test - public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest(SOP sop) throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - ByteArrayAndResult bytesAndResult = getSop().inlineDetach() + ByteArrayAndResult bytesAndResult = sop.inlineDetach() .noArmor() .message(inlineSigned) .toByteArrayAndResult(); @@ -76,12 +74,12 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna .getBytes(); assertFalse(arrayStartsWith(signatures, BEGIN_PGP_SIGNATURE)); - byte[] armored = getSop().armor() + byte[] armored = sop.armor() .data(signatures) .getBytes(); assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); - List verifications = getSop().detachedVerify() + List verifications = sop.detachedVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(plaintext); diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java index f0a632e..e6ee329 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -4,9 +4,10 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; +import sop.SOP; import sop.Verification; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; @@ -21,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static sop.external.JUtils.assertSignedBy; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; @@ -29,20 +29,19 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n"; private static final byte[] BEGIN_PGP_SIGNED_MESSAGE_BYTES = BEGIN_PGP_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - @Test - public void inlineSignVerifyAlice() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignVerifyAlice(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -52,13 +51,12 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } - @Test - public void inlineSignVerifyAliceNoArmor() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) @@ -66,7 +64,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES)); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -76,13 +74,12 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } - @Test - public void clearsignVerifyAlice() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void clearsignVerifyAlice(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] clearsigned = getSop().inlineSign() + byte[] clearsigned = sop.inlineSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.clearsigned) .data(message) @@ -90,7 +87,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE_BYTES); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(clearsigned) .toByteArrayAndResult(); @@ -100,15 +97,13 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } - @Test - public void inlineVerifyCompareSignatureDate() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineVerifyCompareSignatureDate(SOP sop) throws IOException { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult(); @@ -116,52 +111,47 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate); } - @Test - public void assertNotBeforeThrowsNoSignature() { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void assertNotBeforeThrowsNoSignature(SOP sop) { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() + assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notBefore(afterSignature) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } - @Test - public void assertNotAfterThrowsNoSignature() { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - ignoreIf("sqop", Is.leq, "0.27.2"); // returns 1 instead of 3 (NO_SIGNATURE) - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void assertNotAfterThrowsNoSignature(SOP sop) { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> getSop().inlineVerify() + assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notAfter(beforeSignature) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } - @Test - public void inlineSignVerifyBob() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignVerifyBob(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -171,20 +161,19 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } - @Test - public void inlineSignVerifyCarol() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignVerifyCarol(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -194,20 +183,19 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } - @Test - public void inlineSignVerifyProtectedKey() throws IOException { - ignoreIf("sqop", Is.leq, "0.26.1"); // inline-sign not supported - + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void inlineSignVerifyProtectedKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = getSop().inlineSign() + byte[] inlineSigned = sop.inlineSign() .withKeyPassword(TestData.PASSWORD) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.binary) .data(message) .getBytes(); - ByteArrayAndResult> bytesAndResult = getSop().inlineVerify() + ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/external-sop/src/test/java/sop/external/ExternalVersionTest.java index bde6cb5..0415d03 100644 --- a/external-sop/src/test/java/sop/external/ExternalVersionTest.java +++ b/external-sop/src/test/java/sop/external/ExternalVersionTest.java @@ -4,38 +4,42 @@ package sop.external; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") public class ExternalVersionTest extends AbstractExternalSOPTest { - @Test - public void versionNameTest() { - String name = getSop().version().getName(); + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void versionNameTest(SOP sop) { + String name = sop.version().getName(); assertNotNull(name); assertFalse(name.isEmpty()); } - @Test - public void versionVersionTest() { - String version = getSop().version().getVersion(); + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void versionVersionTest(SOP sop) { + String version = sop.version().getVersion(); assertTrue(version.matches("\\d+(\\.\\d+)*\\S*")); } - @Test - public void backendVersionTest() { - String backend = getSop().version().getBackendVersion(); + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void backendVersionTest(SOP sop) { + String backend = sop.version().getBackendVersion(); assertFalse(backend.isEmpty()); } - @Test - public void extendedVersionTest() { - String extended = getSop().version().getExtendedVersion(); + @ParameterizedTest + @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + public void extendedVersionTest(SOP sop) { + String extended = sop.version().getExtendedVersion(); assertFalse(extended.isEmpty()); } diff --git a/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java b/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java deleted file mode 100644 index 55e13e2..0000000 --- a/external-sop/src/test/java/sop/external/UnsupportedSubcommandTest.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; -import sop.exception.SOPGPException; - -import java.io.IOException; -import java.util.Properties; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -@EnabledIf("sop.external.AbstractExternalSOPTest#isExternalSopInstalled") -public class UnsupportedSubcommandTest extends AbstractExternalSOPTest { - - private final UnsupportedSubcommandExternal unsupportedSubcommand; - - public UnsupportedSubcommandTest() { - String backend = readSopBackendFromProperties(); - assumeTrue(backend != null); - Properties environment = readBackendEnvironment(); - unsupportedSubcommand = new UnsupportedSubcommandExternal(backend, environment); - } - - @Test - public void testUnsupportedSubcommand() { - // "sop unsupported" returns error code UNSUPPORTED_SUBCOMMAND - assertThrows(SOPGPException.UnsupportedSubcommand.class, - unsupportedSubcommand::executeUnsupportedSubcommand); - } - - private static class UnsupportedSubcommandExternal { - - private final Runtime runtime = Runtime.getRuntime(); - private final String binary; - private final Properties environment; - - UnsupportedSubcommandExternal(String binaryName, Properties environment) { - this.binary = binaryName; - this.environment = environment; - } - - public void executeUnsupportedSubcommand() { - String[] command = new String[] {binary, "unsupported"}; // ~$ sop unsupported - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - ExternalSOP.finish(process); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -} From 3789b60f0bf6879626488ebe693492a9227764f6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 16:53:50 +0100 Subject: [PATCH 106/444] Properly ignore tests if no backends are configured --- external-sop/build.gradle | 4 +-- .../sop/external/AbstractExternalSOPTest.java | 36 +++++++++---------- .../ExternalArmorDearmorRoundTripTest.java | 2 ++ .../ExternalDecryptWithSessionKeyTest.java | 2 ++ ...ternalDetachedSignVerifyRoundTripTest.java | 2 ++ .../ExternalEncryptDecryptRoundTripTest.java | 2 ++ .../sop/external/ExternalExtractCertTest.java | 2 ++ .../sop/external/ExternalGenerateKeyTest.java | 2 ++ ...alInlineSignDetachVerifyRoundTripTest.java | 2 ++ .../ExternalInlineSignVerifyTest.java | 2 ++ .../sop/external/ExternalVersionTest.java | 2 ++ 11 files changed, 37 insertions(+), 21 deletions(-) diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 0f5055b..37bd8f3 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -16,6 +16,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + // Read json config file testImplementation "com.google.code.gson:gson:2.10.1" api project(":sop-java") @@ -23,9 +24,6 @@ dependencies { api "org.slf4j:slf4j-api:$slf4jVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" - // Compare version strings - implementation 'org.apache.maven:maven-artifact:3.6.3' - // @Nonnull, @Nullable... implementation "com.google.code.findbugs:jsr305:$jsrVersion" } diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java index 3661d2a..824990f 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java @@ -17,32 +17,28 @@ import java.util.List; import java.util.Properties; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assumptions.assumeFalse; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - public abstract class AbstractExternalSOPTest { private static final List backends = new ArrayList<>(); static { TestSuite suite = readConfiguration(); - assumeTrue(suite != null); - assumeFalse(suite.backends.isEmpty()); - - for (TestSubject subject : suite.backends) { - if (!new File(subject.sop).exists()) { - continue; - } - - Properties env = new Properties(); - if (subject.env != null) { - for (Var var : subject.env) { - env.put(var.key, var.value); + if (suite != null && !suite.backends.isEmpty()) { + for (TestSubject subject : suite.backends) { + if (!new File(subject.sop).exists()) { + continue; } - } - SOP sop = new ExternalSOP(subject.sop, env); - backends.add(Arguments.of(Named.of(subject.name, sop))); + Properties env = new Properties(); + if (subject.env != null) { + for (Var var : subject.env) { + env.put(var.key, var.value); + } + } + + SOP sop = new ExternalSOP(subject.sop, env); + backends.add(Arguments.of(Named.of(subject.name, sop))); + } } } @@ -62,6 +58,10 @@ public abstract class AbstractExternalSOPTest { return suite; } + public static boolean hasBackends() { + return !backends.isEmpty(); + } + // JSON DTOs public static class TestSuite { diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index d6e4402..dec48f4 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; @@ -17,6 +18,7 @@ import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertAsciiArmorEquals; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java index 7a5e4f6..8a7ff56 100644 --- a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; @@ -17,6 +18,7 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { private static final String CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index 8e87b00..ddee99a 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; @@ -21,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertSignedBy; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 2b505a4..4692f21 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; @@ -26,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest { @ParameterizedTest diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 39a661b..19866ee 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; @@ -17,6 +18,7 @@ import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; import static sop.external.JUtils.assertAsciiArmorEquals; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalExtractCertTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index 7839c60..2525014 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; @@ -15,6 +16,7 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.assertArrayStartsWith; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { private static final Charset UTF8 = StandardCharsets.UTF_8; diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java index a1e84b1..bc77534 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; @@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static sop.external.JUtils.arrayStartsWith; import static sop.external.JUtils.assertArrayStartsWith; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { private static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java index e6ee329..22916d6 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; @@ -22,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static sop.external.JUtils.assertSignedBy; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/external-sop/src/test/java/sop/external/ExternalVersionTest.java index 0415d03..4b1f87c 100644 --- a/external-sop/src/test/java/sop/external/ExternalVersionTest.java +++ b/external-sop/src/test/java/sop/external/ExternalVersionTest.java @@ -4,6 +4,7 @@ package sop.external; +import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; @@ -12,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalVersionTest extends AbstractExternalSOPTest { @ParameterizedTest From c1ae5314a09c3f9aeeb7c2f8d3ac71a138a4548c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 17:07:17 +0100 Subject: [PATCH 107/444] CI: Test external-sop against sqop --- .reuse/dep5 | 4 ++++ .woodpecker/.build.yml | 7 ++++++- .../src/main/resources/sop/external/config.json.ci | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 external-sop/src/main/resources/sop/external/config.json.ci diff --git a/.reuse/dep5 b/.reuse/dep5 index 9dee06a..f5be92f 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -17,4 +17,8 @@ License: Apache-2.0 # Woodpecker build files Files: .woodpecker/* Copyright: 2022 the original author or authors. +License: Apache-2.0 + +Files: external-sop/src/main/resources/sop/external/* +Copyright: 2023 the original author or authors License: Apache-2.0 \ No newline at end of file diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index f504b44..e235f7f 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -1,8 +1,13 @@ pipeline: run: - image: gradle:7.5-jdk8 + image: gradle:7.5-jdk8-jammy commands: + # Install Sequoia-SOP + - apt update && apt install --yes sqop + # Checkout code - git checkout $CI_COMMIT_BRANCH + # Prepare CI + - cp external-sop/src/main/resources/sop/external/config.json.ci external-sop/src/main/resources/sop/external/config.json # Code works - gradle test # Code is clean diff --git a/external-sop/src/main/resources/sop/external/config.json.ci b/external-sop/src/main/resources/sop/external/config.json.ci new file mode 100644 index 0000000..c7fd159 --- /dev/null +++ b/external-sop/src/main/resources/sop/external/config.json.ci @@ -0,0 +1,8 @@ +{ + "backends": [ + { + "name": "Sequoia-SOP", + "sop": "/usr/bin/sqop" + } + ] +} \ No newline at end of file From 6c3e148bcdfbf73de2ad4ad16147985b6ea5fc3b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 17:35:48 +0100 Subject: [PATCH 108/444] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e8a82..2f9f2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 4.1.1-SNAPSHOT +- Restructure test suite to allow simultaneous testing of multiple backends +- Fix IOException in `sop sign` due to premature stream closing + ## 4.1.0 - Add module `external-sop` - This module implements the `sop-java` interfaces and allows the use of an external SOP binary From 88e3ba009556ecba3e764e30a6d450e2082223af Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 22 Jan 2023 17:37:47 +0100 Subject: [PATCH 109/444] Add section about license compliance and testing to external-sop/README --- external-sop/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/external-sop/README.md b/external-sop/README.md index 187683c..e54e548 100644 --- a/external-sop/README.md +++ b/external-sop/README.md @@ -23,6 +23,8 @@ SOP sop = new ExternalSOP("/usr/bin/example-sop"); This SOP object can now be used as usual (see [here](../sop-java/README.md)). +Keep in mind the license of the external SOP binary when integrating one with your project! + Some SOP binaries might require additional configuration, e.g. a Java based SOP might need to know which JAVA_HOME to use. For this purpose, additional environment variables can be passed in using a `Properties` object: @@ -50,3 +52,8 @@ ExternalSOP.TempDirProvider provider = new ExternalSOP.TempDirProvider() { }; SOP sop = new ExternalSOP("/usr/bin/example-sop", provider); ``` + +## Testing +The `external-sop` module comes with a growing test suite, which tests SOP binaries against the expectations of the SOP specification. +To configure one or multiple backends for use with the test suite, just provide a custom `config.json` file in `src/main/resources/sop/external`. +An example configuration file with the required file format is available as `config.json.example`. From fd426b533c4b8939543a8ec9810cc9f7cbf6e716 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 27 Jan 2023 00:35:38 +0100 Subject: [PATCH 110/444] Restructure test artifacts into sop-java testFixtures --- external-sop/build.gradle | 1 + .../ExternalArmorDearmorRoundTripTest.java | 87 +++++++------ .../ExternalDecryptWithSessionKeyTest.java | 8 +- ...ternalDetachedSignVerifyRoundTripTest.java | 118 ++++++++++-------- .../ExternalEncryptDecryptRoundTripTest.java | 71 ++++++----- .../sop/external/ExternalExtractCertTest.java | 23 ++-- .../sop/external/ExternalGenerateKeyTest.java | 29 +++-- ...alInlineSignDetachVerifyRoundTripTest.java | 20 +-- .../ExternalInlineSignVerifyTest.java | 64 +++++----- sop-java/build.gradle | 2 + .../java/sop/testing}/JUtils.java | 43 ++++++- .../java/sop/testing}/TestData.java | 14 ++- .../java/sop/testing/package-info.java | 8 ++ 13 files changed, 303 insertions(+), 185 deletions(-) rename {external-sop/src/test/java/sop/external => sop-java/src/testFixtures/java/sop/testing}/JUtils.java (82%) rename {external-sop/src/test/java/sop/external => sop-java/src/testFixtures/java/sop/testing}/TestData.java (96%) create mode 100644 sop-java/src/testFixtures/java/sop/testing/package-info.java diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 37bd8f3..bd45c71 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -20,6 +20,7 @@ dependencies { testImplementation "com.google.code.gson:gson:2.10.1" api project(":sop-java") + testImplementation(testFixtures(project(":sop-java"))) api "org.slf4j:slf4j-api:$slf4jVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java index dec48f4..f1d29e6 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java @@ -8,28 +8,29 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import sop.testing.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.external.JUtils.arrayStartsWith; -import static sop.external.JUtils.assertArrayStartsWith; -import static sop.external.JUtils.assertAsciiArmorEquals; +import static sop.testing.JUtils.arrayStartsWith; +import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; +import static sop.testing.JUtils.assertArrayStartsWith; +import static sop.testing.JUtils.assertAsciiArmorEquals; +import static sop.testing.TestData.BEGIN_PGP_MESSAGE; +import static sop.testing.TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK; +import static sop.testing.TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK; +import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; +import static sop.testing.TestData.END_PGP_MESSAGE; +import static sop.testing.TestData.END_PGP_PRIVATE_KEY_BLOCK; +import static sop.testing.TestData.END_PGP_PUBLIC_KEY_BLOCK; +import static sop.testing.TestData.END_PGP_SIGNATURE; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { - private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; - private static final byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); - private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; - private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); - private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; - private static final byte[] BEGIN_PGP_MESSAGE_BYTES = BEGIN_PGP_MESSAGE.getBytes(StandardCharsets.UTF_8); - private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; - private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void dearmorArmorAliceKey(SOP sop) throws IOException { @@ -39,14 +40,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(aliceKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(aliceKey, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + + // assertAsciiArmorEquals(aliceKey, armored); } @ParameterizedTest @@ -58,14 +61,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(aliceCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(aliceCert, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + + // assertAsciiArmorEquals(aliceCert, armored); } @ParameterizedTest @@ -77,14 +82,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(bobKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(bobKey, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + + // assertAsciiArmorEquals(bobKey, armored); } @ParameterizedTest @@ -96,14 +103,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(bobCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(bobCert, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + + // assertAsciiArmorEquals(bobCert, armored); } @ParameterizedTest @@ -115,14 +124,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(carolKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(carolKey, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + + // assertAsciiArmorEquals(carolKey, armored); } @ParameterizedTest @@ -134,14 +145,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(carolCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); - assertAsciiArmorEquals(carolCert, armored); + assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + + // assertAsciiArmorEquals(carolCert, armored); } @ParameterizedTest @@ -159,14 +172,16 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(message) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_MESSAGE_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_MESSAGE)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_MESSAGE_BYTES); - assertAsciiArmorEquals(message, armored); + assertArrayStartsWith(armored, BEGIN_PGP_MESSAGE); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_MESSAGE); + + // assertAsciiArmorEquals(message, armored); } @ParameterizedTest @@ -185,13 +200,15 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(signature) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_SIGNATURE_BYTES)); + assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_SIGNATURE)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE_BYTES); + assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); + assertArrayEndsWithIgnoreNewlines(armored, END_PGP_SIGNATURE); + assertAsciiArmorEquals(signature, armored); } diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java index 8a7ff56..b75ec04 100644 --- a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java @@ -17,6 +17,8 @@ import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static sop.testing.TestData.ALICE_KEY; +import static sop.testing.TestData.PLAINTEXT; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { @@ -35,13 +37,13 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void testDecryptAndExtractSessionKey(SOP sop) throws IOException { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult(); assertEquals(SESSION_KEY, bytesAndResult.getResult().getSessionKey().get().toString()); - assertArrayEquals("Hello, World!\n".getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); + assertArrayEquals(PLAINTEXT.getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); } @ParameterizedTest @@ -53,6 +55,6 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { .toByteArrayAndResult() .getBytes(); - assertArrayEquals("Hello, World!\n".getBytes(StandardCharsets.UTF_8), decrypted); + assertArrayEquals(PLAINTEXT.getBytes(StandardCharsets.UTF_8), decrypted); } } diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java index ddee99a..e08d7a9 100644 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java @@ -19,127 +19,143 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static sop.external.JUtils.assertArrayStartsWith; -import static sop.external.JUtils.assertSignedBy; +import static sop.testing.JUtils.assertArrayStartsWith; +import static sop.testing.JUtils.assertSignedBy; +import static sop.testing.TestData.ALICE_CERT; +import static sop.testing.TestData.ALICE_DETACHED_SIGNED_MESSAGE; +import static sop.testing.TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; +import static sop.testing.TestData.ALICE_KEY; +import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; +import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; +import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; +import static sop.testing.TestData.BOB_CERT; +import static sop.testing.TestData.BOB_KEY; +import static sop.testing.TestData.BOB_PRIMARY_FINGERPRINT; +import static sop.testing.TestData.BOB_SIGNING_FINGERPRINT; +import static sop.testing.TestData.CAROL_CERT; +import static sop.testing.TestData.CAROL_KEY; +import static sop.testing.TestData.CAROL_PRIMARY_FINGERPRINT; +import static sop.testing.TestData.CAROL_SIGNING_FINGERPRINT; +import static sop.testing.TestData.PASSWORD; +import static sop.testing.TestData.PASSWORD_PROTECTED_CERT; +import static sop.testing.TestData.PASSWORD_PROTECTED_KEY; +import static sop.testing.TestData.PLAINTEXT; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { - private static final String BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n"; - private static final byte[] BEGIN_PGP_SIGNATURE_BYTES = BEGIN_PGP_SIGNATURE.getBytes(StandardCharsets.UTF_8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signVerifyWithAliceKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(SignAs.Text) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void verifyKnownMessageWithAliceCert(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); List verificationList = sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT, ALICE_DETACHED_SIGNED_MESSAGE_DATE); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signVerifyWithBobKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .key(BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = sop.detachedVerify() - .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, BOB_SIGNING_FINGERPRINT, BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signVerifyWithCarolKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .key(CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); List verificationList = sop.detachedVerify() - .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, CAROL_SIGNING_FINGERPRINT, CAROL_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signVerifyWithEncryptedKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .withKeyPassword(TestData.PASSWORD) + .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword(PASSWORD) .data(message) .toByteArrayAndResult() .getBytes(); - assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE_BYTES); + assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE); List verificationList = sop.detachedVerify() - .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -149,10 +165,10 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signArmorVerifyWithBobKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.detachedSign() - .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .key(BOB_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .toByteArrayAndResult() @@ -163,24 +179,23 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP .getBytes(); List verificationList = sop.detachedVerify() - .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(message); assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, BOB_SIGNING_FINGERPRINT, BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void verifyNotAfterThrowsNoSignature(SOP sop) { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; - Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date beforeSignature = new Date(ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notAfter(beforeSignature) .signatures(signature) .data(message)); @@ -189,13 +204,12 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void verifyNotBeforeThrowsNoSignature(SOP sop) { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - Date signatureDate = TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; - Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after sig + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date afterSignature = new Date(ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notBefore(afterSignature) .signatures(signature) .data(message)); @@ -207,8 +221,8 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.detachedSign() - .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) + .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .data(PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() .getBytes()); } @@ -217,19 +231,19 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void signWithProtectedKeyAndMultiplePassphrasesTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = sop.sign() - .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword("wrong") - .withKeyPassword(TestData.PASSWORD) // correct + .withKeyPassword(PASSWORD) // correct .withKeyPassword("wrong2") .data(message) .toByteArrayAndResult() .getBytes(); assertFalse(sop.verify() - .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message) .isEmpty()); @@ -238,11 +252,11 @@ public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOP @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void verifyMissingCertCausesMissingArg(SOP sop) { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> sop.verify() - .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) + .signatures(ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) .data(message)); } diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java index 4692f21..fa5f2a8 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java @@ -25,7 +25,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static sop.testing.JUtils.assertSignedBy; +import static sop.testing.TestData.ALICE_CERT; +import static sop.testing.TestData.ALICE_KEY; +import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; +import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; +import static sop.testing.TestData.BOB_CERT; +import static sop.testing.TestData.BOB_KEY; +import static sop.testing.TestData.CAROL_CERT; +import static sop.testing.TestData.CAROL_KEY; +import static sop.testing.TestData.PLAINTEXT; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest { @@ -33,7 +42,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() .withPassword("sw0rdf1sh") .plaintext(message) @@ -51,14 +60,14 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -72,14 +81,14 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = sop.decrypt() - .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(BOB_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -90,14 +99,14 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = sop.decrypt() - .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -108,9 +117,9 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) .getBytes(); @@ -120,7 +129,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(armored) .toByteArrayAndResult(); @@ -131,16 +140,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -151,23 +160,23 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertNotNull(result.getSessionKey().get()); List verificationList = result.getVerifications(); assertEquals(1, verificationList.size()); - assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.Text) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -178,7 +187,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertNotNull(result.getSessionKey().get()); List verificationList = result.getVerifications(); assertEquals(1, verificationList.size()); - assertTrue(verificationList.get(0).toString().contains("EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E")); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -234,8 +243,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotAfter(beforeSignature) .ciphertext(message) .toByteArrayAndResult(); @@ -267,8 +276,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotBefore(afterSignature) .ciphertext(message) .toByteArrayAndResult(); @@ -282,7 +291,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void missingArgsTest(SOP sop) { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() .plaintext(message) diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java index 19866ee..53d7e6d 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java @@ -8,22 +8,23 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import sop.testing.TestData; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.external.JUtils.arrayStartsWith; -import static sop.external.JUtils.assertArrayStartsWith; -import static sop.external.JUtils.assertAsciiArmorEquals; +import static sop.testing.JUtils.arrayStartsWith; +import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; +import static sop.testing.JUtils.assertArrayStartsWith; +import static sop.testing.JUtils.assertAsciiArmorEquals; +import static sop.testing.TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK; +import static sop.testing.TestData.END_PGP_PUBLIC_KEY_BLOCK; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalExtractCertTest extends AbstractExternalSOPTest { - private static final String BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"; - private static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES = BEGIN_PGP_PUBLIC_KEY_BLOCK.getBytes(StandardCharsets.UTF_8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException { @@ -33,7 +34,8 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .getInputStream(); byte[] cert = sop.extractCert().key(keyIn).getBytes(); - assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(cert, END_PGP_PUBLIC_KEY_BLOCK); } @ParameterizedTest @@ -76,7 +78,7 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK)); } @ParameterizedTest @@ -92,7 +94,8 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES); + assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(cert, END_PGP_PUBLIC_KEY_BLOCK); } @ParameterizedTest @@ -109,6 +112,6 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK_BYTES)); + assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK)); } } diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java index 2525014..606efab 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java @@ -8,21 +8,19 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import sop.testing.JUtils; import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.external.JUtils.assertArrayStartsWith; +import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; +import static sop.testing.JUtils.assertArrayStartsWith; +import static sop.testing.TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK; +import static sop.testing.TestData.END_PGP_PRIVATE_KEY_BLOCK; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { - private static final Charset UTF8 = StandardCharsets.UTF_8; - private static final String BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"; - byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES = BEGIN_PGP_PRIVATE_KEY_BLOCK.getBytes(UTF8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void generateKeyTest(SOP sop) throws IOException { @@ -31,7 +29,8 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest @@ -43,7 +42,7 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES)); + assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK)); } @ParameterizedTest @@ -55,7 +54,8 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest @@ -65,7 +65,8 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest @@ -77,7 +78,8 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest @@ -90,6 +92,7 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK_BYTES); + assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); + assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); } } diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java index bc77534..23ba7fd 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java @@ -18,21 +18,23 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.external.JUtils.arrayStartsWith; -import static sop.external.JUtils.assertArrayStartsWith; +import static sop.testing.JUtils.arrayStartsWith; +import static sop.testing.JUtils.assertArrayStartsWith; +import static sop.testing.TestData.ALICE_CERT; +import static sop.testing.TestData.ALICE_KEY; +import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; +import static sop.testing.TestData.PLAINTEXT; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { - private static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException { - byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -47,7 +49,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna .getBytes(); List verifications = sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(plaintext); @@ -60,7 +62,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -82,7 +84,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); List verifications = sop.detachedVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(plaintext); diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java index 22916d6..5d1012f 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java @@ -12,6 +12,8 @@ import sop.SOP; import sop.Verification; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; +import sop.testing.JUtils; +import sop.testing.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -21,82 +23,84 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static sop.external.JUtils.assertSignedBy; +import static sop.testing.JUtils.assertSignedBy; +import static sop.testing.TestData.ALICE_CERT; +import static sop.testing.TestData.ALICE_KEY; +import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; +import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; +import static sop.testing.TestData.BEGIN_PGP_MESSAGE; +import static sop.testing.TestData.BEGIN_PGP_SIGNED_MESSAGE; +import static sop.testing.TestData.PLAINTEXT; @EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { - private static final String BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n"; - private static final byte[] BEGIN_PGP_MESSAGE_BYTES = BEGIN_PGP_MESSAGE.getBytes(StandardCharsets.UTF_8); - private static final String BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n"; - private static final byte[] BEGIN_PGP_SIGNED_MESSAGE_BYTES = BEGIN_PGP_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignVerifyAlice(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .getBytes(); - assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES)); + assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE)); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void clearsignVerifyAlice(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] clearsigned = sop.inlineSign() - .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.clearsigned) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE_BYTES); + JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(clearsigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -106,11 +110,11 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult(); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate); + assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT, signatureDate); } @ParameterizedTest @@ -122,7 +126,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notBefore(afterSignature) - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @@ -136,7 +140,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notAfter(beforeSignature) - .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @@ -144,14 +148,14 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignVerifyBob(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) @@ -166,14 +170,14 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignVerifyCarol(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE_BYTES); + JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) @@ -188,7 +192,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { @ParameterizedTest @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") public void inlineSignVerifyProtectedKey(SOP sop) throws IOException { - byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .withKeyPassword(TestData.PASSWORD) diff --git a/sop-java/build.gradle b/sop-java/build.gradle index ff0f293..0211189 100644 --- a/sop-java/build.gradle +++ b/sop-java/build.gradle @@ -4,6 +4,7 @@ plugins { id 'java-library' + id 'java-test-fixtures' } group 'org.pgpainless' @@ -15,6 +16,7 @@ repositories { dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" } test { diff --git a/external-sop/src/test/java/sop/external/JUtils.java b/sop-java/src/testFixtures/java/sop/testing/JUtils.java similarity index 82% rename from external-sop/src/test/java/sop/external/JUtils.java rename to sop-java/src/testFixtures/java/sop/testing/JUtils.java index 0b15142..99f5a5f 100644 --- a/external-sop/src/test/java/sop/external/JUtils.java +++ b/sop-java/src/testFixtures/java/sop/testing/JUtils.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testing; import sop.Verification; import sop.util.UTCUtil; @@ -86,6 +86,47 @@ public class JUtils { } } + public static boolean arrayEndsWith(byte[] array, byte[] end) { + return arrayEndsWith(array, end, 0); + } + + public static boolean arrayEndsWith(byte[] array, byte[] end, int offset) { + if (end.length + offset > array.length) { + return false; + } + + for (int i = 0; i < end.length; i++) { + int arrOff = array.length - end.length - offset; + if (end[i] != array[arrOff + i]) { + return false; + } + } + return true; + } + + public static void assertArrayEndsWith(byte[] array, byte[] end) { + assertArrayEndsWith(array, end, 0); + } + + public static void assertArrayEndsWith(byte[] array, byte[] end, int offset) { + if (!arrayEndsWith(array, end, offset)) { + byte[] actual = new byte[Math.min(end.length, array.length - offset)]; + System.arraycopy(array, array.length - actual.length, actual, 0, actual.length); + fail("Array does not end with the expected bytes.\n" + + "Expected: <" + Arrays.toString(end) + ">\n" + + "Actual: <" + Arrays.toString(actual) + ">"); + } + } + + public static void assertArrayEndsWithIgnoreNewlines(byte[] array, byte[] end) { + int offset = 0; + while (offset < array.length && array[array.length - 1 - offset] == (byte) 10) { + offset++; + } + + assertArrayEndsWith(array, end, offset); + } + /** * Assert equality of the given two ascii armored byte arrays, ignoring armor header lines. * diff --git a/external-sop/src/test/java/sop/external/TestData.java b/sop-java/src/testFixtures/java/sop/testing/TestData.java similarity index 96% rename from external-sop/src/test/java/sop/external/TestData.java rename to sop-java/src/testFixtures/java/sop/testing/TestData.java index 640c70f..f573eef 100644 --- a/external-sop/src/test/java/sop/external/TestData.java +++ b/sop-java/src/testFixtures/java/sop/testing/TestData.java @@ -2,10 +2,11 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testing; import sop.util.UTCUtil; +import java.nio.charset.StandardCharsets; import java.util.Date; public class TestData { @@ -409,4 +410,15 @@ public class TestData { public static final String PASSWORD = "sw0rdf1sh"; public static final String PASSWORD_PROTECTED_PRIMARY_FINGERPRINT = "FC63688A5E698C2940AF70297C622B00D4592657"; public static final String PASSWORD_PROTECTED_SIGNING_FINGERPRINT = "D8F1CBC2613350D1A766D35F68862FB90F07165B"; + + + public static final byte[] BEGIN_PGP_PRIVATE_KEY_BLOCK = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n".getBytes(StandardCharsets.UTF_8); + public static final byte[] END_PGP_PRIVATE_KEY_BLOCK = "-----END PGP PRIVATE KEY BLOCK-----".getBytes(StandardCharsets.UTF_8); + public static final byte[] BEGIN_PGP_PUBLIC_KEY_BLOCK = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n".getBytes(StandardCharsets.UTF_8); + public static final byte[] END_PGP_PUBLIC_KEY_BLOCK = "-----END PGP PUBLIC KEY BLOCK-----".getBytes(StandardCharsets.UTF_8); + public static final byte[] BEGIN_PGP_MESSAGE = "-----BEGIN PGP MESSAGE-----\n".getBytes(StandardCharsets.UTF_8); + public static final byte[] END_PGP_MESSAGE = "-----END PGP MESSAGE-----".getBytes(StandardCharsets.UTF_8); + public static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); + public static final byte[] END_PGP_SIGNATURE = "-----END PGP SIGNATURE-----".getBytes(StandardCharsets.UTF_8); + public static final byte[] BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n".getBytes(StandardCharsets.UTF_8); } diff --git a/sop-java/src/testFixtures/java/sop/testing/package-info.java b/sop-java/src/testFixtures/java/sop/testing/package-info.java new file mode 100644 index 0000000..1d4527a --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testing/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * Stateless OpenPGP Interface for Java. + */ +package sop.testing; From 0709bce35cc5043b6d2b1ddf81583a1d3c0d6f05 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:20:27 +0100 Subject: [PATCH 111/444] Allow for extension of test suite from 3rd party SOP implementations --- .woodpecker/.build.yml | 2 +- external-sop/build.gradle | 16 +- .../sop/{ => testsuite}/external/.gitignore | 0 .../{ => testsuite}/external/config.json.ci | 0 .../external/config.json.example | 0 ...ternalDetachedSignVerifyRoundTripTest.java | 263 ------------------ .../external/ExternalSOPInstanceFactory.java} | 32 +-- .../operation/ExternalArmorDearmorTest.java | 11 + .../ExternalDecryptWithSessionKeyTest.java | 11 + ...xternalDetachedSignDetachedVerifyTest.java | 10 + .../operation/ExternalEncryptDecryptTest.java | 11 + .../operation/ExternalExtractCertTest.java | 11 + .../operation/ExternalGenerateKeyTest.java | 11 + ...ineSignInlineDetachDetachedVerifyTest.java | 12 + .../ExternalInlineSignInlineVerifyTest.java | 11 + .../operation/ExternalVersionTest.java | 11 + sop-java/build.gradle | 4 +- .../java/sop/testing/package-info.java | 8 - .../sop/{testing => testsuite}/JUtils.java | 2 +- .../sop/testsuite/SOPInstanceFactory.java | 30 ++ .../sop/{testing => testsuite}/TestData.java | 2 +- .../testsuite/operation/AbstractSOPTest.java | 51 ++++ .../testsuite/operation/ArmorDearmorTest.java | 101 ++++--- .../operation/DecryptWithSessionKeyTest.java | 27 +- .../DetachedSignDetachedVerifyTest.java | 250 +++++++++++++++++ .../operation/EncryptDecryptTest.java | 106 ++++--- .../testsuite/operation/ExtractCertTest.java | 54 ++-- .../testsuite/operation/GenerateKeyTest.java | 54 ++-- ...ineSignInlineDetachDetachedVerifyTest.java | 39 +-- .../operation/InlineSignInlineVerifyTest.java | 98 ++++--- .../sop/testsuite/operation/VersionTest.java | 24 +- .../sop/testsuite/operation/package-info.java | 8 + .../java/sop/testsuite/package-info.java | 8 + 33 files changed, 731 insertions(+), 547 deletions(-) rename external-sop/src/main/resources/sop/{ => testsuite}/external/.gitignore (100%) rename external-sop/src/main/resources/sop/{ => testsuite}/external/config.json.ci (100%) rename external-sop/src/main/resources/sop/{ => testsuite}/external/config.json.example (100%) delete mode 100644 external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java rename external-sop/src/test/java/sop/{external/AbstractExternalSOPTest.java => testsuite/external/ExternalSOPInstanceFactory.java} (65%) create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java delete mode 100644 sop-java/src/testFixtures/java/sop/testing/package-info.java rename sop-java/src/testFixtures/java/sop/{testing => testsuite}/JUtils.java (99%) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java rename sop-java/src/testFixtures/java/sop/{testing => testsuite}/TestData.java (99%) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java rename external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java (62%) rename external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java (70%) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java rename external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java (72%) rename external-sop/src/test/java/sop/external/ExternalExtractCertTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java (59%) rename external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java (53%) rename external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java (68%) rename external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java (62%) rename external-sop/src/test/java/sop/external/ExternalVersionTest.java => sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java (65%) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/package-info.java diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index e235f7f..d72d19e 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -7,7 +7,7 @@ pipeline: # Checkout code - git checkout $CI_COMMIT_BRANCH # Prepare CI - - cp external-sop/src/main/resources/sop/external/config.json.ci external-sop/src/main/resources/sop/external/config.json + - cp external-sop/src/main/resources/sop/testsuite/external/config.json.ci external-sop/src/main/resources/sop/testsuite/external/config.json # Code works - gradle test # Code is clean diff --git a/external-sop/build.gradle b/external-sop/build.gradle index bd45c71..acd55f6 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -16,21 +16,27 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - // Read json config file - testImplementation "com.google.code.gson:gson:2.10.1" api project(":sop-java") - testImplementation(testFixtures(project(":sop-java"))) - api "org.slf4j:slf4j-api:$slf4jVersion" testImplementation "ch.qos.logback:logback-classic:$logbackVersion" // @Nonnull, @Nullable... implementation "com.google.code.findbugs:jsr305:$jsrVersion" + + // The ExternalTestSubjectFactory reads json config file to find configured SOP binaries... + testImplementation "com.google.code.gson:gson:2.10.1" + // ...and extends TestSubjectFactory + testImplementation(testFixtures(project(":sop-java"))) } test { + // Inject configured external SOP instances using our custom TestSubjectFactory + environment("test.implementation", "sop.testsuite.external.ExternalSOPInstanceFactory") + useJUnitPlatform() + // since we test external backends, we ignore test failures in this module ignoreFailures = true -} \ No newline at end of file +} + diff --git a/external-sop/src/main/resources/sop/external/.gitignore b/external-sop/src/main/resources/sop/testsuite/external/.gitignore similarity index 100% rename from external-sop/src/main/resources/sop/external/.gitignore rename to external-sop/src/main/resources/sop/testsuite/external/.gitignore diff --git a/external-sop/src/main/resources/sop/external/config.json.ci b/external-sop/src/main/resources/sop/testsuite/external/config.json.ci similarity index 100% rename from external-sop/src/main/resources/sop/external/config.json.ci rename to external-sop/src/main/resources/sop/testsuite/external/config.json.ci diff --git a/external-sop/src/main/resources/sop/external/config.json.example b/external-sop/src/main/resources/sop/testsuite/external/config.json.example similarity index 100% rename from external-sop/src/main/resources/sop/external/config.json.example rename to external-sop/src/main/resources/sop/testsuite/external/config.json.example diff --git a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java b/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java deleted file mode 100644 index e08d7a9..0000000 --- a/external-sop/src/test/java/sop/external/ExternalDetachedSignVerifyRoundTripTest.java +++ /dev/null @@ -1,263 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external; - -import org.junit.jupiter.api.condition.EnabledIf; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import sop.SOP; -import sop.Verification; -import sop.enums.SignAs; -import sop.exception.SOPGPException; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static sop.testing.JUtils.assertArrayStartsWith; -import static sop.testing.JUtils.assertSignedBy; -import static sop.testing.TestData.ALICE_CERT; -import static sop.testing.TestData.ALICE_DETACHED_SIGNED_MESSAGE; -import static sop.testing.TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE; -import static sop.testing.TestData.ALICE_KEY; -import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; -import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; -import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; -import static sop.testing.TestData.BOB_CERT; -import static sop.testing.TestData.BOB_KEY; -import static sop.testing.TestData.BOB_PRIMARY_FINGERPRINT; -import static sop.testing.TestData.BOB_SIGNING_FINGERPRINT; -import static sop.testing.TestData.CAROL_CERT; -import static sop.testing.TestData.CAROL_KEY; -import static sop.testing.TestData.CAROL_PRIMARY_FINGERPRINT; -import static sop.testing.TestData.CAROL_SIGNING_FINGERPRINT; -import static sop.testing.TestData.PASSWORD; -import static sop.testing.TestData.PASSWORD_PROTECTED_CERT; -import static sop.testing.TestData.PASSWORD_PROTECTED_KEY; -import static sop.testing.TestData.PLAINTEXT; - -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalDetachedSignVerifyRoundTripTest extends AbstractExternalSOPTest { - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyWithAliceKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .data(message) - .toByteArrayAndResult() - .getBytes(); - - List verificationList = sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(SignAs.Text) - .data(message) - .toByteArrayAndResult() - .getBytes(); - - List verificationList = sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void verifyKnownMessageWithAliceCert(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - - List verificationList = sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT, ALICE_DETACHED_SIGNED_MESSAGE_DATE); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyWithBobKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(BOB_KEY.getBytes(StandardCharsets.UTF_8)) - .data(message) - .toByteArrayAndResult() - .getBytes(); - - List verificationList = sop.detachedVerify() - .cert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, BOB_SIGNING_FINGERPRINT, BOB_PRIMARY_FINGERPRINT); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyWithCarolKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(CAROL_KEY.getBytes(StandardCharsets.UTF_8)) - .data(message) - .toByteArrayAndResult() - .getBytes(); - - List verificationList = sop.detachedVerify() - .cert(CAROL_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, CAROL_SIGNING_FINGERPRINT, CAROL_PRIMARY_FINGERPRINT); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyWithEncryptedKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .withKeyPassword(PASSWORD) - .data(message) - .toByteArrayAndResult() - .getBytes(); - - assertArrayStartsWith(signature, BEGIN_PGP_SIGNATURE); - - List verificationList = sop.detachedVerify() - .cert(PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message); - - assertFalse(verificationList.isEmpty()); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signArmorVerifyWithBobKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.detachedSign() - .key(BOB_KEY.getBytes(StandardCharsets.UTF_8)) - .noArmor() - .data(message) - .toByteArrayAndResult() - .getBytes(); - - byte[] armored = sop.armor() - .data(signature) - .getBytes(); - - List verificationList = sop.detachedVerify() - .cert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(armored) - .data(message); - - assertFalse(verificationList.isEmpty()); - assertSignedBy(verificationList, BOB_SIGNING_FINGERPRINT, BOB_PRIMARY_FINGERPRINT); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void verifyNotAfterThrowsNoSignature(SOP sop) { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - Date beforeSignature = new Date(ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig - - assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .notAfter(beforeSignature) - .signatures(signature) - .data(message)); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void verifyNotBeforeThrowsNoSignature(SOP sop) { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - Date afterSignature = new Date(ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig - - assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .notBefore(afterSignature) - .signatures(signature) - .data(message)); - } - - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { - assertThrows(SOPGPException.KeyIsProtected.class, () -> - sop.detachedSign() - .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .data(PLAINTEXT.getBytes(StandardCharsets.UTF_8)) - .toByteArrayAndResult() - .getBytes()); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void signWithProtectedKeyAndMultiplePassphrasesTest(SOP sop) - throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - byte[] signature = sop.sign() - .key(PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) - .withKeyPassword("wrong") - .withKeyPassword(PASSWORD) // correct - .withKeyPassword("wrong2") - .data(message) - .toByteArrayAndResult() - .getBytes(); - - assertFalse(sop.verify() - .cert(PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) - .signatures(signature) - .data(message) - .isEmpty()); - } - - @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") - public void verifyMissingCertCausesMissingArg(SOP sop) { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); - - assertThrows(SOPGPException.MissingArg.class, () -> - sop.verify() - .signatures(ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) - .data(message)); - } - -} diff --git a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java similarity index 65% rename from external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java rename to external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java index 824990f..aa051ff 100644 --- a/external-sop/src/test/java/sop/external/AbstractExternalSOPTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java @@ -2,26 +2,26 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.external; import com.google.gson.Gson; -import org.junit.jupiter.api.Named; -import org.junit.jupiter.params.provider.Arguments; import sop.SOP; +import sop.external.ExternalSOP; +import sop.testsuite.SOPInstanceFactory; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; -import java.util.stream.Stream; -public abstract class AbstractExternalSOPTest { +public class ExternalSOPInstanceFactory extends SOPInstanceFactory { - private static final List backends = new ArrayList<>(); - - static { + @Override + public Map provideSOPInstances() { + Map backends = new HashMap<>(); TestSuite suite = readConfiguration(); if (suite != null && !suite.backends.isEmpty()) { for (TestSubject subject : suite.backends) { @@ -37,30 +37,24 @@ public abstract class AbstractExternalSOPTest { } SOP sop = new ExternalSOP(subject.sop, env); - backends.add(Arguments.of(Named.of(subject.name, sop))); + backends.put(subject.name, sop); } } + return backends; } - public static Stream provideBackends() { - return backends.stream(); - } public static TestSuite readConfiguration() { Gson gson = new Gson(); - InputStream inputStream = AbstractExternalSOPTest.class.getResourceAsStream("config.json"); + InputStream inputStream = ExternalSOPInstanceFactory.class.getResourceAsStream("config.json"); if (inputStream == null) { return null; } InputStreamReader reader = new InputStreamReader(inputStream); - TestSuite suite = gson.fromJson(reader, TestSuite.class); - return suite; + return gson.fromJson(reader, TestSuite.class); } - public static boolean hasBackends() { - return !backends.isEmpty(); - } // JSON DTOs diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java new file mode 100644 index 0000000..f3ee7be --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.ArmorDearmorTest; + +public class ExternalArmorDearmorTest extends ArmorDearmorTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java new file mode 100644 index 0000000..4f9364d --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.DecryptWithSessionKeyTest; + +public class ExternalDecryptWithSessionKeyTest extends DecryptWithSessionKeyTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java new file mode 100644 index 0000000..b831295 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.DetachedSignDetachedVerifyTest; + +public class ExternalDetachedSignDetachedVerifyTest extends DetachedSignDetachedVerifyTest { +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java new file mode 100644 index 0000000..3584c95 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.EncryptDecryptTest; + +public class ExternalEncryptDecryptTest extends EncryptDecryptTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java new file mode 100644 index 0000000..47cd8e1 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.ExtractCertTest; + +public class ExternalExtractCertTest extends ExtractCertTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java new file mode 100644 index 0000000..6352736 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.GenerateKeyTest; + +public class ExternalGenerateKeyTest extends GenerateKeyTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java new file mode 100644 index 0000000..48633f3 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.InlineSignInlineDetachDetachedVerifyTest; + +public class ExternalInlineSignInlineDetachDetachedVerifyTest + extends InlineSignInlineDetachDetachedVerifyTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java new file mode 100644 index 0000000..705b05f --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.InlineSignInlineVerifyTest; + +public class ExternalInlineSignInlineVerifyTest extends InlineSignInlineVerifyTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java new file mode 100644 index 0000000..4c0e3a3 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.VersionTest; + +public class ExternalVersionTest extends VersionTest { + +} diff --git a/sop-java/build.gradle b/sop-java/build.gradle index 0211189..c958610 100644 --- a/sop-java/build.gradle +++ b/sop-java/build.gradle @@ -15,10 +15,12 @@ repositories { dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" } test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/sop-java/src/testFixtures/java/sop/testing/package-info.java b/sop-java/src/testFixtures/java/sop/testing/package-info.java deleted file mode 100644 index 1d4527a..0000000 --- a/sop-java/src/testFixtures/java/sop/testing/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - */ -package sop.testing; diff --git a/sop-java/src/testFixtures/java/sop/testing/JUtils.java b/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java similarity index 99% rename from sop-java/src/testFixtures/java/sop/testing/JUtils.java rename to sop-java/src/testFixtures/java/sop/testsuite/JUtils.java index 99f5a5f..c30d86b 100644 --- a/sop-java/src/testFixtures/java/sop/testing/JUtils.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.testing; +package sop.testsuite; import sop.Verification; import sop.util.UTCUtil; diff --git a/sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java b/sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java new file mode 100644 index 0000000..106c494 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite; + +import sop.SOP; + +import java.util.Map; + +/** + * Factory class to instantiate SOP implementations for testing. + * Overwrite this class and the {@link #provideSOPInstances()} method to return the SOP instances you want + * to test. + * Then, add the following line to your
build.gradle
files
dependencies
section: + *
{@code
+ *     testImplementation(testFixtures("org.pgpainless:sop-java:"))
+ * }
+ * To inject the factory class into the test suite, add the following line to your modules
test
task: + *
{@code
+ *     environment("test.implementation", "org.example.YourTestSubjectFactory")
+ * }
+ * Next, in your
test
sources, extend all test classes from the
testFixtures
+ *
sop.operation
package. + * Take a look at the
external-sop
module for an example. + */ +public abstract class SOPInstanceFactory { + + public abstract Map provideSOPInstances(); +} diff --git a/sop-java/src/testFixtures/java/sop/testing/TestData.java b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java similarity index 99% rename from sop-java/src/testFixtures/java/sop/testing/TestData.java rename to sop-java/src/testFixtures/java/sop/testsuite/TestData.java index f573eef..e1d35c2 100644 --- a/sop-java/src/testFixtures/java/sop/testing/TestData.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.testing; +package sop.testsuite; import sop.util.UTCUtil; diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java new file mode 100644 index 0000000..fa9cbbf --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.provider.Arguments; +import sop.SOP; +import sop.testsuite.SOPInstanceFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public abstract class AbstractSOPTest { + + private static final List backends = new ArrayList<>(); + + static { + // populate instances list via configured test subject factory + String factoryName = System.getenv("test.implementation"); + if (factoryName != null) { + try { + Class testSubjectFactoryClass = Class.forName(factoryName); + SOPInstanceFactory factory = (SOPInstanceFactory) testSubjectFactoryClass.newInstance(); + Map testSubjects = factory.provideSOPInstances(); + + for (String key : testSubjects.keySet()) { + backends.add(Arguments.of(Named.of(key, testSubjects.get(key)))); + } + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + public static Stream provideBackends() { + return backends.stream(); + } + + public static boolean hasBackends() { + return !backends.isEmpty(); + } + +} diff --git a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java similarity index 62% rename from external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java index f1d29e6..ce3427a 100644 --- a/external-sop/src/test/java/sop/external/ExternalArmorDearmorRoundTripTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java @@ -2,37 +2,32 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; -import sop.testing.TestData; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.testing.JUtils.arrayStartsWith; -import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; -import static sop.testing.JUtils.assertArrayStartsWith; -import static sop.testing.JUtils.assertAsciiArmorEquals; -import static sop.testing.TestData.BEGIN_PGP_MESSAGE; -import static sop.testing.TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK; -import static sop.testing.TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK; -import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; -import static sop.testing.TestData.END_PGP_MESSAGE; -import static sop.testing.TestData.END_PGP_PRIVATE_KEY_BLOCK; -import static sop.testing.TestData.END_PGP_PUBLIC_KEY_BLOCK; -import static sop.testing.TestData.END_PGP_SIGNATURE; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class ArmorDearmorTest { + + static Stream provideInstances() { + return AbstractSOPTest.provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorAliceKey(SOP sop) throws IOException { byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8); @@ -40,20 +35,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(aliceKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PRIVATE_KEY_BLOCK); // assertAsciiArmorEquals(aliceKey, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorAliceCert(SOP sop) throws IOException { byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8); @@ -61,20 +56,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(aliceCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PUBLIC_KEY_BLOCK); // assertAsciiArmorEquals(aliceCert, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorBobKey(SOP sop) throws IOException { byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8); @@ -82,20 +77,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(bobKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PRIVATE_KEY_BLOCK); // assertAsciiArmorEquals(bobKey, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorBobCert(SOP sop) throws IOException { byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8); @@ -103,20 +98,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(bobCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PUBLIC_KEY_BLOCK); // assertAsciiArmorEquals(bobCert, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorCarolKey(SOP sop) throws IOException { byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8); @@ -124,20 +119,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(carolKey) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PRIVATE_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PRIVATE_KEY_BLOCK); // assertAsciiArmorEquals(carolKey, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorCarolCert(SOP sop) throws IOException { byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8); @@ -145,20 +140,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(carolCert) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_PUBLIC_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_PUBLIC_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_PUBLIC_KEY_BLOCK); // assertAsciiArmorEquals(carolCert, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorMessage(SOP sop) throws IOException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + @@ -172,20 +167,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(message) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_MESSAGE)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_MESSAGE)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_MESSAGE); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_MESSAGE); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_MESSAGE); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_MESSAGE); // assertAsciiArmorEquals(message, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void dearmorArmorSignature(SOP sop) throws IOException { byte[] signature = ("-----BEGIN PGP SIGNATURE-----\n" + "\n" + @@ -200,20 +195,20 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(signature) .getBytes(); - assertFalse(arrayStartsWith(dearmored, BEGIN_PGP_SIGNATURE)); + Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_SIGNATURE)); byte[] armored = sop.armor() .data(dearmored) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); - assertArrayEndsWithIgnoreNewlines(armored, END_PGP_SIGNATURE); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_SIGNATURE); + JUtils.assertArrayEndsWithIgnoreNewlines(armored, TestData.END_PGP_SIGNATURE); - assertAsciiArmorEquals(signature, armored); + JUtils.assertAsciiArmorEquals(signature, armored); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void testDearmoringTwiceIsIdempotent(SOP sop) throws IOException { byte[] dearmored = sop.dearmor() .data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) @@ -227,7 +222,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void testArmoringTwiceIsIdempotent(SOP sop) throws IOException { byte[] armored = ("-----BEGIN PGP SIGNATURE-----\n" + "\n" + @@ -242,7 +237,7 @@ public class ExternalArmorDearmorRoundTripTest extends AbstractExternalSOPTest { .data(armored) .getBytes(); - assertAsciiArmorEquals(armored, armoredAgain); + JUtils.assertAsciiArmorEquals(armored, armoredAgain); } } diff --git a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java similarity index 70% rename from external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java index b75ec04..2321770 100644 --- a/external-sop/src/test/java/sop/external/ExternalDecryptWithSessionKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java @@ -2,26 +2,27 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.SOP; import sop.SessionKey; +import sop.testsuite.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; -import static sop.testing.TestData.ALICE_KEY; -import static sop.testing.TestData.PLAINTEXT; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class DecryptWithSessionKeyTest extends AbstractSOPTest { private static final String CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + "\n" + @@ -33,21 +34,25 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { "-----END PGP MESSAGE-----\n"; private static final String SESSION_KEY = "9:ED682800F5FEA829A82E8B7DDF8CE9CF4BF9BB45024B017764462EE53101C36A"; + static Stream provideInstances() { + return provideBackends(); + } + @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void testDecryptAndExtractSessionKey(SOP sop) throws IOException { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult(); assertEquals(SESSION_KEY, bytesAndResult.getResult().getSessionKey().get().toString()); - assertArrayEquals(PLAINTEXT.getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); + Assertions.assertArrayEquals(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8), bytesAndResult.getBytes()); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void testDecryptWithSessionKey(SOP sop) throws IOException { byte[] decrypted = sop.decrypt() .withSessionKey(SessionKey.fromString(SESSION_KEY)) @@ -55,6 +60,6 @@ public class ExternalDecryptWithSessionKeyTest extends AbstractExternalSOPTest { .toByteArrayAndResult() .getBytes(); - assertArrayEquals(PLAINTEXT.getBytes(StandardCharsets.UTF_8), decrypted); + Assertions.assertArrayEquals(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8), decrypted); } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java new file mode 100644 index 0000000..a822f89 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -0,0 +1,250 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; +import sop.Verification; +import sop.enums.SignAs; +import sop.exception.SOPGPException; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithAliceKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .mode(SignAs.Text) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void verifyKnownMessageWithAliceCert(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + + List verificationList = sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithBobKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithCarolKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithEncryptedKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword(TestData.PASSWORD) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + JUtils.assertArrayStartsWith(signature, TestData.BEGIN_PGP_SIGNATURE); + + List verificationList = sop.detachedVerify() + .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message); + + assertFalse(verificationList.isEmpty()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signArmorVerifyWithBobKey(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.detachedSign() + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .noArmor() + .data(message) + .toByteArrayAndResult() + .getBytes(); + + byte[] armored = sop.armor() + .data(signature) + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(armored) + .data(message); + + assertFalse(verificationList.isEmpty()); + JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void verifyNotAfterThrowsNoSignature(SOP sop) { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date beforeSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig + + assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .notAfter(beforeSignature) + .signatures(signature) + .data(message)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void verifyNotBeforeThrowsNoSignature(SOP sop) { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); + Date afterSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig + + assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .notBefore(afterSignature) + .signatures(signature) + .data(message)); + } + + + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { + assertThrows(SOPGPException.KeyIsProtected.class, () -> + sop.detachedSign() + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) + .toByteArrayAndResult() + .getBytes()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void signWithProtectedKeyAndMultiplePassphrasesTest(SOP sop) + throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + byte[] signature = sop.sign() + .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) + .withKeyPassword("wrong") + .withKeyPassword(TestData.PASSWORD) // correct + .withKeyPassword("wrong2") + .data(message) + .toByteArrayAndResult() + .getBytes(); + + assertFalse(sop.verify() + .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signature) + .data(message) + .isEmpty()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void verifyMissingCertCausesMissingArg(SOP sop) { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + + assertThrows(SOPGPException.MissingArg.class, () -> + sop.verify() + .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) + .data(message)); + } + +} diff --git a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java similarity index 72% rename from external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index fa5f2a8..255bc19 100644 --- a/external-sop/src/test/java/sop/external/ExternalEncryptDecryptRoundTripTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -2,10 +2,11 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; @@ -13,36 +14,33 @@ import sop.SOP; import sop.Verification; import sop.enums.EncryptAs; import sop.exception.SOPGPException; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static sop.testing.JUtils.assertSignedBy; -import static sop.testing.TestData.ALICE_CERT; -import static sop.testing.TestData.ALICE_KEY; -import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; -import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; -import static sop.testing.TestData.BOB_CERT; -import static sop.testing.TestData.BOB_KEY; -import static sop.testing.TestData.CAROL_CERT; -import static sop.testing.TestData.CAROL_KEY; -import static sop.testing.TestData.PLAINTEXT; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class EncryptDecryptTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() .withPassword("sw0rdf1sh") .plaintext(message) @@ -58,16 +56,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -79,16 +77,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = sop.decrypt() - .withKey(BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -97,16 +95,16 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(CAROL_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); byte[] plaintext = sop.decrypt() - .withKey(CAROL_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); @@ -115,11 +113,11 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) .getBytes(); @@ -129,7 +127,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(armored) .toByteArrayAndResult(); @@ -138,18 +136,18 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -160,23 +158,23 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertNotNull(result.getSessionKey().get()); List verificationList = result.getVerifications(); assertEquals(1, verificationList.size()); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] ciphertext = sop.encrypt() - .withCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) - .signWith(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.Text) .plaintext(message) .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -187,11 +185,11 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertNotNull(result.getSessionKey().get()); List verificationList = result.getVerifications(); assertEquals(1, verificationList.size()); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException { byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); byte[] key = sop.generateKey() @@ -223,7 +221,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void decryptVerifyNotAfterTest(SOP sop) { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + @@ -243,8 +241,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotAfter(beforeSignature) .ciphertext(message) .toByteArrayAndResult(); @@ -256,7 +254,7 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void decryptVerifyNotBeforeTest(SOP sop) { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + @@ -276,8 +274,8 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest assertThrows(SOPGPException.NoSignature.class, () -> { ByteArrayAndResult bytesAndResult = sop.decrypt() - .withKey(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .verifyWithCert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotBefore(afterSignature) .ciphertext(message) .toByteArrayAndResult(); @@ -289,9 +287,9 @@ public class ExternalEncryptDecryptRoundTripTest extends AbstractExternalSOPTest } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void missingArgsTest(SOP sop) { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() .plaintext(message) diff --git a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java similarity index 59% rename from external-sop/src/test/java/sop/external/ExternalExtractCertTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java index 53d7e6d..a0b421a 100644 --- a/external-sop/src/test/java/sop/external/ExternalExtractCertTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java @@ -2,31 +2,31 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; -import sop.testing.TestData; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.testing.JUtils.arrayStartsWith; -import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; -import static sop.testing.JUtils.assertArrayStartsWith; -import static sop.testing.JUtils.assertAsciiArmorEquals; -import static sop.testing.TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK; -import static sop.testing.TestData.END_PGP_PUBLIC_KEY_BLOCK; +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class ExtractCertTest extends AbstractSOPTest { -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalExtractCertTest extends AbstractExternalSOPTest { + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException { InputStream keyIn = sop.generateKey() .userId("Alice ") @@ -34,39 +34,39 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .getInputStream(); byte[] cert = sop.extractCert().key(keyIn).getBytes(); - assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(cert, END_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(cert, TestData.END_PGP_PUBLIC_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractAliceCertFromAliceKeyTest(SOP sop) throws IOException { byte[] armoredCert = sop.extractCert() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + JUtils.assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractBobsCertFromBobsKeyTest(SOP sop) throws IOException { byte[] armoredCert = sop.extractCert() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + JUtils.assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractCarolsCertFromCarolsKeyTest(SOP sop) throws IOException { byte[] armoredCert = sop.extractCert() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); + JUtils.assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractUnarmoredCertFromArmoredKeyTest(SOP sop) throws IOException { InputStream keyIn = sop.generateKey() .userId("Alice ") @@ -78,11 +78,11 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractArmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { InputStream keyIn = sop.generateKey() .userId("Alice ") @@ -94,12 +94,12 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertArrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(cert, END_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(cert, TestData.END_PGP_PUBLIC_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extractUnarmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { InputStream keyIn = sop.generateKey() .noArmor() @@ -112,6 +112,6 @@ public class ExternalExtractCertTest extends AbstractExternalSOPTest { .key(keyIn) .getBytes(); - assertFalse(arrayStartsWith(cert, BEGIN_PGP_PUBLIC_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); } } diff --git a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java similarity index 53% rename from external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java index 606efab..748817b 100644 --- a/external-sop/src/test/java/sop/external/ExternalGenerateKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java @@ -2,39 +2,41 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; -import sop.testing.JUtils; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import java.io.IOException; +import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.testing.JUtils.assertArrayEndsWithIgnoreNewlines; -import static sop.testing.JUtils.assertArrayStartsWith; -import static sop.testing.TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK; -import static sop.testing.TestData.END_PGP_PRIVATE_KEY_BLOCK; +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class GenerateKeyTest extends AbstractSOPTest { -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyTest(SOP sop) throws IOException { byte[] key = sop.generateKey() .userId("Alice ") .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyNoArmor(SOP sop) throws IOException { byte[] key = sop.generateKey() .userId("Alice ") @@ -42,11 +44,11 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertFalse(JUtils.arrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK)); + Assertions.assertFalse(JUtils.arrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyWithMultipleUserIdsTest(SOP sop) throws IOException { byte[] key = sop.generateKey() .userId("Alice ") @@ -54,23 +56,23 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyWithoutUserIdTest(SOP sop) throws IOException { byte[] key = sop.generateKey() .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyWithPasswordTest(SOP sop) throws IOException { byte[] key = sop.generateKey() .userId("Alice ") @@ -78,12 +80,12 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void generateKeyWithMultipleUserIdsAndPassword(SOP sop) throws IOException { byte[] key = sop.generateKey() .userId("Alice ") @@ -92,7 +94,7 @@ public class ExternalGenerateKeyTest extends AbstractExternalSOPTest { .generate() .getBytes(); - assertArrayStartsWith(key, BEGIN_PGP_PRIVATE_KEY_BLOCK); - assertArrayEndsWithIgnoreNewlines(key, END_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } } diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java similarity index 68% rename from external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java index 23ba7fd..6b1e250 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignDetachVerifyRoundTripTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java @@ -2,39 +2,42 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.SOP; import sop.Signatures; import sop.Verification; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static sop.testing.JUtils.arrayStartsWith; -import static sop.testing.JUtils.assertArrayStartsWith; -import static sop.testing.TestData.ALICE_CERT; -import static sop.testing.TestData.ALICE_KEY; -import static sop.testing.TestData.BEGIN_PGP_SIGNATURE; -import static sop.testing.TestData.PLAINTEXT; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -49,7 +52,7 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna .getBytes(); List verifications = sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(plaintext); @@ -57,12 +60,12 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest(SOP sop) throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); @@ -76,15 +79,15 @@ public class ExternalInlineSignDetachVerifyRoundTripTest extends AbstractExterna byte[] signatures = bytesAndResult.getResult() .getBytes(); - assertFalse(arrayStartsWith(signatures, BEGIN_PGP_SIGNATURE)); + Assertions.assertFalse(JUtils.arrayStartsWith(signatures, TestData.BEGIN_PGP_SIGNATURE)); byte[] armored = sop.armor() .data(signatures) .getBytes(); - assertArrayStartsWith(armored, BEGIN_PGP_SIGNATURE); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_SIGNATURE); List verifications = sop.detachedVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(plaintext); diff --git a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java similarity index 62% rename from external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java index 5d1012f..96782ea 100644 --- a/external-sop/src/test/java/sop/external/ExternalInlineSignVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java @@ -2,123 +2,121 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.SOP; import sop.Verification; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; -import sop.testing.JUtils; -import sop.testing.TestData; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static sop.testing.JUtils.assertSignedBy; -import static sop.testing.TestData.ALICE_CERT; -import static sop.testing.TestData.ALICE_KEY; -import static sop.testing.TestData.ALICE_PRIMARY_FINGERPRINT; -import static sop.testing.TestData.ALICE_SIGNING_FINGERPRINT; -import static sop.testing.TestData.BEGIN_PGP_MESSAGE; -import static sop.testing.TestData.BEGIN_PGP_SIGNED_MESSAGE; -import static sop.testing.TestData.PLAINTEXT; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class InlineSignInlineVerifyTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignVerifyAlice(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); + JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .getBytes(); - assertFalse(JUtils.arrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE)); + Assertions.assertFalse(JUtils.arrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE)); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void clearsignVerifyAlice(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] clearsigned = sop.inlineSign() - .key(ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.clearsigned) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(clearsigned, BEGIN_PGP_SIGNED_MESSAGE); + JUtils.assertArrayStartsWith(clearsigned, TestData.BEGIN_PGP_SIGNED_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(clearsigned) .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineVerifyCompareSignatureDate(SOP sop) throws IOException { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; ByteArrayAndResult> bytesAndResult = sop.inlineVerify() - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult(); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, ALICE_SIGNING_FINGERPRINT, ALICE_PRIMARY_FINGERPRINT, signatureDate); + JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void assertNotBeforeThrowsNoSignature(SOP sop) { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; @@ -126,13 +124,13 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notBefore(afterSignature) - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void assertNotAfterThrowsNoSignature(SOP sop) { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; @@ -140,22 +138,22 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() .notAfter(beforeSignature) - .cert(ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult()); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignVerifyBob(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); + JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) @@ -164,20 +162,20 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignVerifyCarol(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - JUtils.assertArrayStartsWith(inlineSigned, BEGIN_PGP_MESSAGE); + JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); ByteArrayAndResult> bytesAndResult = sop.inlineVerify() .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) @@ -186,13 +184,13 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { assertArrayEquals(message, bytesAndResult.getBytes()); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void inlineSignVerifyProtectedKey(SOP sop) throws IOException { - byte[] message = PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] inlineSigned = sop.inlineSign() .withKeyPassword(TestData.PASSWORD) @@ -207,7 +205,7 @@ public class ExternalInlineSignVerifyTest extends AbstractExternalSOPTest { .toByteArrayAndResult(); List verificationList = bytesAndResult.getResult(); - assertSignedBy(verificationList, TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); + JUtils.assertSignedBy(verificationList, TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); } } diff --git a/external-sop/src/test/java/sop/external/ExternalVersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java similarity index 65% rename from external-sop/src/test/java/sop/external/ExternalVersionTest.java rename to sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 4b1f87c..a1f4701 100644 --- a/external-sop/src/test/java/sop/external/ExternalVersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -2,22 +2,28 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.external; +package sop.testsuite.operation; import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import java.util.stream.Stream; + import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -@EnabledIf("sop.external.AbstractExternalSOPTest#hasBackends") -public class ExternalVersionTest extends AbstractExternalSOPTest { +@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +public class VersionTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void versionNameTest(SOP sop) { String name = sop.version().getName(); assertNotNull(name); @@ -25,21 +31,21 @@ public class ExternalVersionTest extends AbstractExternalSOPTest { } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void versionVersionTest(SOP sop) { String version = sop.version().getVersion(); - assertTrue(version.matches("\\d+(\\.\\d+)*\\S*")); + assertFalse(version.isEmpty()); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void backendVersionTest(SOP sop) { String backend = sop.version().getBackendVersion(); assertFalse(backend.isEmpty()); } @ParameterizedTest - @MethodSource("sop.external.AbstractExternalSOPTest#provideBackends") + @MethodSource("provideInstances") public void extendedVersionTest(SOP sop) { String extended = sop.version().getExtendedVersion(); assertFalse(extended.isEmpty()); diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java new file mode 100644 index 0000000..e3a27ee --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * SOP binary test suite. + */ +package sop.testsuite.operation; diff --git a/sop-java/src/testFixtures/java/sop/testsuite/package-info.java b/sop-java/src/testFixtures/java/sop/testsuite/package-info.java new file mode 100644 index 0000000..cfb68d2 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * SOP binary test suite. + */ +package sop.testsuite; From 6fad442cd02d318322a2ed3d2e791baba2996633 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:25:59 +0100 Subject: [PATCH 112/444] Update changelgo --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f9f2b8..4be2fbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0 ## 4.1.1-SNAPSHOT - Restructure test suite to allow simultaneous testing of multiple backends - Fix IOException in `sop sign` due to premature stream closing +- Allow for downstream implementations of `sop-java` to reuse the test suite + - Check out Javadoc of `sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory` for details ## 4.1.0 - Add module `external-sop` From 6ac133499ce30f175715ee20c0a925711d6e4dba Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:35:48 +0100 Subject: [PATCH 113/444] Enable tests only if test backends are available --- .../sop/testsuite/external/ExternalSOPInstanceFactory.java | 5 +++++ .../external/operation/ExternalArmorDearmorTest.java | 2 ++ .../operation/ExternalDecryptWithSessionKeyTest.java | 2 ++ .../operation/ExternalDetachedSignDetachedVerifyTest.java | 2 ++ .../external/operation/ExternalEncryptDecryptTest.java | 2 ++ .../external/operation/ExternalExtractCertTest.java | 2 ++ .../external/operation/ExternalGenerateKeyTest.java | 2 ++ .../ExternalInlineSignInlineDetachDetachedVerifyTest.java | 2 ++ .../operation/ExternalInlineSignInlineVerifyTest.java | 2 ++ .../testsuite/external/operation/ExternalVersionTest.java | 2 ++ .../java/sop/testsuite/operation/ArmorDearmorTest.java | 2 +- .../sop/testsuite/operation/DecryptWithSessionKeyTest.java | 2 +- .../testsuite/operation/DetachedSignDetachedVerifyTest.java | 2 +- .../java/sop/testsuite/operation/EncryptDecryptTest.java | 2 +- .../java/sop/testsuite/operation/ExtractCertTest.java | 2 +- .../java/sop/testsuite/operation/GenerateKeyTest.java | 2 +- .../operation/InlineSignInlineDetachDetachedVerifyTest.java | 2 +- .../sop/testsuite/operation/InlineSignInlineVerifyTest.java | 2 +- .../java/sop/testsuite/operation/VersionTest.java | 2 +- 19 files changed, 32 insertions(+), 9 deletions(-) diff --git a/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java index aa051ff..3f46fc1 100644 --- a/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java +++ b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java @@ -17,6 +17,11 @@ import java.util.List; import java.util.Map; import java.util.Properties; +/** + * This implementation of {@link SOPInstanceFactory} reads the JSON file at + *
external-sop/src/main/resources/sop/testsuite/external/config.json
+ * to determine configured external test backends + */ public class ExternalSOPInstanceFactory extends SOPInstanceFactory { @Override diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java index f3ee7be..1d8ff2b 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.ArmorDearmorTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalArmorDearmorTest extends ArmorDearmorTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java index 4f9364d..0ac03a4 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.DecryptWithSessionKeyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalDecryptWithSessionKeyTest extends DecryptWithSessionKeyTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java index b831295..13959df 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java @@ -4,7 +4,9 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.DetachedSignDetachedVerifyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalDetachedSignDetachedVerifyTest extends DetachedSignDetachedVerifyTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java index 3584c95..b83ca46 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.EncryptDecryptTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalEncryptDecryptTest extends EncryptDecryptTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java index 47cd8e1..f47656c 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.ExtractCertTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalExtractCertTest extends ExtractCertTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java index 6352736..7ac971b 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.GenerateKeyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalGenerateKeyTest extends GenerateKeyTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java index 48633f3..2dd3396 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.InlineSignInlineDetachDetachedVerifyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalInlineSignInlineDetachDetachedVerifyTest extends InlineSignInlineDetachDetachedVerifyTest { diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java index 705b05f..24e30aa 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.InlineSignInlineVerifyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalInlineSignInlineVerifyTest extends InlineSignInlineVerifyTest { } diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java index 4c0e3a3..ee63f09 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.VersionTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalVersionTest extends VersionTest { } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java index ce3427a..35959b0 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java @@ -19,7 +19,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ArmorDearmorTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java index 2321770..65ec4a5 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java @@ -21,7 +21,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class DecryptWithSessionKeyTest extends AbstractSOPTest { private static final String CIPHERTEXT = "-----BEGIN PGP MESSAGE-----\n" + diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java index a822f89..06a0f9b 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -24,7 +24,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 255bc19..8c62fb5 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -30,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class EncryptDecryptTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java index a0b421a..99acf81 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java @@ -18,7 +18,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.stream.Stream; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExtractCertTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java index 748817b..ce10763 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java @@ -16,7 +16,7 @@ import sop.testsuite.TestData; import java.io.IOException; import java.util.stream.Stream; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class GenerateKeyTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java index 6b1e250..3e20a09 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java @@ -24,7 +24,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java index 96782ea..7d0c227 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java @@ -26,7 +26,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class InlineSignInlineVerifyTest extends AbstractSOPTest { static Stream provideInstances() { diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index a1f4701..16aff4d 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -15,7 +15,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -@EnabledIf("sop.operation.AbstractSOPTest#hasBackends") +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class VersionTest extends AbstractSOPTest { static Stream provideInstances() { From 40dc9e3707f8b152eb6576481ea142ce9eff40f3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:36:05 +0100 Subject: [PATCH 114/444] Move gson version to version.gradle --- external-sop/build.gradle | 2 +- version.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/external-sop/build.gradle b/external-sop/build.gradle index acd55f6..dea3c3d 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -25,7 +25,7 @@ dependencies { implementation "com.google.code.findbugs:jsr305:$jsrVersion" // The ExternalTestSubjectFactory reads json config file to find configured SOP binaries... - testImplementation "com.google.code.gson:gson:2.10.1" + testImplementation "com.google.code.gson:gson:$gsonVersion" // ...and extends TestSubjectFactory testImplementation(testFixtures(project(":sop-java"))) } diff --git a/version.gradle b/version.gradle index d956fc9..cf81177 100644 --- a/version.gradle +++ b/version.gradle @@ -8,6 +8,7 @@ allprojects { isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 + gsonVersion = '2.10.1' jsrVersion = '3.0.2' junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' From 546b97fcc906acaef103b58c217318770987f473 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:40:14 +0100 Subject: [PATCH 115/444] Fix checkstyle error --- .../java/sop/testsuite/external/ExternalSOPInstanceFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java index 3f46fc1..f84f7e6 100644 --- a/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java +++ b/external-sop/src/test/java/sop/testsuite/external/ExternalSOPInstanceFactory.java @@ -20,7 +20,7 @@ import java.util.Properties; /** * This implementation of {@link SOPInstanceFactory} reads the JSON file at *
external-sop/src/main/resources/sop/testsuite/external/config.json
- * to determine configured external test backends + * to determine configured external test backends. */ public class ExternalSOPInstanceFactory extends SOPInstanceFactory { From 9fdc8a5badd43b0f57fb27a33e5fc06885c5f337 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:40:38 +0100 Subject: [PATCH 116/444] Improve comment on external-sop/build.gradle --- external-sop/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external-sop/build.gradle b/external-sop/build.gradle index dea3c3d..1bb86fc 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -36,7 +36,8 @@ test { useJUnitPlatform() - // since we test external backends, we ignore test failures in this module + // since we test external backends which we might not control, + // we ignore test failures in this module ignoreFailures = true } From d488eee36f5ce3dcc18b58744d0498510b7ebe49 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:41:13 +0100 Subject: [PATCH 117/444] SOP-Java 4.1.1 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be2fbb..dd66534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 4.1.1-SNAPSHOT +## 4.1.1 - Restructure test suite to allow simultaneous testing of multiple backends - Fix IOException in `sop sign` due to premature stream closing - Allow for downstream implementations of `sop-java` to reuse the test suite diff --git a/version.gradle b/version.gradle index cf81177..503e3ab 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '4.1.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 6448debf46de9b440ffbb9fb5d5ea818b3dcf2e8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:45:26 +0100 Subject: [PATCH 118/444] SOP-Java 4.1.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 503e3ab..f69a160 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '4.1.1' - isSnapshot = false + shortVersion = '4.1.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From f37354d2685c5fe010fb46a5a7ea4bcd0005ce84 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Jan 2023 18:50:41 +0100 Subject: [PATCH 119/444] Fix reuse --- .reuse/dep5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index f5be92f..8feb5a2 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -19,6 +19,6 @@ Files: .woodpecker/* Copyright: 2022 the original author or authors. License: Apache-2.0 -Files: external-sop/src/main/resources/sop/external/* +Files: external-sop/src/main/resources/sop/testsuite/external/* Copyright: 2023 the original author or authors -License: Apache-2.0 \ No newline at end of file +License: Apache-2.0 From ae64414492ec9a9c71bb75b843245cff4346d2df Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 19:00:35 +0200 Subject: [PATCH 120/444] Decide when to remove --verify-out --- .../src/main/java/sop/cli/picocli/commands/DecryptCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java index cde7c50..a681b4d 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java @@ -51,7 +51,7 @@ public class DecryptCmd extends AbstractSopCmd { paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); - @CommandLine.Option(names = {OPT_VERIFICATIONS_OUT, "--verify-out"}, // TODO: Remove --verify-out at some point + @CommandLine.Option(names = {OPT_VERIFICATIONS_OUT, "--verify-out"}, // TODO: Remove --verify-out in 06 paramLabel = "VERIFICATIONS") String verifyOut; From 5d04bb965b6af45c46dcc607b8191bf2501c2e0d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 19:29:10 +0200 Subject: [PATCH 121/444] Add support for 05-style verifications --- sop-java/src/main/java/sop/Verification.java | 76 +++++++++++++++++-- .../main/java/sop/enums/SignatureMode.java | 10 +++ .../test/java/sop/util/VerificationTest.java | 56 ++++++++++++++ 3 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 sop-java/src/main/java/sop/enums/SignatureMode.java create mode 100644 sop-java/src/test/java/sop/util/VerificationTest.java diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index 6ff63f6..de5b2d3 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -6,6 +6,7 @@ package sop; import java.util.Date; +import sop.enums.SignatureMode; import sop.util.UTCUtil; public class Verification { @@ -13,20 +14,52 @@ public class Verification { private final Date creationTime; private final String signingKeyFingerprint; private final String signingCertFingerprint; + private final SignatureMode signatureMode; + private final String description; + + public static final String MODE = "mode:"; + public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) { + this(creationTime, signingKeyFingerprint, signingCertFingerprint, null, null); + } + + public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, SignatureMode signatureMode, String description) { this.creationTime = creationTime; this.signingKeyFingerprint = signingKeyFingerprint; this.signingCertFingerprint = signingCertFingerprint; + this.signatureMode = signatureMode; + this.description = description == null ? null : description.trim(); } public static Verification fromString(String toString) { String[] split = toString.trim().split(" "); - if (split.length != 3) { + if (split.length < 3) { throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint'"); } - return new Verification(UTCUtil.parseUTCDate(split[0]), split[1], split[2]); + SignatureMode mode = null; + int index = 3; + if (split[index].startsWith(MODE)) { + mode = SignatureMode.valueOf(split[3].substring(MODE.length())); + index++; + } + + StringBuilder sb = new StringBuilder(); + for (int i = index; i < split.length; i++) { + if (sb.length() != 0) { + sb.append(' '); + } + sb.append(split[i]); + } + + return new Verification( + UTCUtil.parseUTCDate(split[0]), + split[1], // key FP + split[2], // cert FP + mode, + sb.length() != 0 ? sb.toString() : null // description + ); } /** @@ -56,13 +89,42 @@ public class Verification { return signingCertFingerprint; } + /** + * Return the mode of the signature. + * + * @return signature mode + */ + public SignatureMode getSignatureMode() { + return signatureMode; + } + + /** + * Return an optional description. + * + * @return description + */ + public String getDescription() { + return description; + } + @Override public String toString() { - return UTCUtil.formatUTCDate(getCreationTime()) + - ' ' + - getSigningKeyFingerprint() + - ' ' + - getSigningCertFingerprint(); + StringBuilder sb = new StringBuilder() + .append(UTCUtil.formatUTCDate(getCreationTime())) + .append(' ') + .append(getSigningKeyFingerprint()) + .append(' ') + .append(getSigningCertFingerprint()); + + if (signatureMode != null) { + sb.append(' ').append(MODE).append(signatureMode); + } + + if (description != null) { + sb.append(' ').append(description); + } + + return sb.toString(); } @Override diff --git a/sop-java/src/main/java/sop/enums/SignatureMode.java b/sop-java/src/main/java/sop/enums/SignatureMode.java new file mode 100644 index 0000000..b41f6ce --- /dev/null +++ b/sop-java/src/main/java/sop/enums/SignatureMode.java @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums; + +public enum SignatureMode { + binary, + text +} diff --git a/sop-java/src/test/java/sop/util/VerificationTest.java b/sop-java/src/test/java/sop/util/VerificationTest.java new file mode 100644 index 0000000..3688d6d --- /dev/null +++ b/sop-java/src/test/java/sop/util/VerificationTest.java @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util; + +import org.junit.jupiter.api.Test; +import sop.Verification; +import sop.enums.SignatureMode; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VerificationTest { + + @Test + public void limitedConstructorTest() { + Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); + String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; + String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; + Verification verification = new Verification(signDate, keyFP, certFP); + assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString()); + } + + public void limitedParsingTest() { + String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; + Verification verification = Verification.fromString(string); + assertEquals(string, verification.toString()); + } + + @Test + public void extendedConstructorTest() { + Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); + String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; + String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; + SignatureMode mode = SignatureMode.binary; + String description = "certificate from dkg.asc"; + Verification verification = new Verification(signDate, keyFP, certFP, mode, description); + assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc", verification.toString()); + assertEquals(SignatureMode.binary, verification.getSignatureMode()); + assertEquals(description, verification.getDescription()); + } + + @Test + public void extendedParsingTest() { + String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc"; + Verification verification = Verification.fromString(string); + assertEquals(string, verification.toString()); + + // no mode + string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B certificate from dkg.asc"; + verification = Verification.fromString(string); + assertEquals(string, verification.toString()); + } +} From f5e34bce6cc8954f8badb8960ae85e9ce6870aa9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 19:51:49 +0200 Subject: [PATCH 122/444] Fix IOOB in Verification.fromString() --- sop-java/src/main/java/sop/Verification.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index de5b2d3..781af6f 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -38,6 +38,13 @@ public class Verification { throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint'"); } + if (split.length == 3) { + return new Verification(UTCUtil.parseUTCDate(split[0]), + split[1], // key FP + split[2] // cert FP + ); + } + SignatureMode mode = null; int index = 3; if (split[index].startsWith(MODE)) { From 6d28a7b07df88eb618d2f056291f780cafd17c86 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 19:52:34 +0200 Subject: [PATCH 123/444] Add new Exceptions --- .../java/sop/exception/SOPGPException.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 7d15705..a192c6c 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -378,4 +378,36 @@ public abstract class SOPGPException extends RuntimeException { return EXIT_CODE; } } + + public static class IncompatibleOptions extends SOPGPException { + + public static final int EXIT_CODE = 83; + + public IncompatibleOptions() { + super(); + } + + public IncompatibleOptions(String errorMsg) { + super(errorMsg); + } + + @Override + public int getExitCode() { + return EXIT_CODE; + } + } + + public static class UnsupportedProfile extends SOPGPException { + + public static final int EXIT_CODE = 89; + + public UnsupportedProfile() { + super(); + } + + @Override + public int getExitCode() { + return EXIT_CODE; + } + } } From 17b305924c92cd272dcad18b5255440ac393e70c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 19:53:21 +0200 Subject: [PATCH 124/444] Throw IncompatibleOptions error for sign --as=clearsigned --no-armor --- .../main/java/sop/cli/picocli/commands/InlineSignCmd.java | 5 +++++ sop-java-picocli/src/main/resources/msg_sop.properties | 3 +++ sop-java-picocli/src/main/resources/msg_sop_de.properties | 3 +++ 3 files changed, 11 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 2608783..7e7c8af 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -41,6 +41,11 @@ public class InlineSignCmd extends AbstractSopCmd { InlineSign inlineSign = throwIfUnsupportedSubcommand( SopCLI.getSop().inlineSign(), "inline-sign"); + if (!armor && type == InlineSignAs.clearsigned) { + String errorMsg = getMsg("sop.error.usage.incompatible_options.clearsigned_no_armor"); + throw new SOPGPException.IncompatibleOptions(errorMsg); + } + if (type != null) { try { inlineSign.mode(type); diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index c0f9f67..069b0ce 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -32,6 +32,8 @@ usage.exitCodeList.15=69:Unsupported subcommand usage.exitCodeList.16=71:Unsupported special prefix (e.g. \"@ENV/@FD\") of indirect parameter usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator already exists) usage.exitCodeList.18=79:Key is not signing capable +usage.exitCodeList.19=83:Options were supplied that are incompatible with each other +usage.exitCodeList.20=89:The requested profile is unsupported, or the indicated subcommand does not accept profiles ## SHARED RESOURCES stacktrace=Print stacktrace @@ -73,6 +75,7 @@ sop.error.usage.password_or_cert_required=At least one password file or cert fil sop.error.usage.argument_required=Argument '%s' is required. sop.error.usage.parameter_required=Parameter '%s' is required. sop.error.usage.option_requires_other_option=Option '%s' is requested, but no option %s was provided. +sop.error.usage.incompatible_options.clearsigned_no_armor=Options '--no-armor' and '--as=clearsigned' are incompatible. # Feature Support sop.error.feature_support.subcommand_not_supported=Subcommand '%s' is not supported. sop.error.feature_support.option_not_supported=Option '%s' not supported. \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index b05c86a..a8569e5 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -32,6 +32,8 @@ usage.exitCodeList.15=69:Nicht unterst usage.exitCodeList.16=71:Nicht unterstützter Spezialprefix (z.B.. "@ENV/@FD") von indirektem Parameter usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner entspricht, existiert bereits) usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren +usage.exitCodeList.19=83:Miteinander inkompatible Optionen spezifiziert +usage.exitCodeList.20=89:Das angeforderte Profil wird nicht unterstützt, oder der angegebene Unterbefehl akzeptiert keine Profile ## SHARED RESOURCES stacktrace=Stacktrace ausgeben @@ -72,6 +74,7 @@ sop.error.usage.password_or_cert_required=Es wird mindestens ein Passwort und/od sop.error.usage.argument_required=Argument '%s' ist erforderlich. sop.error.usage.parameter_required=Parameter '%s' ist erforderlich. sop.error.usage.option_requires_other_option=Option '%s' wurde angegeben, jedoch kein Wert für %s. +sop.error.usage.incompatible_options.clearsigned_no_armor=Optionen '--no-armor' und '--as=clearsigned' sind inkompatibel. # Feature Support sop.error.feature_support.subcommand_not_supported=Unterbefehl '%s' wird nicht unterstützt. sop.error.feature_support.option_not_supported=Option '%s' wird nicht unterstützt. \ No newline at end of file From 83a003e80f88643928410cf79df47a68c8a40fa9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Apr 2023 15:06:37 +0200 Subject: [PATCH 125/444] Add list-profiles command --- .../main/java/sop/external/ExternalSOP.java | 7 +++ .../operation/GenerateKeyExternal.java | 6 +++ .../operation/ListProfilesExternal.java | 47 +++++++++++++++++++ .../operation/ExternalListProfilesTest.java | 13 +++++ .../src/main/java/sop/cli/picocli/SopCLI.java | 2 + .../cli/picocli/commands/GenerateKeyCmd.java | 6 +++ .../cli/picocli/commands/ListProfilesCmd.java | 39 +++++++++++++++ .../resources/msg_list-profiles.properties | 10 ++++ .../resources/msg_list-profiles_de.properties | 10 ++++ .../test/java/sop/cli/picocli/SOPTest.java | 6 +++ sop-java/src/main/java/sop/SOP.java | 2 + .../main/java/sop/operation/GenerateKey.java | 9 ++++ .../main/java/sop/operation/ListProfiles.java | 19 ++++++++ .../testsuite/operation/ListProfilesTest.java | 40 ++++++++++++++++ 14 files changed, 216 insertions(+) create mode 100644 external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java create mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java create mode 100644 sop-java-picocli/src/main/resources/msg_list-profiles.properties create mode 100644 sop-java-picocli/src/main/resources/msg_list-profiles_de.properties create mode 100644 sop-java/src/main/java/sop/operation/ListProfiles.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index add37c5..f6e90a5 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -18,6 +18,7 @@ import sop.external.operation.GenerateKeyExternal; import sop.external.operation.InlineDetachExternal; import sop.external.operation.InlineSignExternal; import sop.external.operation.InlineVerifyExternal; +import sop.external.operation.ListProfilesExternal; import sop.external.operation.VersionExternal; import sop.operation.Armor; import sop.operation.Dearmor; @@ -30,6 +31,7 @@ import sop.operation.GenerateKey; import sop.operation.InlineDetach; import sop.operation.InlineSign; import sop.operation.InlineVerify; +import sop.operation.ListProfiles; import sop.operation.Version; import javax.annotation.Nonnull; @@ -154,6 +156,11 @@ public class ExternalSOP implements SOP { return new ArmorExternal(binaryName, properties); } + @Override + public ListProfiles listProfiles() { + return new ListProfilesExternal(binaryName, properties); + } + @Override public Dearmor dearmor() { return new DearmorExternal(binaryName, properties); diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index 95e86b8..e0ca97e 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -51,6 +51,12 @@ public class GenerateKeyExternal implements GenerateKey { return this; } + @Override + public GenerateKey profile(String profile) { + commandList.add("--profile=" + profile); + return this; + } + @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java new file mode 100644 index 0000000..85eae6c --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.external.ExternalSOP; +import sop.operation.ListProfiles; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +public class ListProfilesExternal extends ListProfiles { + + private final List commandList = new ArrayList<>(); + private final List envList; + + public ListProfilesExternal(String binary, Properties properties) { + this.commandList.add(binary); + this.commandList.add("list-profiles"); + this.envList = ExternalSOP.propertiesToEnv(properties); + } + + @Override + public List ofCommand(String command) { + commandList.add(command); + try { + String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); + return Arrays.asList(output.split("\n")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List global() { + try { + String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); + return Arrays.asList(output.split("\n")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java new file mode 100644 index 0000000..18da883 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import sop.testsuite.operation.ListProfilesTest; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class ExternalListProfilesTest extends ListProfilesTest { + +} diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 5f8b944..fab99e0 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -16,6 +16,7 @@ import sop.cli.picocli.commands.ExtractCertCmd; import sop.cli.picocli.commands.GenerateKeyCmd; import sop.cli.picocli.commands.InlineSignCmd; import sop.cli.picocli.commands.InlineVerifyCmd; +import sop.cli.picocli.commands.ListProfilesCmd; import sop.cli.picocli.commands.SignCmd; import sop.cli.picocli.commands.VerifyCmd; import sop.cli.picocli.commands.VersionCmd; @@ -41,6 +42,7 @@ import java.util.ResourceBundle; VerifyCmd.class, InlineSignCmd.class, InlineVerifyCmd.class, + ListProfilesCmd.class, VersionCmd.class, AutoComplete.GenerateCompletion.class } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index d215baf..a592863 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -30,6 +30,10 @@ public class GenerateKeyCmd extends AbstractSopCmd { paramLabel = "PASSWORD") String withKeyPassword; + @CommandLine.Option(names = "--profile", + paramLabel = "PROFILE") + String profile = "default"; + @Override public void run() { GenerateKey generateKey = throwIfUnsupportedSubcommand( @@ -43,6 +47,8 @@ public class GenerateKeyCmd extends AbstractSopCmd { generateKey.noArmor(); } + generateKey.profile(profile); + if (withKeyPassword != null) { try { String password = stringFromInputStream(getInput(withKeyPassword)); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java new file mode 100644 index 0000000..14211c3 --- /dev/null +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import picocli.CommandLine; +import sop.cli.picocli.SopCLI; +import sop.operation.ListProfiles; + +@CommandLine.Command(name = "list-profiles", + resourceBundle = "msg_list-profiles", + exitCodeOnInvalidInput = 37) +public class ListProfilesCmd extends AbstractSopCmd { + + @CommandLine.Parameters(paramLabel = "COMMAND", arity="0..1", descriptionKey = "subcommand") + String subcommand; + + @Override + public void run() { + ListProfiles listProfiles = throwIfUnsupportedSubcommand( + SopCLI.getSop().listProfiles(), "list-profiles"); + + if (subcommand == null) { + for (String profile : listProfiles.global()) { + // CHECKSTYLE:OFF + System.out.println(profile); + // CHECKSTYLE:ON + } + return; + } + + for (String profile : listProfiles.ofCommand(subcommand)) { + // CHECKSTYLE:OFF + System.out.println(profile); + // CHECKSTYLE:ON + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles.properties b/sop-java-picocli/src/main/resources/msg_list-profiles.properties new file mode 100644 index 0000000..71bf5aa --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_list-profiles.properties @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Emit a list of profiles supported by the identified subcommand +subcommand=Subcommand for which to list profiles +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties new file mode 100644 index 0000000..e6d8836 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden +subcommand=Unterbefehl für welchen Profile gelistet werden sollen +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 9ca88da..c05440e 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -26,6 +26,7 @@ import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; +import sop.operation.ListProfiles; import sop.operation.Version; public class SOPTest { @@ -95,6 +96,11 @@ public class SOPTest { return null; } + @Override + public ListProfiles listProfiles() { + return null; + } + @Override public InlineDetach inlineDetach() { return null; diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 284a0ab..7ce617a 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -15,6 +15,7 @@ import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; +import sop.operation.ListProfiles; import sop.operation.Version; /** @@ -146,4 +147,5 @@ public interface SOP { */ Dearmor dearmor(); + ListProfiles listProfiles(); } diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java index af7275b..1eb2620 100644 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ b/sop-java/src/main/java/sop/operation/GenerateKey.java @@ -6,6 +6,7 @@ package sop.operation; import java.io.IOException; import java.io.InputStream; +import java.util.List; import sop.Ready; import sop.exception.SOPGPException; @@ -56,6 +57,14 @@ public interface GenerateKey { return withKeyPassword(UTF8Util.decodeUTF8(password)); } + /** + * Pass in a profile identifier. + * + * @param profile profile identifier + * @return this + */ + GenerateKey profile(String profile); + /** * Generate the OpenPGP key and return it encoded as an {@link InputStream}. * diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java new file mode 100644 index 0000000..bdfa811 --- /dev/null +++ b/sop-java/src/main/java/sop/operation/ListProfiles.java @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation; + +import java.util.List; + +public abstract class ListProfiles { + + public ListProfiles() { + + } + + public abstract List ofCommand(String command); + + public abstract List global(); + +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java new file mode 100644 index 0000000..20f0590 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class ListProfilesTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void listGlobalProfiles(SOP sop) throws IOException { + List profiles = sop.listProfiles() + .global(); + assertFalse(profiles.isEmpty()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void listGenerateKeyProfiles(SOP sop) throws IOException { + List profiles = sop.listProfiles() + .ofCommand("generate-key"); + assertFalse(profiles.isEmpty()); + } + +} From 0ed5c52f4bc8db56c0afb833f0fb9db9572df404 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Apr 2023 15:27:23 +0200 Subject: [PATCH 126/444] Change ListProfiles class to interface --- .../sop/external/operation/ListProfilesExternal.java | 2 +- .../src/main/java/sop/exception/SOPGPException.java | 4 ++++ sop-java/src/main/java/sop/operation/ListProfiles.java | 10 +++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java index 85eae6c..1c6cdf3 100644 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -13,7 +13,7 @@ import java.util.Arrays; import java.util.List; import java.util.Properties; -public class ListProfilesExternal extends ListProfiles { +public class ListProfilesExternal implements ListProfiles { private final List commandList = new ArrayList<>(); private final List envList; diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index a192c6c..04da74d 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -405,6 +405,10 @@ public abstract class SOPGPException extends RuntimeException { super(); } + public UnsupportedProfile(String errorMessage) { + super(errorMessage); + } + @Override public int getExitCode() { return EXIT_CODE; diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java index bdfa811..e27789f 100644 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ b/sop-java/src/main/java/sop/operation/ListProfiles.java @@ -6,14 +6,10 @@ package sop.operation; import java.util.List; -public abstract class ListProfiles { +public interface ListProfiles { - public ListProfiles() { + List ofCommand(String command); - } - - public abstract List ofCommand(String command); - - public abstract List global(); + List global(); } From b8544396f8c4e6f1df2d6165b7ba1b4400b9d500 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:03:22 +0200 Subject: [PATCH 127/444] Add Profile class --- sop-java/src/main/java/sop/Profile.java | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 sop-java/src/main/java/sop/Profile.java diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java new file mode 100644 index 0000000..4a9b5b9 --- /dev/null +++ b/sop-java/src/main/java/sop/Profile.java @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop; + +/** + * Tuple class bundling a profile name and description. + * + * @see + * SOP Spec - Profile + */ +public class Profile { + + private final String name; + private final String description; + + public Profile(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String toString() { + return getName() + ": " + getDescription(); + } +} From 5935d65c90029c93595b0be0d338c11c71a8d1f5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:06:34 +0200 Subject: [PATCH 128/444] Implement profiles --- .../operation/ListProfilesExternal.java | 19 ++++++------ .../cli/picocli/commands/GenerateKeyCmd.java | 9 ++++-- .../cli/picocli/commands/ListProfilesCmd.java | 18 +++++------- .../resources/msg_generate-key.properties | 1 + .../resources/msg_generate-key_de.properties | 1 + .../src/main/resources/msg_sop.properties | 4 ++- .../src/main/resources/msg_sop_de.properties | 4 ++- .../java/sop/exception/SOPGPException.java | 29 ++++++++++++++++--- .../main/java/sop/operation/GenerateKey.java | 14 +++++++-- .../main/java/sop/operation/ListProfiles.java | 6 ++-- .../testsuite/operation/ListProfilesTest.java | 13 ++------- 11 files changed, 75 insertions(+), 43 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java index 1c6cdf3..b9df0f3 100644 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -4,12 +4,12 @@ package sop.external.operation; +import sop.Profile; import sop.external.ExternalSOP; import sop.operation.ListProfiles; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Properties; @@ -25,23 +25,22 @@ public class ListProfilesExternal implements ListProfiles { } @Override - public List ofCommand(String command) { + public List subcommand(String command) { commandList.add(command); try { String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); - return Arrays.asList(output.split("\n")); + return toProfiles(output); } catch (IOException e) { throw new RuntimeException(e); } } - @Override - public List global() { - try { - String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); - return Arrays.asList(output.split("\n")); - } catch (IOException e) { - throw new RuntimeException(e); + private List toProfiles(String output) { + List profiles = new ArrayList<>(); + for (String line : output.split("\n")) { + String[] split = line.split(": "); + profiles.add(new Profile(split[0], split[1])); } + return profiles; } } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index a592863..1d08b7c 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -31,7 +31,7 @@ public class GenerateKeyCmd extends AbstractSopCmd { String withKeyPassword; @CommandLine.Option(names = "--profile", - paramLabel = "PROFILE") + paramLabel = "PROFILE") String profile = "default"; @Override @@ -47,7 +47,12 @@ public class GenerateKeyCmd extends AbstractSopCmd { generateKey.noArmor(); } - generateKey.profile(profile); + try { + generateKey.profile(profile); + } catch (SOPGPException.UnsupportedProfile e) { + String errorMsg = getMsg("sop.error.usage.profile_not_supported", "generate-key", profile); + throw new SOPGPException.UnsupportedProfile(errorMsg, e); + } if (withKeyPassword != null) { try { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java index 14211c3..be271e6 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java @@ -5,7 +5,9 @@ package sop.cli.picocli.commands; import picocli.CommandLine; +import sop.Profile; import sop.cli.picocli.SopCLI; +import sop.exception.SOPGPException; import sop.operation.ListProfiles; @CommandLine.Command(name = "list-profiles", @@ -13,7 +15,7 @@ import sop.operation.ListProfiles; exitCodeOnInvalidInput = 37) public class ListProfilesCmd extends AbstractSopCmd { - @CommandLine.Parameters(paramLabel = "COMMAND", arity="0..1", descriptionKey = "subcommand") + @CommandLine.Parameters(paramLabel = "COMMAND", arity="1", descriptionKey = "subcommand") String subcommand; @Override @@ -21,19 +23,15 @@ public class ListProfilesCmd extends AbstractSopCmd { ListProfiles listProfiles = throwIfUnsupportedSubcommand( SopCLI.getSop().listProfiles(), "list-profiles"); - if (subcommand == null) { - for (String profile : listProfiles.global()) { + try { + for (Profile profile : listProfiles.subcommand(subcommand)) { // CHECKSTYLE:OFF System.out.println(profile); // CHECKSTYLE:ON } - return; - } - - for (String profile : listProfiles.ofCommand(subcommand)) { - // CHECKSTYLE:OFF - System.out.println(profile); - // CHECKSTYLE:ON + } catch (SOPGPException.UnsupportedProfile e) { + String errorMsg = getMsg("sop.error.feature_support.subcommand_does_not_support_profiles", subcommand); + throw new SOPGPException.UnsupportedProfile(errorMsg, e); } } } diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 5744199..4647791 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -4,6 +4,7 @@ usage.header=Generate a secret key no-armor=ASCII armor the output USERID[0..*]=User-ID, e.g. "Alice " +profile=Profile identifier to switch between profiles with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index b3cbf62..41a493b 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -4,6 +4,7 @@ usage.header=Generiere einen privaten Schlüssel no-armor=Schütze Ausgabe mit ASCII Armor USERID[0..*]=Nutzer-ID, z.B.. "Alice " +profile=Profil-Identifikator um zwischen Profilen zu wechseln with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 069b0ce..6ddcf9c 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -74,8 +74,10 @@ sop.error.runtime.cannot_decrypt_message=Message could not be decrypted. sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption. sop.error.usage.argument_required=Argument '%s' is required. sop.error.usage.parameter_required=Parameter '%s' is required. +sop.error.usage.profile_not_supported=Subcommand '%s' does not support profile '%s'. sop.error.usage.option_requires_other_option=Option '%s' is requested, but no option %s was provided. sop.error.usage.incompatible_options.clearsigned_no_armor=Options '--no-armor' and '--as=clearsigned' are incompatible. # Feature Support sop.error.feature_support.subcommand_not_supported=Subcommand '%s' is not supported. -sop.error.feature_support.option_not_supported=Option '%s' not supported. \ No newline at end of file +sop.error.feature_support.option_not_supported=Option '%s' not supported. +sop.error.feature_support.subcommand_does_not_support_profiles=Subcommand '%s' does not support any profiles. \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index a8569e5..a20a11d 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -73,8 +73,10 @@ sop.error.runtime.cannot_decrypt_message=Nachricht konnte nicht entschl sop.error.usage.password_or_cert_required=Es wird mindestens ein Passwort und/oder Zertifikat zur Verschlüsselung benötigt. sop.error.usage.argument_required=Argument '%s' ist erforderlich. sop.error.usage.parameter_required=Parameter '%s' ist erforderlich. +sop.error.usage.profile_not_supported=Unterbefehl '%s' unterstützt Profil '%s' nicht. sop.error.usage.option_requires_other_option=Option '%s' wurde angegeben, jedoch kein Wert für %s. sop.error.usage.incompatible_options.clearsigned_no_armor=Optionen '--no-armor' und '--as=clearsigned' sind inkompatibel. # Feature Support sop.error.feature_support.subcommand_not_supported=Unterbefehl '%s' wird nicht unterstützt. -sop.error.feature_support.option_not_supported=Option '%s' wird nicht unterstützt. \ No newline at end of file +sop.error.feature_support.option_not_supported=Option '%s' wird nicht unterstützt. +sop.error.feature_support.subcommand_does_not_support_profiles=Unterbefehl '%s' unterstützt keine Profile. \ No newline at end of file diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 04da74d..1618a58 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -401,12 +401,33 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 89; - public UnsupportedProfile() { - super(); + private final String subcommand; + private final String profile; + + public UnsupportedProfile(String subcommand) { + super("Subcommand '" + subcommand + "' does not support any profiles."); + this.subcommand = subcommand; + this.profile = null; } - public UnsupportedProfile(String errorMessage) { - super(errorMessage); + public UnsupportedProfile(String subcommand, String profile) { + super("Subcommand '" + subcommand + "' does not support profile '" + profile + "'."); + this.subcommand = subcommand; + this.profile = profile; + } + + public UnsupportedProfile(String errorMsg, UnsupportedProfile e) { + super(errorMsg, e); + this.subcommand = e.getSubcommand(); + this.profile = e.getProfile(); + } + + public String getSubcommand() { + return subcommand; + } + + public String getProfile() { + return profile; } @Override diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java index 1eb2620..27a3b19 100644 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ b/sop-java/src/main/java/sop/operation/GenerateKey.java @@ -6,8 +6,8 @@ package sop.operation; import java.io.IOException; import java.io.InputStream; -import java.util.List; +import sop.Profile; import sop.Ready; import sop.exception.SOPGPException; import sop.util.UTF8Util; @@ -57,11 +57,21 @@ public interface GenerateKey { return withKeyPassword(UTF8Util.decodeUTF8(password)); } + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + default GenerateKey profile(Profile profile) { + return profile(profile.getName()); + } + /** * Pass in a profile identifier. * * @param profile profile identifier - * @return this + * @return builder instance */ GenerateKey profile(String profile); diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java index e27789f..87bb3b3 100644 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ b/sop-java/src/main/java/sop/operation/ListProfiles.java @@ -4,12 +4,12 @@ package sop.operation; +import sop.Profile; + import java.util.List; public interface ListProfiles { - List ofCommand(String command); - - List global(); + List subcommand(String command); } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java index 20f0590..e97dd39 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java @@ -7,6 +7,7 @@ package sop.testsuite.operation; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import sop.Profile; import sop.SOP; import java.io.IOException; @@ -21,19 +22,11 @@ public class ListProfilesTest extends AbstractSOPTest { return provideBackends(); } - @ParameterizedTest - @MethodSource("provideInstances") - public void listGlobalProfiles(SOP sop) throws IOException { - List profiles = sop.listProfiles() - .global(); - assertFalse(profiles.isEmpty()); - } - @ParameterizedTest @MethodSource("provideInstances") public void listGenerateKeyProfiles(SOP sop) throws IOException { - List profiles = sop.listProfiles() - .ofCommand("generate-key"); + List profiles = sop.listProfiles() + .subcommand("generate-key"); assertFalse(profiles.isEmpty()); } From dff5b01eecd582cfd32bffdff3e099cc55a0c0da Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:06:49 +0200 Subject: [PATCH 129/444] Add some documentation to AbstractSopCmd --- .../main/java/sop/cli/picocli/commands/AbstractSopCmd.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index 176d97b..d155e9c 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -24,8 +24,14 @@ import java.util.Locale; import java.util.ResourceBundle; import java.util.regex.Pattern; +/** + * Abstract super class of SOP subcommands. + */ public abstract class AbstractSopCmd implements Runnable { + /** + * Interface to modularize resolving of environment variables. + */ public interface EnvironmentVariableResolver { /** * Resolve the value of the given environment variable. From 1e805db1f018f871c1cee1d3eaa04adda54695bc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:19:22 +0200 Subject: [PATCH 130/444] Bump version to 5.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index f69a160..fce24ff 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '4.1.2' + shortVersion = '5.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From 19663c4decc5ec727fbe07a35ba3e192513b4113 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:21:52 +0200 Subject: [PATCH 131/444] Update SOP revision --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f23e580..3a4ad20 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 4](https://img.shields.io/badge/Spec%20Revision-4-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/04/) +[![Spec Revision: 5](https://img.shields.io/badge/Spec%20Revision-5-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/05/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) From d38556f79aa4b2f4e7e66caeaf6814a62619d0d9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:01:50 +0200 Subject: [PATCH 132/444] GenerateKeyCmd: Do not assert default profile --- .../cli/picocli/commands/GenerateKeyCmd.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index 1d08b7c..aac7124 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -32,13 +32,22 @@ public class GenerateKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--profile", paramLabel = "PROFILE") - String profile = "default"; + String profile; @Override public void run() { GenerateKey generateKey = throwIfUnsupportedSubcommand( SopCLI.getSop().generateKey(), "generate-key"); + if (profile != null) { + try { + generateKey.profile(profile); + } catch (SOPGPException.UnsupportedProfile e) { + String errorMsg = getMsg("sop.error.usage.profile_not_supported", "generate-key", profile); + throw new SOPGPException.UnsupportedProfile(errorMsg, e); + } + } + for (String userId : userId) { generateKey.userId(userId); } @@ -47,13 +56,6 @@ public class GenerateKeyCmd extends AbstractSopCmd { generateKey.noArmor(); } - try { - generateKey.profile(profile); - } catch (SOPGPException.UnsupportedProfile e) { - String errorMsg = getMsg("sop.error.usage.profile_not_supported", "generate-key", profile); - throw new SOPGPException.UnsupportedProfile(errorMsg, e); - } - if (withKeyPassword != null) { try { String password = stringFromInputStream(getInput(withKeyPassword)); From 64c0fb11bcf68b657c21579c61cb70ef7470a23f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:03:18 +0200 Subject: [PATCH 133/444] ListProfilesCmd: Replace System.out.println() with Print.outln() --- .../main/java/sop/cli/picocli/commands/ListProfilesCmd.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java index be271e6..5b7aa4c 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java @@ -6,6 +6,7 @@ package sop.cli.picocli.commands; import picocli.CommandLine; import sop.Profile; +import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; import sop.operation.ListProfiles; @@ -25,9 +26,7 @@ public class ListProfilesCmd extends AbstractSopCmd { try { for (Profile profile : listProfiles.subcommand(subcommand)) { - // CHECKSTYLE:OFF - System.out.println(profile); - // CHECKSTYLE:ON + Print.outln(profile.toString()); } } catch (SOPGPException.UnsupportedProfile e) { String errorMsg = getMsg("sop.error.feature_support.subcommand_does_not_support_profiles", subcommand); From 9bc391fc7c946a94b4818e4ad6bd301937617ad0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:07:54 +0200 Subject: [PATCH 134/444] Add note discouraging reuse of subcommand objects --- sop-java/src/main/java/sop/SOP.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 7ce617a..7b87a30 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -20,6 +20,11 @@ import sop.operation.Version; /** * Stateless OpenPGP Interface. + * This class provides a stateless interface to various OpenPGP related operations. + *
+ * Note: Subcommand objects acquired by calling any method of this interface are not intended for reuse. + * If you for example need to generate multiple keys, make a dedicated call to {@link #generateKey()} once per + * key generation. */ public interface SOP { From 7743f15e7248a5a262a66ce8f0749a40c14f233d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:10:30 +0200 Subject: [PATCH 135/444] ListProfilesExternal: make toProfiles() method static --- .../main/java/sop/external/operation/ListProfilesExternal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java index b9df0f3..adc0e11 100644 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -35,7 +35,7 @@ public class ListProfilesExternal implements ListProfiles { } } - private List toProfiles(String output) { + private static List toProfiles(String output) { List profiles = new ArrayList<>(); for (String line : output.split("\n")) { String[] split = line.split(": "); From 6ec62560ba3474662239f24c1bb7a8c3e6081d2b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:13:36 +0200 Subject: [PATCH 136/444] Add comment about why --as=clearsigned and --no-armor are incompatible --- .../src/main/java/sop/cli/picocli/commands/InlineSignCmd.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 7e7c8af..773e6af 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -41,6 +41,7 @@ public class InlineSignCmd extends AbstractSopCmd { InlineSign inlineSign = throwIfUnsupportedSubcommand( SopCLI.getSop().inlineSign(), "inline-sign"); + // Clearsigned messages are inherently armored, so --no-armor makes no sense. if (!armor && type == InlineSignAs.clearsigned) { String errorMsg = getMsg("sop.error.usage.incompatible_options.clearsigned_no_armor"); throw new SOPGPException.IncompatibleOptions(errorMsg); From 5d2f87eb80ebea2ca005fdb161d7140e7e446356 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:16:13 +0200 Subject: [PATCH 137/444] msg_sop.properties: Add newline at the end --- sop-java-picocli/src/main/resources/msg_sop.properties | 2 +- sop-java-picocli/src/main/resources/msg_sop_de.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 6ddcf9c..b41d314 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -80,4 +80,4 @@ sop.error.usage.incompatible_options.clearsigned_no_armor=Options '--no-armor' a # Feature Support sop.error.feature_support.subcommand_not_supported=Subcommand '%s' is not supported. sop.error.feature_support.option_not_supported=Option '%s' not supported. -sop.error.feature_support.subcommand_does_not_support_profiles=Subcommand '%s' does not support any profiles. \ No newline at end of file +sop.error.feature_support.subcommand_does_not_support_profiles=Subcommand '%s' does not support any profiles. diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index a20a11d..5900f39 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -79,4 +79,4 @@ sop.error.usage.incompatible_options.clearsigned_no_armor=Optionen '--no-armor' # Feature Support sop.error.feature_support.subcommand_not_supported=Unterbefehl '%s' wird nicht unterstützt. sop.error.feature_support.option_not_supported=Option '%s' wird nicht unterstützt. -sop.error.feature_support.subcommand_does_not_support_profiles=Unterbefehl '%s' unterstützt keine Profile. \ No newline at end of file +sop.error.feature_support.subcommand_does_not_support_profiles=Unterbefehl '%s' unterstützt keine Profile. From 67292864b3a93054ea103b71fb758ba9462bf7e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:39:36 +0200 Subject: [PATCH 138/444] Add documentation to Profile class --- sop-java/src/main/java/sop/Profile.java | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java index 4a9b5b9..801f748 100644 --- a/sop-java/src/main/java/sop/Profile.java +++ b/sop-java/src/main/java/sop/Profile.java @@ -4,6 +4,8 @@ package sop; +import java.nio.charset.Charset; + /** * Tuple class bundling a profile name and description. * @@ -15,20 +17,66 @@ public class Profile { private final String name; private final String description; + /** + * Create a new {@link Profile} object. + * The {@link #toString()} representation MUST NOT exceed a length of 1000 bytes. + * + * @param name profile name + * @param description profile description + */ public Profile(String name, String description) { this.name = name; this.description = description; + + if (exceeds1000CharLineLimit(this)) { + throw new IllegalArgumentException("The line representation of a profile MUST NOT exceed 1000 bytes."); + } } + /** + * Return the name (also known as identifier) of the profile. + * A profile name is a UTF-8 string that has no whitespace in it. + * Similar to OpenPGP Notation names, profile names are divided into two namespaces: + * The IETF namespace and the user namespace. + * A profile name in the user namespace ends with the
@
character (0x40) followed by a DNS domain name. + * A profile name in the IETF namespace does not have an
@
character. + * A profile name in the user space is owned and controlled by the owner of the domain in the suffix. + * A profile name in the IETF namespace that begins with the string
rfc
should have semantics that hew as + * closely as possible to the referenced RFC. + * Similarly, a profile name in the IETF namespace that begins with the string
draft-
should have + * semantics that hew as closely as possible to the referenced Internet Draft. + * + * @return name + */ public String getName() { return name; } + /** + * Return a free-form description of the profile. + * + * @return description + */ public String getDescription() { return description; } + /** + * Convert the profile into a String for displaying. + * + * @return string + */ public String toString() { return getName() + ": " + getDescription(); } + + /** + * Test if the string representation of the profile exceeds the limit of 1000 bytes length. + * @param profile profile + * @return
true
if the profile exceeds 1000 bytes,
false
otherwise. + */ + private static boolean exceeds1000CharLineLimit(Profile profile) { + String line = profile.toString(); + return line.getBytes(Charset.forName("UTF8")).length > 1000; + } } From ffdd5eee51c68cb80b75924d0ae7bd8784ca892d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:41:03 +0200 Subject: [PATCH 139/444] Document SOP.listProfiles() --- sop-java/src/main/java/sop/SOP.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 7b87a30..c74c6cd 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -152,5 +152,10 @@ public interface SOP { */ Dearmor dearmor(); + /** + * List supported {@link Profile Profiles} of a subcommand. + * + * @return builder instance + */ ListProfiles listProfiles(); } From d838e9589b8ea45a48f7d64549feecffd1415421 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:51:05 +0200 Subject: [PATCH 140/444] Add documentation to Verification --- sop-java/src/main/java/sop/Verification.java | 41 +++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index 781af6f..f18826e 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -9,6 +9,9 @@ import java.util.Date; import sop.enums.SignatureMode; import sop.util.UTCUtil; +/** + * Class bundling information about a verified signature. + */ public class Verification { private final Date creationTime; @@ -17,13 +20,28 @@ public class Verification { private final SignatureMode signatureMode; private final String description; - public static final String MODE = "mode:"; - + private static final String MODE = "mode:"; + /** + * Create a new {@link Verification} without mode and description. + * + * @param creationTime signature creation time + * @param signingKeyFingerprint fingerprint of the signing (sub-) key + * @param signingCertFingerprint fingerprint of the certificate + */ public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) { this(creationTime, signingKeyFingerprint, signingCertFingerprint, null, null); } + /** + * Create a new {@link Verification}. + * + * @param creationTime signature creation time + * @param signingKeyFingerprint fingerprint of the signing (sub-) key + * @param signingCertFingerprint fingerprint of the certificate + * @param signatureMode signature mode (optional, may be
null
) + * @param description free-form description, e.g.
certificate from dkg.asc
(optional, may be
null
) + */ public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, SignatureMode signatureMode, String description) { this.creationTime = creationTime; this.signingKeyFingerprint = signingKeyFingerprint; @@ -35,13 +53,14 @@ public class Verification { public static Verification fromString(String toString) { String[] split = toString.trim().split(" "); if (split.length < 3) { - throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint'"); + throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'"); } if (split.length == 3) { - return new Verification(UTCUtil.parseUTCDate(split[0]), - split[1], // key FP - split[2] // cert FP + return new Verification( + UTCUtil.parseUTCDate(split[0]), // timestamp + split[1], // key FP + split[2] // cert FP ); } @@ -61,10 +80,10 @@ public class Verification { } return new Verification( - UTCUtil.parseUTCDate(split[0]), - split[1], // key FP - split[2], // cert FP - mode, + UTCUtil.parseUTCDate(split[0]), // timestamp + split[1], // key FP + split[2], // cert FP + mode, // signature mode sb.length() != 0 ? sb.toString() : null // description ); } @@ -98,6 +117,7 @@ public class Verification { /** * Return the mode of the signature. + * Optional, may return
null
. * * @return signature mode */ @@ -107,6 +127,7 @@ public class Verification { /** * Return an optional description. + * Optional, may return
null
. * * @return description */ From 360f2fba024ac3177f070006e8c811086c45e313 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 13:56:43 +0200 Subject: [PATCH 141/444] Document SignatureMode --- .../src/main/java/sop/enums/SignatureMode.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sop-java/src/main/java/sop/enums/SignatureMode.java b/sop-java/src/main/java/sop/enums/SignatureMode.java index b41f6ce..71ce7d8 100644 --- a/sop-java/src/main/java/sop/enums/SignatureMode.java +++ b/sop-java/src/main/java/sop/enums/SignatureMode.java @@ -4,7 +4,22 @@ package sop.enums; +/** + * Enum referencing relevant signature types. + * + * @see + * RFC4880 §5.2.1 - Signature Types + */ public enum SignatureMode { + /** + * Signature of a binary document (
0x00
). + */ binary, + + /** + * Signature of a canonical text document (
0x01
). + */ text + + // Other Signature Types are irrelevant. } From 7e12da400b4f0c258223119e244f43795799b4b3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:01:49 +0200 Subject: [PATCH 142/444] Add documentation to new exception types --- .../java/sop/exception/SOPGPException.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 1618a58..f044fa6 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -379,6 +379,9 @@ public abstract class SOPGPException extends RuntimeException { } } + /** + * User provided incompatible options (e.g. "--as=clearsigned --no-armor"). + */ public static class IncompatibleOptions extends SOPGPException { public static final int EXIT_CODE = 83; @@ -397,6 +400,10 @@ public abstract class SOPGPException extends RuntimeException { } } + /** + * The user provided a subcommand with an unsupported profile ("--profile=XYZ"), + * or the user tried to list profiles of a subcommand that does not support profiles at all. + */ public static class UnsupportedProfile extends SOPGPException { public static final int EXIT_CODE = 89; @@ -404,28 +411,56 @@ public abstract class SOPGPException extends RuntimeException { private final String subcommand; private final String profile; + /** + * Create an exception signalling a subcommand that does not support any profiles. + * + * @param subcommand subcommand + */ public UnsupportedProfile(String subcommand) { super("Subcommand '" + subcommand + "' does not support any profiles."); this.subcommand = subcommand; this.profile = null; } + /** + * Create an exception signalling a subcommand does not support a specific profile. + * + * @param subcommand subcommand + * @param profile unsupported profile + */ public UnsupportedProfile(String subcommand, String profile) { super("Subcommand '" + subcommand + "' does not support profile '" + profile + "'."); this.subcommand = subcommand; this.profile = profile; } + /** + * Wrap an exception into another instance with a possibly translated error message. + * + * @param errorMsg error message + * @param e exception + */ public UnsupportedProfile(String errorMsg, UnsupportedProfile e) { super(errorMsg, e); this.subcommand = e.getSubcommand(); this.profile = e.getProfile(); } + /** + * Return the subcommand name. + * + * @return subcommand + */ public String getSubcommand() { return subcommand; } + /** + * Return the profile name. + * May return
null
. + * + * @return profile name + */ public String getProfile() { return profile; } From 8a9f53553145d510e1478fe299715dd1f6663929 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:04:06 +0200 Subject: [PATCH 143/444] Add documentation to ListProfiles command --- sop-java/src/main/java/sop/operation/ListProfiles.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java index 87bb3b3..5514649 100644 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ b/sop-java/src/main/java/sop/operation/ListProfiles.java @@ -8,8 +8,18 @@ import sop.Profile; import java.util.List; +/** + * Subcommand to list supported profiles of other subcommands. + */ public interface ListProfiles { + /** + * Provide the name of the subcommand for which profiles shall be listed. + * The returned list of profiles MUST NOT contain more than 4 entries. + * + * @param command command name (e.g.
generate-key
) + * @return list of profiles. + */ List subcommand(String command); } From a3bff6f6d1566c9f3bf295e2e2809db0998aefb9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:12:01 +0200 Subject: [PATCH 144/444] Fix checkstyle issues --- .../src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java index 5b7aa4c..46464f8 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java @@ -16,7 +16,7 @@ import sop.operation.ListProfiles; exitCodeOnInvalidInput = 37) public class ListProfilesCmd extends AbstractSopCmd { - @CommandLine.Parameters(paramLabel = "COMMAND", arity="1", descriptionKey = "subcommand") + @CommandLine.Parameters(paramLabel = "COMMAND", arity = "1", descriptionKey = "subcommand") String subcommand; @Override From 146f24eab88f4ec81adcd61d52a56bc5b6150c25 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:21:34 +0200 Subject: [PATCH 145/444] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd66534..1bd481e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 5.0.0 +- Update implementation to [SOP Specification revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html). + - Add the concept of profiles + - Add `list-profiles` subcommand + - Add option `--profile=XYZ` to `generate-key` subcommand + - `Verification` objects can now optionally indicate the type of the signature (`mode:text` or `mode:binary`) + - `Verification` objects can now contain an optional description of the signature + - `inline-sign` now throws an error if incompatible options `--as=clearsigned` and `--no-armor` are used + ## 4.1.1 - Restructure test suite to allow simultaneous testing of multiple backends - Fix IOException in `sop sign` due to premature stream closing From 3f4ec072a94fcc860bde0d95e497566ee446d14d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:39:23 +0200 Subject: [PATCH 146/444] SOP-Java 5.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index fce24ff..bf632fc 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '5.0.0' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 42bd8f06a410358c79ed24a1cf33cbfc73fb98a9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:41:39 +0200 Subject: [PATCH 147/444] SOP-Java 5.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index bf632fc..3088539 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '5.0.0' - isSnapshot = false + shortVersion = '5.0.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 925505989e10cdf8aa3defe74d2adb269efd8424 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:22:26 +0200 Subject: [PATCH 148/444] Bump version to 6.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 3088539..7202f0a 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '5.0.1' + shortVersion = '6.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From dfce1ad6bbaa5d11dc8dd9793e527579e8ba2535 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:23:04 +0200 Subject: [PATCH 149/444] Bump SOP spec to 06 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a4ad20..d130a48 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 5](https://img.shields.io/badge/Spec%20Revision-5-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/05/) +[![Spec Revision: 6](https://img.shields.io/badge/Spec%20Revision-6-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/06/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) From f49c16e4c5a28564312a5c912ec26e3009a5bae9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:41:37 +0200 Subject: [PATCH 150/444] Implement sop version --sop-spec --- .../external/operation/VersionExternal.java | 19 +++++++++++++++++++ .../sop/cli/picocli/commands/VersionCmd.java | 8 ++++++++ .../src/main/java/sop/operation/Version.java | 10 ++++++++++ .../sop/testsuite/operation/VersionTest.java | 8 ++++++++ 4 files changed, 45 insertions(+) diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index 6848cab..95dc8f5 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -99,4 +99,23 @@ public class VersionExternal implements Version { throw new RuntimeException(e); } } + + @Override + public String getSopSpecVersion() { + String[] command = new String[] {binary, "version", "--sop-spec"}; + String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); + try { + Process process = runtime.exec(command, env); + BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = stdInput.readLine()) != null) { + sb.append(line).append('\n'); + } + ExternalSOP.finish(process); + return sb.toString(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java index 3610ff4..c11d050 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java @@ -22,6 +22,9 @@ public class VersionCmd extends AbstractSopCmd { @CommandLine.Option(names = "--backend") boolean backend; + + @CommandLine.Option(names = "--sop-spec") + boolean sopSpec; } @@ -45,5 +48,10 @@ public class VersionCmd extends AbstractSopCmd { Print.outln(version.getBackendVersion()); return; } + + if (exclusive.sopSpec) { + Print.outln(version.getSopSpecVersion()); + return; + } } } diff --git a/sop-java/src/main/java/sop/operation/Version.java b/sop-java/src/main/java/sop/operation/Version.java index 0b50993..1cd2698 100644 --- a/sop-java/src/main/java/sop/operation/Version.java +++ b/sop-java/src/main/java/sop/operation/Version.java @@ -46,4 +46,14 @@ public interface Version { * @return extended version string */ String getExtendedVersion(); + + /** + * Return the revision of the SOP specification that this implementation is implementing, for example, + *
draft-dkg-openpgp-stateless-cli-06
. + * If the implementation targets a specific draft but the implementer knows the implementation is incomplete, + * it should prefix the draft title with a "~" (TILDE, U+007E), for example:
~draft-dkg-openpgp-stateless-cli-06
. + * + * @return implemented SOP spec version + */ + String getSopSpecVersion(); } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 16aff4d..5ad1a52 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -14,6 +14,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class VersionTest extends AbstractSOPTest { @@ -51,4 +52,11 @@ public class VersionTest extends AbstractSOPTest { assertFalse(extended.isEmpty()); } + @ParameterizedTest + @MethodSource("provideInstances") + public void sopSpecVersionTest(SOP sop) { + String sopSpec = sop.version().getSopSpecVersion(); + assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-") || + sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); + } } From f4ff5f89f73f987082a4d4f7e6b721e11e725b7a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:48:58 +0200 Subject: [PATCH 151/444] i18n for sop version --sop-spec --- sop-java-picocli/src/main/resources/msg_version.properties | 1 + sop-java-picocli/src/main/resources/msg_version_de.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index c081705..a4c75ca 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -4,6 +4,7 @@ usage.header=Display version information about the tool extended=Print an extended version string backend=Print information about the cryptographic backend +sop-spec=Print the latest revision of the SOP specification targeted by the implementation # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index c22d91f..fe460e9 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -4,6 +4,7 @@ usage.header=Zeige Versionsinformationen über das Programm extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus +sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird. # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 From 8a66f0bc4f88a1a647471a5a32c5dfe458c4179c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:04:51 +0200 Subject: [PATCH 152/444] Implement sop encrypt --profile=XXX --- .../external/operation/EncryptExternal.java | 6 ++++++ .../sop/cli/picocli/commands/EncryptCmd.java | 13 +++++++++++++ .../src/main/java/sop/operation/Encrypt.java | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index ca8ddf1..1a7208e 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -85,6 +85,12 @@ public class EncryptExternal implements Encrypt { return this; } + @Override + public Encrypt profile(String profileName) { + commandList.add("--profile=" + profileName); + return this; + } + @Override public Ready plaintext(InputStream plaintext) throws IOException, SOPGPException.KeyIsProtected { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index 20b0779..079f9d8 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java @@ -41,6 +41,10 @@ public class EncryptCmd extends AbstractSopCmd { paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); + @CommandLine.Option(names = "--profile", + paramLabel = "PROFILE") + String profile; + @CommandLine.Parameters(index = "0..*", paramLabel = "CERTS") List certs = new ArrayList<>(); @@ -50,6 +54,15 @@ public class EncryptCmd extends AbstractSopCmd { Encrypt encrypt = throwIfUnsupportedSubcommand( SopCLI.getSop().encrypt(), "encrypt"); + if (profile != null) { + try { + encrypt.profile(profile); + } catch (SOPGPException.UnsupportedProfile e) { + String errorMsg = getMsg("sop.error.usage.profile_not_supported", "encrypt", profile); + throw new SOPGPException.UnsupportedProfile(errorMsg, e); + } + } + if (type != null) { try { encrypt.mode(type); diff --git a/sop-java/src/main/java/sop/operation/Encrypt.java b/sop-java/src/main/java/sop/operation/Encrypt.java index 09e5f12..fed1210 100644 --- a/sop-java/src/main/java/sop/operation/Encrypt.java +++ b/sop-java/src/main/java/sop/operation/Encrypt.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import sop.Profile; import sop.Ready; import sop.enums.EncryptAs; import sop.exception.SOPGPException; @@ -146,6 +147,24 @@ public interface Encrypt { return withCert(new ByteArrayInputStream(cert)); } + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + default Encrypt profile(Profile profile) { + return profile(profile.getName()); + } + + /** + * Pass in a profile identifier. + * + * @param profileName profile identifier + * @return builder instance + */ + Encrypt profile(String profileName); + /** * Encrypt the given data yielding the ciphertext. * @param plaintext plaintext From 90c77706a858114521c545f71989e142e66e58c9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:07:43 +0200 Subject: [PATCH 153/444] Add i18n for profile option --- sop-java-picocli/src/main/resources/msg_encrypt.properties | 1 + sop-java-picocli/src/main/resources/msg_encrypt_de.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index 4b59551..c04ce32 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -7,6 +7,7 @@ as=Type of the input data. Defaults to 'binary' with-password.0=Encrypt the message with a password. with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) sign-with=Sign the output with a private key +profile=Profile identifier to switch between profiles with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). CERTS[0..*]=Certificates the message gets encrypted to diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index f2fd356..c2ae6ef 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -7,6 +7,7 @@ as=Format der Nachricht. Standardm with-password.0=Verschlüssle die Nachricht mit einem Passwort with-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). sign-with=Signiere die Nachricht mit einem privaten Schlüssel +profile=Profil-Identifikator um zwischen Profilen zu wechseln with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll From 8425665fa7f5a954002325d7a68fc72882722fdc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:56:52 +0200 Subject: [PATCH 154/444] Expose SOP revision info more fine-grained --- .../external/operation/VersionExternal.java | 36 +++++++++++++ .../src/main/java/sop/operation/Version.java | 53 ++++++++++++++++++- .../sop/testsuite/operation/VersionTest.java | 11 +++- 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index 95dc8f5..3b21792 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -100,6 +100,42 @@ public class VersionExternal implements Version { } } + @Override + public int getSopSpecVersionNumber() { + String revision = getSopSpecVersion(); + String firstLine; + if (revision.contains("\n")) { + firstLine = revision.substring(0, revision.indexOf("\n")); + } else { + firstLine = revision; + } + + if (!firstLine.contains("-")) { + return -1; + } + + return Integer.parseInt(firstLine.substring(firstLine.lastIndexOf("-") + 1)); + } + + @Override + public boolean isSopSpecImplementationIncomplete() { + String revision = getSopSpecVersion(); + return revision.startsWith("~"); + } + + @Override + public String getSopSpecImplementationIncompletenessRemarks() { + String revision = getSopSpecVersion(); + if (revision.contains("\n")) { + String tail = revision.substring(revision.indexOf("\n") + 1).trim(); + + if (!tail.isEmpty()) { + return tail; + } + } + return null; + } + @Override public String getSopSpecVersion() { String[] command = new String[] {binary, "version", "--sop-spec"}; diff --git a/sop-java/src/main/java/sop/operation/Version.java b/sop-java/src/main/java/sop/operation/Version.java index 1cd2698..95b2fe1 100644 --- a/sop-java/src/main/java/sop/operation/Version.java +++ b/sop-java/src/main/java/sop/operation/Version.java @@ -47,13 +47,62 @@ public interface Version { */ String getExtendedVersion(); + /** + * Return the version number of the latest targeted SOP spec revision. + * + * @return SOP spec revision number + */ + int getSopSpecVersionNumber(); + + /** + * Return the latest targeted revision of the SOP spec. + * + * @return SOP spec revision string + */ + default String getSopSpecRevisionString() { + return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecVersionNumber()); + } + + /** + * Return
true
, if this implementation of the SOP spec is known to be incomplete or defective. + * + * @return true if incomplete, false otherwise + */ + boolean isSopSpecImplementationIncomplete(); + + /** + * Return free-form text containing remarks about the completeness of the SOP implementation. + * If no remarks are known, this method returns
null
. + * + * @return remarks or null + */ + String getSopSpecImplementationIncompletenessRemarks(); + /** * Return the revision of the SOP specification that this implementation is implementing, for example, *
draft-dkg-openpgp-stateless-cli-06
. * If the implementation targets a specific draft but the implementer knows the implementation is incomplete, - * it should prefix the draft title with a "~" (TILDE, U+007E), for example:
~draft-dkg-openpgp-stateless-cli-06
. + * it should prefix the draft title with a "~" (TILDE, U+007E), for example: + *
~draft-dkg-openpgp-stateless-cli-06
. + * The implementation MAY emit additional text about its relationship to the targeted draft on the lines following + * the versioned title. * * @return implemented SOP spec version */ - String getSopSpecVersion(); + default String getSopSpecVersion() { + StringBuilder sb = new StringBuilder(); + if (isSopSpecImplementationIncomplete()) { + sb.append('~'); + } + + sb.append(getSopSpecRevisionString()); + + if (getSopSpecImplementationIncompletenessRemarks() != null) { + sb.append('\n') + .append('\n') + .append(getSopSpecImplementationIncompletenessRemarks()); + } + + return sb.toString(); + } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 5ad1a52..8dc958e 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -14,6 +14,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @@ -56,7 +57,13 @@ public class VersionTest extends AbstractSOPTest { @MethodSource("provideInstances") public void sopSpecVersionTest(SOP sop) { String sopSpec = sop.version().getSopSpecVersion(); - assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-") || - sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); + if (sop.version().isSopSpecImplementationIncomplete()) { + assertTrue(sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); + } else { + assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-")); + } + + int sopRevision = sop.version().getSopSpecVersionNumber(); + assertTrue(sop.version().getSopSpecRevisionString().endsWith("" + sopRevision)); } } From 1a4affde3561cb2914b113b0a7931af9432a507a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 16:29:02 +0200 Subject: [PATCH 155/444] Skip sopSpecVersionTest if --sop-spec is not supported --- .../java/sop/testsuite/operation/VersionTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 8dc958e..59208dc 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -14,8 +14,8 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class VersionTest extends AbstractSOPTest { @@ -56,6 +56,12 @@ public class VersionTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void sopSpecVersionTest(SOP sop) { + try { + sop.version().getSopSpecVersion(); + } catch (RuntimeException e) { + assumeTrue(false); // SOP backend does not support this operation yet + } + String sopSpec = sop.version().getSopSpecVersion(); if (sop.version().isSopSpecImplementationIncomplete()) { assertTrue(sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); From 84a01df4bd97da2f53089cb65ebb6ac9b7238529 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:29:06 +0200 Subject: [PATCH 156/444] Rename new methods in Version --- .../external/operation/VersionExternal.java | 4 +- .../src/main/java/sop/operation/Version.java | 69 ++++++++++--------- .../sop/testsuite/operation/VersionTest.java | 4 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index 3b21792..0b9c5b4 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -101,7 +101,7 @@ public class VersionExternal implements Version { } @Override - public int getSopSpecVersionNumber() { + public int getSopSpecRevisionNumber() { String revision = getSopSpecVersion(); String firstLine; if (revision.contains("\n")) { @@ -124,7 +124,7 @@ public class VersionExternal implements Version { } @Override - public String getSopSpecImplementationIncompletenessRemarks() { + public String getSopSpecImplementationRemarks() { String revision = getSopSpecVersion(); if (revision.contains("\n")) { String tail = revision.substring(revision.indexOf("\n") + 1).trim(); diff --git a/sop-java/src/main/java/sop/operation/Version.java b/sop-java/src/main/java/sop/operation/Version.java index 95b2fe1..b6d66b9 100644 --- a/sop-java/src/main/java/sop/operation/Version.java +++ b/sop-java/src/main/java/sop/operation/Version.java @@ -47,37 +47,6 @@ public interface Version { */ String getExtendedVersion(); - /** - * Return the version number of the latest targeted SOP spec revision. - * - * @return SOP spec revision number - */ - int getSopSpecVersionNumber(); - - /** - * Return the latest targeted revision of the SOP spec. - * - * @return SOP spec revision string - */ - default String getSopSpecRevisionString() { - return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecVersionNumber()); - } - - /** - * Return
true
, if this implementation of the SOP spec is known to be incomplete or defective. - * - * @return true if incomplete, false otherwise - */ - boolean isSopSpecImplementationIncomplete(); - - /** - * Return free-form text containing remarks about the completeness of the SOP implementation. - * If no remarks are known, this method returns
null
. - * - * @return remarks or null - */ - String getSopSpecImplementationIncompletenessRemarks(); - /** * Return the revision of the SOP specification that this implementation is implementing, for example, *
draft-dkg-openpgp-stateless-cli-06
. @@ -95,14 +64,46 @@ public interface Version { sb.append('~'); } - sb.append(getSopSpecRevisionString()); + sb.append(getSopSpecRevisionName()); - if (getSopSpecImplementationIncompletenessRemarks() != null) { + if (getSopSpecImplementationRemarks() != null) { sb.append('\n') .append('\n') - .append(getSopSpecImplementationIncompletenessRemarks()); + .append(getSopSpecImplementationRemarks()); } return sb.toString(); } + + /** + * Return the version number of the latest targeted SOP spec revision. + * + * @return SOP spec revision number + */ + int getSopSpecRevisionNumber(); + + /** + * Return the name of the latest targeted revision of the SOP spec. + * + * @return SOP spec revision string + */ + default String getSopSpecRevisionName() { + return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecRevisionNumber()); + } + + /** + * Return
true
, if this implementation of the SOP spec is known to be incomplete or defective. + * + * @return true if incomplete, false otherwise + */ + boolean isSopSpecImplementationIncomplete(); + + /** + * Return free-form text containing remarks about the completeness of the SOP implementation. + * If there are no remarks, this method returns
null
. + * + * @return remarks or null + */ + String getSopSpecImplementationRemarks(); + } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 59208dc..73ba571 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -69,7 +69,7 @@ public class VersionTest extends AbstractSOPTest { assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-")); } - int sopRevision = sop.version().getSopSpecVersionNumber(); - assertTrue(sop.version().getSopSpecRevisionString().endsWith("" + sopRevision)); + int sopRevision = sop.version().getSopSpecRevisionNumber(); + assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision)); } } From 415fc3dd3a02a2d7ec1cd554710ccd1e2e11d1a2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:43:01 +0200 Subject: [PATCH 157/444] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd481e..96d90e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 6.0.0 +- Update implementation to [SOP Specification revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html). + - Add option `--profile=XYZ` to `encrypt` subcommand + - Add option `--sop-spec` to `version` subcommand + - `Version`: Add different getters for specification-related values + ## 5.0.0 - Update implementation to [SOP Specification revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html). - Add the concept of profiles From 41260bb02c74c77b2fbb88627e8c9238b0048eab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:45:37 +0200 Subject: [PATCH 158/444] SOP-Java 6.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 7202f0a..b5ddf19 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '6.0.0' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 51a7d950e5f385487410162b41bb6179bf1de061 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:56:06 +0200 Subject: [PATCH 159/444] SOP-Java 6.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index b5ddf19..095db6c 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '6.0.0' - isSnapshot = false + shortVersion = '6.0.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From d8cac7b9d7c6fed53c926ba383ac51f471ccadc5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 17:58:47 +0200 Subject: [PATCH 160/444] external-sop: Fix error code mapping of new exceptions --- external-sop/src/main/java/sop/external/ExternalSOP.java | 8 ++++++++ .../java/sop/testsuite/operation/ListProfilesTest.java | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index f6e90a5..f22c778 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -267,6 +267,14 @@ public class ExternalSOP implements SOP { throw new SOPGPException.KeyCannotSign("External SOP backend reported error KeyCannotSign (" + exitCode + "):\n" + errorMessage); + case SOPGPException.IncompatibleOptions.EXIT_CODE: + throw new SOPGPException.IncompatibleOptions("External SOP backend reported error IncompatibleOptions (" + + exitCode + "):\n" + errorMessage); + + case SOPGPException.UnsupportedProfile.EXIT_CODE: + throw new SOPGPException.UnsupportedProfile("External SOP backend reported error UnsupportedProfile (" + + exitCode + "):\n" + errorMessage); + default: throw new RuntimeException("External SOP backend reported unknown exit code (" + exitCode + "):\n" + errorMessage); diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java index e97dd39..af60fc9 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java @@ -9,12 +9,14 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.Profile; import sop.SOP; +import sop.exception.SOPGPException; import java.io.IOException; import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; public class ListProfilesTest extends AbstractSOPTest { @@ -30,4 +32,11 @@ public class ListProfilesTest extends AbstractSOPTest { assertFalse(profiles.isEmpty()); } + @ParameterizedTest + @MethodSource("provideInstances") + public void listUnsupportedProfiles(SOP sop) throws IOException { + assertThrows(SOPGPException.UnsupportedProfile.class, () -> sop + .listProfiles() + .subcommand("invalid")); + } } From 78ecf2f554c91ae6ffcfc0b34b369b883b8582dc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:04:23 +0200 Subject: [PATCH 161/444] ListProfiles: Add shortcut methods generateKey() and encrypt() --- .../main/java/sop/operation/ListProfiles.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java index 5514649..0c17bd6 100644 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ b/sop-java/src/main/java/sop/operation/ListProfiles.java @@ -22,4 +22,22 @@ public interface ListProfiles { */ List subcommand(String command); + /** + * Return a list of {@link Profile Profiles} supported by the {@link GenerateKey} implementation. + * + * @return profiles + */ + default List generateKey() { + return subcommand("generate-key"); + } + + /** + * Return a list of {@link Profile Profiles} supported by the {@link Encrypt} implementation. + * + * @return profiles + */ + default List encrypt() { + return subcommand("encrypt"); + } + } From 4a7c2b74da19d032f378879af96a521cb7bb8f22 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:05:10 +0200 Subject: [PATCH 162/444] Add test for listing encrypt profiles, use new shortcut methods --- .../testsuite/operation/ListProfilesTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java index af60fc9..f566dd3 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java @@ -27,8 +27,20 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void listGenerateKeyProfiles(SOP sop) throws IOException { - List profiles = sop.listProfiles() - .subcommand("generate-key"); + List profiles = sop + .listProfiles() + .generateKey(); + + assertFalse(profiles.isEmpty()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void listEncryptProfiles(SOP sop) throws IOException { + List profiles = sop + .listProfiles() + .encrypt(); + assertFalse(profiles.isEmpty()); } From bb2b4e03fbcf38b333b298affafc9e109027d62b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:15:11 +0200 Subject: [PATCH 163/444] Add comment to remind future self of adding new exception types to switch-case --- external-sop/src/main/java/sop/external/ExternalSOP.java | 1 + 1 file changed, 1 insertion(+) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index f22c778..bc5eb46 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -276,6 +276,7 @@ public class ExternalSOP implements SOP { exitCode + "):\n" + errorMessage); default: + // Did you forget to add a case for a new exception type? throw new RuntimeException("External SOP backend reported unknown exit code (" + exitCode + "):\n" + errorMessage); } From aeda534f379d1e2ce4867c6de72829572a4fde23 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 15:52:53 +0200 Subject: [PATCH 164/444] Add DSL for testing verification results --- .../test/java/sop/util/VerificationTest.java | 39 +++++++- .../java/sop/testsuite/JUtils.java | 65 +------------ .../assertions/VerificationAssert.java | 71 ++++++++++++++ .../assertions/VerificationListAssert.java | 70 ++++++++++++++ .../testsuite/assertions/package-info.java | 8 ++ .../DetachedSignDetachedVerifyTest.java | 96 +++++++++++++++---- .../operation/EncryptDecryptTest.java | 29 ++++-- .../operation/InlineSignInlineVerifyTest.java | 43 +++++++-- 8 files changed, 326 insertions(+), 95 deletions(-) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java diff --git a/sop-java/src/test/java/sop/util/VerificationTest.java b/sop-java/src/test/java/sop/util/VerificationTest.java index 3688d6d..b292688 100644 --- a/sop-java/src/test/java/sop/util/VerificationTest.java +++ b/sop-java/src/test/java/sop/util/VerificationTest.java @@ -7,6 +7,7 @@ package sop.util; import org.junit.jupiter.api.Test; import sop.Verification; import sop.enums.SignatureMode; +import sop.testsuite.assertions.VerificationAssert; import java.util.Date; @@ -21,12 +22,37 @@ public class VerificationTest { String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; Verification verification = new Verification(signDate, keyFP, certFP); assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString()); + + VerificationAssert.assertThatVerification(verification) + .issuedBy(certFP) + .isBySigningKey(keyFP) + .isCreatedAt(signDate) + .hasMode(null) + .hasDescription(null); } + @Test public void limitedParsingTest() { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); + VerificationAssert.assertThatVerification(verification) + .isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z")) + .issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B") + .hasMode(null) + .hasDescription(null); + } + + @Test + public void parsingWithModeTest() { + String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:text"; + Verification verification = Verification.fromString(string); + assertEquals(string, verification.toString()); + VerificationAssert.assertThatVerification(verification) + .isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z")) + .issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B") + .hasMode(SignatureMode.text) + .hasDescription(null); } @Test @@ -37,9 +63,13 @@ public class VerificationTest { SignatureMode mode = SignatureMode.binary; String description = "certificate from dkg.asc"; Verification verification = new Verification(signDate, keyFP, certFP, mode, description); + assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc", verification.toString()); - assertEquals(SignatureMode.binary, verification.getSignatureMode()); - assertEquals(description, verification.getDescription()); + VerificationAssert.assertThatVerification(verification) + .isCreatedAt(signDate) + .issuedBy(keyFP, certFP) + .hasMode(SignatureMode.binary) + .hasDescription(description); } @Test @@ -52,5 +82,10 @@ public class VerificationTest { string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B certificate from dkg.asc"; verification = Verification.fromString(string); assertEquals(string, verification.toString()); + VerificationAssert.assertThatVerification(verification) + .isCreatedAt(UTCUtil.parseUTCDate("2022-11-07T15:01:24Z")) + .issuedBy("F9E6F53F7201C60A87064EAB0B27F2B0760A1209", "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B") + .hasMode(null) + .hasDescription("certificate from dkg.asc"); } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java b/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java index c30d86b..5302192 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java @@ -4,15 +4,14 @@ package sop.testsuite; -import sop.Verification; import sop.util.UTCUtil; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Date; -import java.util.List; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; public class JUtils { @@ -157,66 +156,12 @@ public class JUtils { return string.getBytes(StandardCharsets.UTF_8); } - public static void assertSignedBy(List verifications, String primaryFingerprint) { - for (Verification verification : verifications) { - if (verification.getSigningCertFingerprint().equals(primaryFingerprint)) { - return; - } - } - - if (verifications.isEmpty()) { - fail("Verification list is empty."); - } - - fail("Verification list does not contain verification by cert " + primaryFingerprint + ":\n" + - Arrays.toString(verifications.toArray(new Verification[0]))); + public static void assertDateEquals(Date expected, Date actual) { + assertEquals(UTCUtil.formatUTCDate(expected), UTCUtil.formatUTCDate(actual)); } - public static void assertSignedBy(List verifications, String signingFingerprint, String primaryFingerprint) { - for (Verification verification : verifications) { - if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && verification.getSigningKeyFingerprint().equals(signingFingerprint)) { - return; - } - } - - if (verifications.isEmpty()) { - fail("Verification list is empty."); - } - - fail("Verification list does not contain verification by key " + signingFingerprint + " on cert " + primaryFingerprint + ":\n" + - Arrays.toString(verifications.toArray(new Verification[0]))); + public static boolean dateEquals(Date expected, Date actual) { + return UTCUtil.formatUTCDate(expected).equals(UTCUtil.formatUTCDate(actual)); } - public static void assertSignedBy(List verifications, String primaryFingerprint, Date signatureDate) { - for (Verification verification : verifications) { - if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && - verification.getCreationTime().equals(signatureDate)) { - return; - } - } - - if (verifications.isEmpty()) { - fail("Verification list is empty."); - } - - fail("Verification list does not contain verification by cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" + - Arrays.toString(verifications.toArray(new Verification[0]))); - } - - public static void assertSignedBy(List verifications, String signingFingerprint, String primaryFingerprint, Date signatureDate) { - for (Verification verification : verifications) { - if (verification.getSigningCertFingerprint().equals(primaryFingerprint) && - verification.getSigningKeyFingerprint().equals(signingFingerprint) && - verification.getCreationTime().equals(signatureDate)) { - return; - } - } - - if (verifications.isEmpty()) { - fail("Verification list is empty."); - } - - fail("Verification list does not contain verification by key" + signingFingerprint + " on cert " + primaryFingerprint + " made at " + UTCUtil.formatUTCDate(signatureDate) + ":\n" + - Arrays.toString(verifications.toArray(new Verification[0]))); - } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java new file mode 100644 index 0000000..35e3ed6 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.assertions; + +import sop.Verification; +import sop.enums.SignatureMode; +import sop.testsuite.JUtils; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class VerificationAssert { + + private final Verification verification; + + public static VerificationAssert assertThatVerification(Verification verification) { + return new VerificationAssert(verification); + } + + private VerificationAssert(Verification verification) { + this.verification = verification; + } + + public VerificationAssert issuedBy(String signingKeyFingerprint, String primaryFingerprint) { + return isBySigningKey(signingKeyFingerprint) + .issuedBy(primaryFingerprint); + } + + public VerificationAssert issuedBy(String primaryFingerprint) { + assertEquals(primaryFingerprint, verification.getSigningCertFingerprint()); + return this; + } + + public VerificationAssert isBySigningKey(String signingKeyFingerprint) { + assertEquals(signingKeyFingerprint, verification.getSigningKeyFingerprint()); + return this; + } + + public VerificationAssert isCreatedAt(Date creationDate) { + JUtils.assertDateEquals(creationDate, verification.getCreationTime()); + return this; + } + + public VerificationAssert hasDescription(String description) { + assertEquals(description, verification.getDescription()); + return this; + } + + public VerificationAssert hasDescriptionOrNull(String description) { + if (verification.getDescription() == null) { + return this; + } + + return hasDescription(description); + } + + public VerificationAssert hasMode(SignatureMode mode) { + assertEquals(mode, verification.getSignatureMode()); + return this; + } + + public VerificationAssert hasModeOrNull(SignatureMode mode) { + if (verification.getSignatureMode() == null) { + return this; + } + return hasMode(mode); + } +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java new file mode 100644 index 0000000..6c90609 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.assertions; + +import sop.Verification; + +import java.util.ArrayList; +import java.util.List; + +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 static org.junit.jupiter.api.Assertions.fail; + +public final class VerificationListAssert { + + private final List verificationList = new ArrayList<>(); + + private VerificationListAssert(List verifications) { + this.verificationList.addAll(verifications); + } + + public static VerificationListAssert assertThatVerificationList(List verifications) { + return new VerificationListAssert(verifications); + } + + public VerificationListAssert isEmpty() { + assertTrue(verificationList.isEmpty()); + return this; + } + + public VerificationListAssert isNotEmpty() { + assertFalse(verificationList.isEmpty()); + return this; + } + + public VerificationListAssert sizeEquals(int size) { + assertEquals(size, verificationList.size()); + return this; + } + + public VerificationAssert hasSingleItem() { + sizeEquals(1); + return VerificationAssert.assertThatVerification(verificationList.get(0)); + } + + public VerificationListAssert containsVerificationByCert(String primaryFingerprint) { + for (Verification verification : verificationList) { + if (primaryFingerprint.equals(verification.getSigningCertFingerprint())) { + return this; + } + } + fail("No verification was issued by certificate " + primaryFingerprint); + return this; + } + + public VerificationListAssert containsVerificationBy(String signingKeyFingerprint, String primaryFingerprint) { + for (Verification verification : verificationList) { + if (primaryFingerprint.equals(verification.getSigningCertFingerprint()) && + signingKeyFingerprint.equals(verification.getSigningKeyFingerprint())) { + return this; + } + } + + fail("No verification was issued by key " + signingKeyFingerprint + " of cert " + primaryFingerprint); + return this; + } +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java b/sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java new file mode 100644 index 0000000..8e2ef7c --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * DSL for assertions on SOP objects. + */ +package sop.testsuite.assertions; diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java index 06a0f9b..e715c14 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -11,9 +11,11 @@ import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; import sop.Verification; import sop.enums.SignAs; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.testsuite.JUtils; import sop.testsuite.TestData; +import sop.testsuite.assertions.VerificationListAssert; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -21,7 +23,6 @@ import java.util.Date; import java.util.List; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @@ -47,8 +48,11 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .hasModeOrNull(SignatureMode.binary); } @ParameterizedTest @@ -68,8 +72,11 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .hasModeOrNull(SignatureMode.text); } @ParameterizedTest @@ -83,8 +90,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -103,8 +112,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -123,8 +134,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -146,7 +159,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -170,8 +186,10 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .signatures(armored) .data(message); - assertFalse(verificationList.isEmpty()); - JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -202,6 +220,21 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .data(message)); } + @ParameterizedTest + @MethodSource("provideInstances") + public void signWithAliceVerifyWithBobThrowsNoSignature(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signatures = sop.detachedSign() + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signatures) + .data(message)); + } @ParameterizedTest @MethodSource("provideInstances") @@ -229,11 +262,15 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes(); - assertFalse(sop.verify() + List verificationList = sop.verify() .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) - .data(message) - .isEmpty()); + .data(message); + + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -247,4 +284,29 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .data(message)); } + @ParameterizedTest + @MethodSource("provideInstances") + public void signVerifyWithMultipleKeys(SOP sop) throws IOException { + byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signatures = sop.detachedSign() + .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) + .data(message) + .toByteArrayAndResult() + .getBytes(); + + List verificationList = sop.detachedVerify() + .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) + .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) + .signatures(signatures) + .data(message); + + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .sizeEquals(2) + .containsVerificationBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .containsVerificationBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + } + + } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 8c62fb5..23f117e 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -13,9 +13,10 @@ import sop.DecryptionResult; import sop.SOP; import sop.Verification; import sop.enums.EncryptAs; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; -import sop.testsuite.JUtils; import sop.testsuite.TestData; +import sop.testsuite.assertions.VerificationListAssert; import sop.util.UTCUtil; import java.io.IOException; @@ -25,8 +26,6 @@ import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -142,6 +141,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .mode(EncryptAs.Binary) .plaintext(message) .getBytes(); @@ -156,9 +156,13 @@ public class EncryptDecryptTest extends AbstractSOPTest { DecryptionResult result = bytesAndResult.getResult(); assertNotNull(result.getSessionKey().get()); + List verificationList = result.getVerifications(); - assertEquals(1, verificationList.size()); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .hasModeOrNull(SignatureMode.binary); } @ParameterizedTest @@ -183,9 +187,12 @@ public class EncryptDecryptTest extends AbstractSOPTest { DecryptionResult result = bytesAndResult.getResult(); assertNotNull(result.getSessionKey().get()); + List verificationList = result.getVerifications(); - assertEquals(1, verificationList.size()); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .hasModeOrNull(SignatureMode.text); } @ParameterizedTest @@ -216,8 +223,10 @@ public class EncryptDecryptTest extends AbstractSOPTest { .ciphertext(ciphertext) .toByteArrayAndResult(); - assertFalse(bytesAndResult.getResult().getVerifications().isEmpty()); - assertArrayEquals(message, bytesAndResult.getBytes()); + List verifications = bytesAndResult.getResult().getVerifications(); + VerificationListAssert.assertThatVerificationList(verifications) + .isNotEmpty() + .hasSingleItem(); } @ParameterizedTest @@ -247,6 +256,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .ciphertext(message) .toByteArrayAndResult(); + // Some implementations do not throw NoSignature and instead return an empty list. if (bytesAndResult.getResult().getVerifications().isEmpty()) { throw new SOPGPException.NoSignature("No verifiable signature found."); } @@ -280,6 +290,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .ciphertext(message) .toByteArrayAndResult(); + // Some implementations do not throw NoSignature and instead return an empty list. if (bytesAndResult.getResult().getVerifications().isEmpty()) { throw new SOPGPException.NoSignature("No verifiable signature found."); } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java index 7d0c227..39a26c6 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java @@ -13,9 +13,11 @@ import sop.ByteArrayAndResult; import sop.SOP; import sop.Verification; import sop.enums.InlineSignAs; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.testsuite.JUtils; import sop.testsuite.TestData; +import sop.testsuite.assertions.VerificationListAssert; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -51,8 +53,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -74,8 +80,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -97,8 +107,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT) + .hasModeOrNull(SignatureMode.text); } @ParameterizedTest @@ -111,8 +125,13 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult(); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT, signatureDate); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .isCreatedAt(signatureDate) + .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -161,8 +180,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.BOB_SIGNING_FINGERPRINT, TestData.BOB_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -183,8 +206,12 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); assertArrayEquals(message, bytesAndResult.getBytes()); + List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .isNotEmpty() + .hasSingleItem() + .issuedBy(TestData.CAROL_SIGNING_FINGERPRINT, TestData.CAROL_PRIMARY_FINGERPRINT); } @ParameterizedTest @@ -205,7 +232,9 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { .toByteArrayAndResult(); List verificationList = bytesAndResult.getResult(); - JUtils.assertSignedBy(verificationList, TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .issuedBy(TestData.PASSWORD_PROTECTED_SIGNING_FINGERPRINT, TestData.PASSWORD_PROTECTED_PRIMARY_FINGERPRINT); } } From a722e98578b805e37f23be0a3984797e05e13f0d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:12:32 +0200 Subject: [PATCH 165/444] Update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d90e4..0a4e918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 6.0.1-SNAPSHOT +- `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` +- Add DSL for testing `Verification` results +- `external-sop`: Properly map error codes to new exception types: + - `UNSUPPORTED_PROFILE` + - `INCOMPATIBLE_OPTIONS` + +## 5.0.1 +- `external-sop`: Properly map error codes to new exception types: + - `UNSUPPORTED_PROFILE` + - `INCOMPATIBLE_OPTIONS` + ## 6.0.0 - Update implementation to [SOP Specification revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html). - Add option `--profile=XYZ` to `encrypt` subcommand From 0fccf3051ccc1ee22d6a15ea444eaa28c973ad2e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:21:37 +0200 Subject: [PATCH 166/444] Refactor AbstractSOPTest --- .../testsuite/operation/AbstractSOPTest.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java index fa9cbbf..8595898 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.Arguments; import sop.SOP; import sop.testsuite.SOPInstanceFactory; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -19,24 +20,29 @@ public abstract class AbstractSOPTest { private static final List backends = new ArrayList<>(); static { - // populate instances list via configured test subject factory - String factoryName = System.getenv("test.implementation"); - if (factoryName != null) { - try { - Class testSubjectFactoryClass = Class.forName(factoryName); - SOPInstanceFactory factory = (SOPInstanceFactory) testSubjectFactoryClass.newInstance(); - Map testSubjects = factory.provideSOPInstances(); + initBackends(); + } - for (String key : testSubjects.keySet()) { - backends.add(Arguments.of(Named.of(key, testSubjects.get(key)))); - } - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } + // populate instances list via configured test subject factory + private static void initBackends() { + String factoryName = System.getenv("test.implementation"); + if (factoryName == null) { + return; + } + + SOPInstanceFactory factory; + try { + Class testSubjectFactoryClass = Class.forName(factoryName); + factory = (SOPInstanceFactory) testSubjectFactoryClass + .getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | + InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + Map testSubjects = factory.provideSOPInstances(); + for (String key : testSubjects.keySet()) { + backends.add(Arguments.of(Named.of(key, testSubjects.get(key)))); } } From 790d80ec29340b337d0606ae6c81f0be351d11b5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:22:53 +0200 Subject: [PATCH 167/444] DateParsingTest: make armor command final --- .../src/test/java/sop/cli/picocli/DateParsingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java index 1fff1d5..e4550c7 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java @@ -14,7 +14,7 @@ import sop.cli.picocli.commands.ArmorCmd; import sop.util.UTCUtil; public class DateParsingTest { - private AbstractSopCmd cmd = new ArmorCmd(); // we use ArmorCmd as a concrete implementation. + private final AbstractSopCmd cmd = new ArmorCmd(); // we use ArmorCmd as a concrete implementation. @Test public void parseNotAfterDashReturnsEndOfTime() { From 0aabfac695db1d17bc401406e2aec4b05b7bb9d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:23:18 +0200 Subject: [PATCH 168/444] DetachedVerifyExternal: Make certs set final --- .../java/sop/external/operation/DetachedVerifyExternal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java index 05318d4..b9cd182 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -31,7 +31,7 @@ public class DetachedVerifyExternal implements DetachedVerify { private final List commandList = new ArrayList<>(); private final List envList; - private Set certs = new HashSet<>(); + private final Set certs = new HashSet<>(); private InputStream signatures; private int certCounter = 0; From ed59c713eb5b20a7610503a6a94da9f9b1f4c504 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:28:04 +0200 Subject: [PATCH 169/444] Remove unused 'throws IOException' declarations --- .../main/java/sop/external/operation/ArmorExternal.java | 5 ++--- .../main/java/sop/external/operation/DearmorExternal.java | 3 +-- .../sop/external/operation/DetachedVerifyExternal.java | 4 ++-- .../main/java/sop/external/operation/EncryptExternal.java | 2 +- .../java/sop/external/operation/InlineSignExternal.java | 2 +- .../java/sop/testsuite/operation/ListProfilesTest.java | 7 +++---- 6 files changed, 10 insertions(+), 13 deletions(-) diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java index cb53573..4df7fca 100644 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -10,11 +10,10 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Armor; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; -import java.util.Properties; import java.util.List; +import java.util.Properties; /** * Implementation of the {@link Armor} operation using an external SOP binary. @@ -37,7 +36,7 @@ public class ArmorExternal implements Armor { } @Override - public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + public Ready data(InputStream data) throws SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java index 7fa1fdc..bedf018 100644 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -9,7 +9,6 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Dearmor; -import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -30,7 +29,7 @@ public class DearmorExternal implements Dearmor { } @Override - public Ready data(InputStream data) throws SOPGPException.BadData, IOException { + public Ready data(InputStream data) throws SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java index b9cd182..2f19c5b 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -54,13 +54,13 @@ public class DetachedVerifyExternal implements DetachedVerify { } @Override - public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData { this.certs.add(cert); return this; } @Override - public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData, IOException { + public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData { this.signatures = signatures; return this; } diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index 1a7208e..bc40208 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -93,7 +93,7 @@ public class EncryptExternal implements Encrypt { @Override public Ready plaintext(InputStream plaintext) - throws IOException, SOPGPException.KeyIsProtected { + throws SOPGPException.KeyIsProtected { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, plaintext); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java index d78dd7b..68a630d 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -62,7 +62,7 @@ public class InlineSignExternal implements InlineSign { } @Override - public Ready data(InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java index f566dd3..6d3c4c4 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java @@ -11,7 +11,6 @@ import sop.Profile; import sop.SOP; import sop.exception.SOPGPException; -import java.io.IOException; import java.util.List; import java.util.stream.Stream; @@ -26,7 +25,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void listGenerateKeyProfiles(SOP sop) throws IOException { + public void listGenerateKeyProfiles(SOP sop) { List profiles = sop .listProfiles() .generateKey(); @@ -36,7 +35,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void listEncryptProfiles(SOP sop) throws IOException { + public void listEncryptProfiles(SOP sop) { List profiles = sop .listProfiles() .encrypt(); @@ -46,7 +45,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void listUnsupportedProfiles(SOP sop) throws IOException { + public void listUnsupportedProfiles(SOP sop) { assertThrows(SOPGPException.UnsupportedProfile.class, () -> sop .listProfiles() .subcommand("invalid")); From e336e536a8dc7cc797c26c333c7b0380feb14a64 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:44:49 +0200 Subject: [PATCH 170/444] Javadoc: Insert

tags to preserve newlines --- sop-java/src/main/java/sop/SOP.java | 12 ++++++------ sop-java/src/main/java/sop/SigningResult.java | 2 +- .../src/main/java/sop/exception/SOPGPException.java | 2 +- .../src/main/java/sop/util/ProxyOutputStream.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index c74c6cd..804cd28 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -54,7 +54,7 @@ public interface SOP { /** * Create detached signatures. * Customize the operation using the builder {@link DetachedSign}. - * + *

* If you want to sign a message inline, use {@link #inlineSign()} instead. * * @return builder instance @@ -66,7 +66,7 @@ public interface SOP { /** * Create detached signatures. * Customize the operation using the builder {@link DetachedSign}. - * + *

* If you want to sign a message inline, use {@link #inlineSign()} instead. * * @return builder instance @@ -75,7 +75,7 @@ public interface SOP { /** * Sign a message using inline signatures. - * + *

* If you need to create detached signatures, use {@link #detachedSign()} instead. * * @return builder instance @@ -85,7 +85,7 @@ public interface SOP { /** * Verify detached signatures. * Customize the operation using the builder {@link DetachedVerify}. - * + *

* If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. * * @return builder instance @@ -97,7 +97,7 @@ public interface SOP { /** * Verify detached signatures. * Customize the operation using the builder {@link DetachedVerify}. - * + *

* If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. * * @return builder instance @@ -106,7 +106,7 @@ public interface SOP { /** * Verify signatures of an inline-signed message. - * + *

* If you need to verify detached signatures over a message, use {@link #detachedVerify()} instead. * * @return builder instance diff --git a/sop-java/src/main/java/sop/SigningResult.java b/sop-java/src/main/java/sop/SigningResult.java index 2cb142d..1ea1ba8 100644 --- a/sop-java/src/main/java/sop/SigningResult.java +++ b/sop-java/src/main/java/sop/SigningResult.java @@ -19,7 +19,7 @@ public final class SigningResult { * Return a string identifying the digest mechanism used to create the signed message. * This is useful for setting the micalg= parameter for the multipart/signed * content type of a PGP/MIME object as described in section 5 of [RFC3156]. - * + *

* If more than one signature was generated and different digest mechanisms were used, * the value of the micalg object is an empty string. * diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index f044fa6..1d13065 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -337,7 +337,7 @@ public abstract class SOPGPException extends RuntimeException { /** * Exception that gets thrown if a special designator (starting with @) is given, but the filesystem contains * a file matching the designator. - * + *

* E.g.

@ENV:FOO
is given, but
./@ENV:FOO
exists on the filesystem. */ public static class AmbiguousInput extends SOPGPException { diff --git a/sop-java/src/main/java/sop/util/ProxyOutputStream.java b/sop-java/src/main/java/sop/util/ProxyOutputStream.java index 0559e8f..ed24fc2 100644 --- a/sop-java/src/main/java/sop/util/ProxyOutputStream.java +++ b/sop-java/src/main/java/sop/util/ProxyOutputStream.java @@ -12,7 +12,7 @@ import java.io.OutputStream; * {@link OutputStream} that buffers data being written into it, until its underlying output stream is being replaced. * At that point, first all the buffered data is being written to the underlying stream, followed by any successive * data that may get written to the {@link ProxyOutputStream}. - * + *

* This class is useful if we need to provide an {@link OutputStream} at one point in time when the final * target output stream is not yet known. */ From 226b5d99a050332426c3f3ff5cc6448c6b13e90c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 26 Apr 2023 16:47:53 +0200 Subject: [PATCH 171/444] Fix issues with i18n properties --- .../src/main/resources/msg_decrypt.properties | 4 ++-- .../src/main/resources/msg_decrypt_de.properties | 2 +- .../src/main/resources/msg_detached-sign.properties | 4 ++-- .../src/main/resources/msg_detached-verify.properties | 8 ++++---- .../src/main/resources/msg_encrypt.properties | 2 +- .../src/main/resources/msg_inline-sign.properties | 6 +++--- .../src/main/resources/msg_inline-sign_de.properties | 2 +- sop-java-picocli/src/main/resources/msg_sop.properties | 2 +- .../src/main/resources/msg_version_de.properties | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index 47d1c31..c68a1b8 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -5,10 +5,10 @@ usage.header=Decrypt a message from standard input session-key-out=Can be used to learn the session key on successful decryption with-session-key.0=Symmetric message key (session key). with-session-key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. -with-session-key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +with-session-key.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). with-password.0=Symmetric passphrase to decrypt the message with. with-password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTEXT". -with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). verify-out=Emits signature verification status to the designated output verify-with=Certificates for signature verification not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index 0ef4a4c..249e39c 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -5,7 +5,7 @@ usage.header=Entschl session-key-out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung with-session-key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). with-session-key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. -with_session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +with-session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). with-password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. with-password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. with-password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index 50ab7c9..f21b79a 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -3,12 +3,12 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Create a detached signature on the data from standard input no-armor=ASCII armor the output -as.0=Specify the output format of the signed message +as.0=Specify the output format of the signed message. as.1=Defaults to 'binary'. as.2=If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing # Generic TODO: Remove when bumping picocli to 4.7.0 diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index 9b843cc..ecc75b3 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -5,10 +5,10 @@ usage.header=Verify a detached signature over the data from standard input not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) not-before.1=Reject signatures with a creation date not in range. not-before.2=Defaults to beginning of time ("-"). -not-after.1=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -not-after.2=Reject signatures with a creation date not in range. -not-after.3=Defaults to current system time ("now").\ -not-after.4 = Accepts special value "-" for end of time. +not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +not-after.1=Reject signatures with a creation date not in range. +not-after.2=Defaults to current system time ("now"). +not-after.3=Accepts special value "-" for end of time. SIGNATURE[0]=Detached signature CERT[0..*]=Public key certificates for signature verification diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index c04ce32..7c3b218 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -5,7 +5,7 @@ usage.header=Encrypt a message from standard input no-armor=ASCII armor the output as=Type of the input data. Defaults to 'binary' with-password.0=Encrypt the message with a password. -with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +with-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). sign-with=Sign the output with a private key profile=Profile identifier to switch between profiles with-key-password.0=Passphrase to unlock the secret key(s). diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index 88584e9..888cbfb 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -3,14 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Create an inline-signed message from data on standard input no-armor=ASCII armor the output -as.0=Specify the signature format of the signed message +as.0=Specify the signature format of the signed message. as.1='text' and 'binary' will produce inline-signed messages. -as.2='cleartextsigned' will make use of the cleartext signature framework. +as.2='clearsigned' will make use of the cleartext signature framework. as.3=Defaults to 'binary'. as.4=If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). -micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156) +micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing # Generic TODO: Remove when bumping picocli to 4.7.0 diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index 10be2f3..d0606d7 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -5,7 +5,7 @@ usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Sign no-armor=Schütze Ausgabe mit ASCII Armor as.0=Bestimme Signaturformat der Nachricht. as.1='text' und 'binary' resultieren in eingebettete Signaturen. -as.2='cleartextsigned' wird die Nachricht Klartext-signieren. +as.2='clearsigned' wird die Nachricht Klartext-signieren. as.3=Standardmäßig: 'binary'. as.4=Ist die Standard-Eingabe nicht UTF-8 kodiert und '--as=text' gesetzt, so wird inline-sign Fehlercode 53 zurückgeben. with-key-password.0=Passwort zum Entsperren des privaten Schlüssels diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index b41d314..52c5368 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -47,7 +47,7 @@ sop.error.input.malformed_not_after=Invalid date string supplied as value of '-- sop.error.input.malformed_not_before=Invalid date string supplied as value of '--not-before'. sop.error.input.stdin_not_a_message=Standard Input appears not to contain a valid OpenPGP message. sop.error.input.stdin_not_a_private_key=Standard Input appears not to contain a valid OpenPGP secret key. -sop.error.input.stdin_not_openpgp_data=Standard Input appears not to contain valid OpenPGP data +sop.error.input.stdin_not_openpgp_data=Standard Input appears not to contain valid OpenPGP data. ## Indirect Data Types sop.error.indirect_data_type.ambiguous_filename=File name '%s' is ambiguous. File with the same name exists on the filesystem. sop.error.indirect_data_type.environment_variable_not_set=Environment variable '%s' not set. diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index fe460e9..573a82f 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -4,7 +4,7 @@ usage.header=Zeige Versionsinformationen über das Programm extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus -sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird. +sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 From 19d6b7e142ec15eed42ab837ed77b5f837fb57d0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 12:44:01 +0200 Subject: [PATCH 172/444] Verification: Make use of Optional for signature mode and description --- sop-java/src/main/java/sop/Verification.java | 42 +++++++++++++------ .../assertions/VerificationAssert.java | 8 ++-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index f18826e..12ed819 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -4,11 +4,12 @@ package sop; -import java.util.Date; - import sop.enums.SignatureMode; +import sop.util.Optional; import sop.util.UTCUtil; +import java.util.Date; + /** * Class bundling information about a verified signature. */ @@ -17,8 +18,8 @@ public class Verification { private final Date creationTime; private final String signingKeyFingerprint; private final String signingCertFingerprint; - private final SignatureMode signatureMode; - private final String description; + private final Optional signatureMode; + private final Optional description; private static final String MODE = "mode:"; @@ -30,7 +31,7 @@ public class Verification { * @param signingCertFingerprint fingerprint of the certificate */ public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) { - this(creationTime, signingKeyFingerprint, signingCertFingerprint, null, null); + this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty()); } /** @@ -43,11 +44,28 @@ public class Verification { * @param description free-form description, e.g.

certificate from dkg.asc
(optional, may be
null
) */ public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, SignatureMode signatureMode, String description) { + this( + creationTime, + signingKeyFingerprint, + signingCertFingerprint, + Optional.ofNullable(signatureMode), + Optional.ofNullable(nullSafeTrim(description)) + ); + } + + private Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, Optional signatureMode, Optional description) { this.creationTime = creationTime; this.signingKeyFingerprint = signingKeyFingerprint; this.signingCertFingerprint = signingCertFingerprint; this.signatureMode = signatureMode; - this.description = description == null ? null : description.trim(); + this.description = description; + } + + private static String nullSafeTrim(String string) { + if (string == null) { + return null; + } + return string.trim(); } public static Verification fromString(String toString) { @@ -121,7 +139,7 @@ public class Verification { * * @return signature mode */ - public SignatureMode getSignatureMode() { + public Optional getSignatureMode() { return signatureMode; } @@ -131,7 +149,7 @@ public class Verification { * * @return description */ - public String getDescription() { + public Optional getDescription() { return description; } @@ -144,12 +162,12 @@ public class Verification { .append(' ') .append(getSigningCertFingerprint()); - if (signatureMode != null) { - sb.append(' ').append(MODE).append(signatureMode); + if (signatureMode.isPresent()) { + sb.append(' ').append(MODE).append(signatureMode.get()); } - if (description != null) { - sb.append(' ').append(description); + if (description.isPresent()) { + sb.append(' ').append(description.get()); } return sb.toString(); diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java index 35e3ed6..63fd237 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java @@ -45,12 +45,12 @@ public final class VerificationAssert { } public VerificationAssert hasDescription(String description) { - assertEquals(description, verification.getDescription()); + assertEquals(description, verification.getDescription().get()); return this; } public VerificationAssert hasDescriptionOrNull(String description) { - if (verification.getDescription() == null) { + if (verification.getDescription().isEmpty()) { return this; } @@ -58,12 +58,12 @@ public final class VerificationAssert { } public VerificationAssert hasMode(SignatureMode mode) { - assertEquals(mode, verification.getSignatureMode()); + assertEquals(mode, verification.getSignatureMode().get()); return this; } public VerificationAssert hasModeOrNull(SignatureMode mode) { - if (verification.getSignatureMode() == null) { + if (verification.getSignatureMode().isEmpty()) { return this; } return hasMode(mode); From 44e6dd2180235bcefdbcc2a0558ce3888b726ce8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 12:44:40 +0200 Subject: [PATCH 173/444] Depend on findbugs:jsr305 for @Nullable etc. annotations --- sop-java/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sop-java/build.gradle b/sop-java/build.gradle index c958610..ca546bf 100644 --- a/sop-java/build.gradle +++ b/sop-java/build.gradle @@ -19,6 +19,10 @@ dependencies { testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + + // @Nullable, @Nonnull annotations + implementation "com.google.code.findbugs:jsr305:3.0.2" + } test { From 8b8863c6dfc7dc9bb2fe87cc182cf8df2a03d4fa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 12:52:12 +0200 Subject: [PATCH 174/444] Verification: Annotate with @Nonnull, @Nullable --- sop-java/src/main/java/sop/Verification.java | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index 12ed819..d48a0b7 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -8,6 +8,8 @@ import sop.enums.SignatureMode; import sop.util.Optional; import sop.util.UTCUtil; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Date; /** @@ -30,7 +32,9 @@ public class Verification { * @param signingKeyFingerprint fingerprint of the signing (sub-) key * @param signingCertFingerprint fingerprint of the certificate */ - public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) { + public Verification(@Nonnull Date creationTime, + @Nonnull String signingKeyFingerprint, + @Nonnull String signingCertFingerprint) { this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty()); } @@ -43,7 +47,11 @@ public class Verification { * @param signatureMode signature mode (optional, may be
null
) * @param description free-form description, e.g.
certificate from dkg.asc
(optional, may be
null
) */ - public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, SignatureMode signatureMode, String description) { + public Verification(@Nonnull Date creationTime, + @Nonnull String signingKeyFingerprint, + @Nonnull String signingCertFingerprint, + @Nullable SignatureMode signatureMode, + @Nullable String description) { this( creationTime, signingKeyFingerprint, @@ -53,7 +61,11 @@ public class Verification { ); } - private Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint, Optional signatureMode, Optional description) { + private Verification(@Nonnull Date creationTime, + @Nonnull String signingKeyFingerprint, + @Nonnull String signingCertFingerprint, + @Nonnull Optional signatureMode, + @Nonnull Optional description) { this.creationTime = creationTime; this.signingKeyFingerprint = signingKeyFingerprint; this.signingCertFingerprint = signingCertFingerprint; @@ -61,14 +73,15 @@ public class Verification { this.description = description; } - private static String nullSafeTrim(String string) { + private static String nullSafeTrim(@Nullable String string) { if (string == null) { return null; } return string.trim(); } - public static Verification fromString(String toString) { + @Nonnull + public static Verification fromString(@Nonnull String toString) { String[] split = toString.trim().split(" "); if (split.length < 3) { throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'"); @@ -111,6 +124,7 @@ public class Verification { * * @return signature creation time */ + @Nonnull public Date getCreationTime() { return creationTime; } @@ -120,6 +134,7 @@ public class Verification { * * @return signing key fingerprint */ + @Nonnull public String getSigningKeyFingerprint() { return signingKeyFingerprint; } @@ -129,6 +144,7 @@ public class Verification { * * @return signing certificate fingerprint */ + @Nonnull public String getSigningCertFingerprint() { return signingCertFingerprint; } @@ -139,6 +155,7 @@ public class Verification { * * @return signature mode */ + @Nonnull public Optional getSignatureMode() { return signatureMode; } @@ -149,6 +166,7 @@ public class Verification { * * @return description */ + @Nonnull public Optional getDescription() { return description; } From 0308732328d6a3879c3b9d6a66ffee2c5d1782e8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 13:07:08 +0200 Subject: [PATCH 175/444] Make UTCUtil.parseUTCDate() throw instead of returning null for malformed inputs --- .../cli/picocli/commands/AbstractSopCmd.java | 30 ++++++++++++++----- .../cli/picocli/commands/DecryptCmdTest.java | 5 ++-- .../cli/picocli/commands/VerifyCmdTest.java | 9 +++--- sop-java/src/main/java/sop/Verification.java | 13 ++++++-- sop-java/src/main/java/sop/util/UTCUtil.java | 12 ++++++-- .../java/sop/util/ByteArrayAndResultTest.java | 3 +- .../java/sop/util/ReadyWithResultTest.java | 3 +- .../src/test/java/sop/util/UTCUtilTest.java | 18 +++++------ .../test/java/sop/util/VerificationTest.java | 11 +++---- .../java/sop/testsuite/TestData.java | 13 ++++++-- .../operation/EncryptDecryptTest.java | 5 ++-- 11 files changed, 85 insertions(+), 37 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index d155e9c..9aec5a7 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java @@ -18,6 +18,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.ParseException; import java.util.Collection; import java.util.Date; import java.util.Locale; @@ -246,21 +247,36 @@ public abstract class AbstractSopCmd implements Runnable { } public Date parseNotAfter(String notAfter) { - Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter); - if (date == null) { + if (notAfter.equals("now")) { + return new Date(); + } + + if (notAfter.equals("-")) { + return END_OF_TIME; + } + + try { + return UTCUtil.parseUTCDate(notAfter); + } catch (ParseException e) { String errorMsg = getMsg("sop.error.input.malformed_not_after"); throw new IllegalArgumentException(errorMsg); } - return date; } public Date parseNotBefore(String notBefore) { - Date date = notBefore.equals("now") ? new Date() : notBefore.equals("-") ? BEGINNING_OF_TIME : UTCUtil.parseUTCDate(notBefore); - if (date == null) { + if (notBefore.equals("now")) { + return new Date(); + } + + if (notBefore.equals("-")) { + return BEGINNING_OF_TIME; + } + + try { + return UTCUtil.parseUTCDate(notBefore); + } catch (ParseException e) { String errorMsg = getMsg("sop.error.input.malformed_not_before"); throw new IllegalArgumentException(errorMsg); } - return date; } - } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index b445a6d..e3dd198 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -31,6 +31,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.text.ParseException; import java.util.Collections; import java.util.Date; @@ -187,7 +188,7 @@ public class DecryptCmdTest { } @Test - public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { + public void assertSessionKeyAndVerificationsIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException, ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -281,7 +282,7 @@ public class DecryptCmdTest { } @Test - public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData { + public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, ParseException { File certFile = File.createTempFile("verify-out-cert", ".asc"); File verifyOut = new File(certFile.getParent(), "verify-out.txt"); if (verifyOut.exists()) { diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java index c33cf74..50a8043 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.text.ParseException; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -41,7 +42,7 @@ public class VerifyCmdTest { PrintStream originalSout; @BeforeEach - public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException { + public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException, ParseException { originalSout = System.out; detachedVerify = mock(DetachedVerify.class); @@ -73,7 +74,7 @@ public class VerifyCmdTest { } @Test - public void notAfter_passedDown() throws SOPGPException.UnsupportedOption { + public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); verify(detachedVerify, times(1)).notAfter(date); @@ -100,7 +101,7 @@ public class VerifyCmdTest { } @Test - public void notBefore_passedDown() throws SOPGPException.UnsupportedOption { + public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); verify(detachedVerify, times(1)).notBefore(date); @@ -178,7 +179,7 @@ public class VerifyCmdTest { } @Test - public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { + public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData, ParseException { when(detachedVerify.data((InputStream) any())).thenReturn(Arrays.asList( new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"), "EB85BB5FA33A75E15E944E63F231550C4F47E38E", diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java index d48a0b7..140e23b 100644 --- a/sop-java/src/main/java/sop/Verification.java +++ b/sop-java/src/main/java/sop/Verification.java @@ -10,6 +10,7 @@ import sop.util.UTCUtil; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.text.ParseException; import java.util.Date; /** @@ -89,7 +90,7 @@ public class Verification { if (split.length == 3) { return new Verification( - UTCUtil.parseUTCDate(split[0]), // timestamp + parseUTCDate(split[0]), // timestamp split[1], // key FP split[2] // cert FP ); @@ -111,7 +112,7 @@ public class Verification { } return new Verification( - UTCUtil.parseUTCDate(split[0]), // timestamp + parseUTCDate(split[0]), // timestamp split[1], // key FP split[2], // cert FP mode, // signature mode @@ -119,6 +120,14 @@ public class Verification { ); } + private static Date parseUTCDate(String utcFormatted) { + try { + return UTCUtil.parseUTCDate(utcFormatted); + } catch (ParseException e) { + throw new IllegalArgumentException("Malformed UTC timestamp.", e); + } + } + /** * Return the signatures' creation time. * diff --git a/sop-java/src/main/java/sop/util/UTCUtil.java b/sop-java/src/main/java/sop/util/UTCUtil.java index 8ef7e77..3849258 100644 --- a/sop-java/src/main/java/sop/util/UTCUtil.java +++ b/sop-java/src/main/java/sop/util/UTCUtil.java @@ -4,6 +4,7 @@ package sop.util; +import javax.annotation.Nonnull; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -33,15 +34,22 @@ public class UTCUtil { * @param dateString string * @return date */ - public static Date parseUTCDate(String dateString) { + @Nonnull + public static Date parseUTCDate(String dateString) throws ParseException { + ParseException exception = null; for (SimpleDateFormat parser : UTC_PARSERS) { try { return parser.parse(dateString); } catch (ParseException e) { + // Store first exception (that of UTC_FORMATTER) to throw if we fail to parse the date + if (exception == null) { + exception = e; + } // Try next parser } } - return null; + // No parser worked, so we throw the store exception + throw exception; } /** diff --git a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java b/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java index 8ae1859..a1413b4 100644 --- a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java +++ b/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Collections; import java.util.List; @@ -18,7 +19,7 @@ import sop.Verification; public class ByteArrayAndResultTest { @Test - public void testCreationAndGetters() { + public void testCreationAndGetters() throws ParseException { byte[] bytes = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); List result = Collections.singletonList( new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"), diff --git a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java b/sop-java/src/test/java/sop/util/ReadyWithResultTest.java index 97841fa..1207c5c 100644 --- a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java +++ b/sop-java/src/test/java/sop/util/ReadyWithResultTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Collections; import java.util.List; @@ -22,7 +23,7 @@ import sop.exception.SOPGPException; public class ReadyWithResultTest { @Test - public void testReadyWithResult() throws SOPGPException.NoSignature, IOException { + public void testReadyWithResult() throws SOPGPException.NoSignature, IOException, ParseException { byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); List result = Collections.singletonList( new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"), diff --git a/sop-java/src/test/java/sop/util/UTCUtilTest.java b/sop-java/src/test/java/sop/util/UTCUtilTest.java index 18de817..f6ccbc8 100644 --- a/sop-java/src/test/java/sop/util/UTCUtilTest.java +++ b/sop-java/src/test/java/sop/util/UTCUtilTest.java @@ -4,12 +4,13 @@ package sop.util; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.Test; +import java.text.ParseException; import java.util.Date; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Test parsing some date examples from the stateless OpenPGP CLI spec. @@ -19,30 +20,29 @@ import org.junit.jupiter.api.Test; public class UTCUtilTest { @Test - public void parseExample1() { + public void parseExample1() throws ParseException { String timestamp = "2019-10-29T12:11:04+00:00"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date)); } @Test - public void parseExample2() { + public void parseExample2() throws ParseException { String timestamp = "2019-10-24T23:48:29Z"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-24T23:48:29Z", UTCUtil.formatUTCDate(date)); } @Test - public void parseExample3() { + public void parseExample3() throws ParseException { String timestamp = "20191029T121104Z"; Date date = UTCUtil.parseUTCDate(timestamp); assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date)); } @Test - public void invalidDateReturnsNull() { + public void invalidDateThrows() { String invalidTimestamp = "foobar"; - Date expectNull = UTCUtil.parseUTCDate(invalidTimestamp); - assertNull(expectNull); + assertThrows(ParseException.class, () -> UTCUtil.parseUTCDate(invalidTimestamp)); } } diff --git a/sop-java/src/test/java/sop/util/VerificationTest.java b/sop-java/src/test/java/sop/util/VerificationTest.java index b292688..c4f864e 100644 --- a/sop-java/src/test/java/sop/util/VerificationTest.java +++ b/sop-java/src/test/java/sop/util/VerificationTest.java @@ -9,6 +9,7 @@ import sop.Verification; import sop.enums.SignatureMode; import sop.testsuite.assertions.VerificationAssert; +import java.text.ParseException; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class VerificationTest { @Test - public void limitedConstructorTest() { + public void limitedConstructorTest() throws ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -32,7 +33,7 @@ public class VerificationTest { } @Test - public void limitedParsingTest() { + public void limitedParsingTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); @@ -44,7 +45,7 @@ public class VerificationTest { } @Test - public void parsingWithModeTest() { + public void parsingWithModeTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:text"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); @@ -56,7 +57,7 @@ public class VerificationTest { } @Test - public void extendedConstructorTest() { + public void extendedConstructorTest() throws ParseException { Date signDate = UTCUtil.parseUTCDate("2022-11-07T15:01:24Z"); String keyFP = "F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; String certFP = "4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; @@ -73,7 +74,7 @@ public class VerificationTest { } @Test - public void extendedParsingTest() { + public void extendedParsingTest() throws ParseException { String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc"; Verification verification = Verification.fromString(string); assertEquals(string, verification.toString()); diff --git a/sop-java/src/testFixtures/java/sop/testsuite/TestData.java b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java index e1d35c2..386c411 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/TestData.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/TestData.java @@ -7,6 +7,7 @@ package sop.testsuite; import sop.util.UTCUtil; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Date; public class TestData { @@ -59,7 +60,7 @@ public class TestData { "sfcfswMA\n" + "=RDAo\n" + "-----END PGP MESSAGE-----"; - public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T17:20:47Z"); + public static final Date ALICE_INLINE_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T17:20:47Z"); // signature over PLAINTEXT public static final String ALICE_DETACHED_SIGNED_MESSAGE = "-----BEGIN PGP SIGNATURE-----\n" + "\n" + @@ -68,7 +69,7 @@ public class TestData { "0K1UgT5roym9Fln8U5W8R03TSbfNiwE=\n" + "=bxPN\n" + "-----END PGP SIGNATURE-----"; - public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = UTCUtil.parseUTCDate("2023-01-13T16:57:57Z"); + public static final Date ALICE_DETACHED_SIGNED_MESSAGE_DATE = parseUTCDate("2023-01-13T16:57:57Z"); // 'Bob' key from draft-bre-openpgp-samples-00 public static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + @@ -421,4 +422,12 @@ public class TestData { public static final byte[] BEGIN_PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n".getBytes(StandardCharsets.UTF_8); public static final byte[] END_PGP_SIGNATURE = "-----END PGP SIGNATURE-----".getBytes(StandardCharsets.UTF_8); public static final byte[] BEGIN_PGP_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n".getBytes(StandardCharsets.UTF_8); + + private static Date parseUTCDate(String utcFormatted) { + try { + return UTCUtil.parseUTCDate(utcFormatted); + } catch (ParseException e) { + throw new IllegalArgumentException("Malformed UTC timestamp.", e); + } + } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 23f117e..0c382dc 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -21,6 +21,7 @@ import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.util.Date; import java.util.List; import java.util.stream.Stream; @@ -231,7 +232,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void decryptVerifyNotAfterTest(SOP sop) { + public void decryptVerifyNotAfterTest(SOP sop) throws ParseException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + @@ -265,7 +266,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") - public void decryptVerifyNotBeforeTest(SOP sop) { + public void decryptVerifyNotBeforeTest(SOP sop) throws ParseException { byte[] message = ("-----BEGIN PGP MESSAGE-----\n" + "\n" + "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" + From 8eba099146d6d1a390c5560118ecb1d5d902c92c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 13:15:44 +0200 Subject: [PATCH 176/444] UTF8Util.decodeUTF8(): throw CharacterCodingException instead of PasswordNotHumanReadable --- .../src/main/java/sop/operation/GenerateKey.java | 7 ++++++- sop-java/src/main/java/sop/util/UTF8Util.java | 16 ++++++---------- .../src/test/java/sop/util/UTF8UtilTest.java | 10 +++++----- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java index 27a3b19..b788785 100644 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ b/sop-java/src/main/java/sop/operation/GenerateKey.java @@ -6,6 +6,7 @@ package sop.operation; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.CharacterCodingException; import sop.Profile; import sop.Ready; @@ -54,7 +55,11 @@ public interface GenerateKey { default GenerateKey withKeyPassword(byte[] password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - return withKeyPassword(UTF8Util.decodeUTF8(password)); + try { + return withKeyPassword(UTF8Util.decodeUTF8(password)); + } catch (CharacterCodingException e) { + throw new SOPGPException.PasswordNotHumanReadable(); + } } /** diff --git a/sop-java/src/main/java/sop/util/UTF8Util.java b/sop-java/src/main/java/sop/util/UTF8Util.java index ec3c22d..a59ff43 100644 --- a/sop-java/src/main/java/sop/util/UTF8Util.java +++ b/sop-java/src/main/java/sop/util/UTF8Util.java @@ -4,8 +4,6 @@ package sop.util; -import sop.exception.SOPGPException; - import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; @@ -15,7 +13,8 @@ import java.nio.charset.CodingErrorAction; public class UTF8Util { - private static final CharsetDecoder UTF8Decoder = Charset.forName("UTF8") + public static final Charset UTF8 = Charset.forName("UTF8"); + private static final CharsetDecoder UTF8Decoder = UTF8 .newDecoder() .onUnmappableCharacter(CodingErrorAction.REPORT) .onMalformedInput(CodingErrorAction.REPORT); @@ -28,13 +27,10 @@ public class UTF8Util { * * @return decoded string */ - public static String decodeUTF8(byte[] data) { + public static String decodeUTF8(byte[] data) + throws CharacterCodingException { ByteBuffer byteBuffer = ByteBuffer.wrap(data); - try { - CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer); - return charBuffer.toString(); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable(); - } + CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer); + return charBuffer.toString(); } } diff --git a/sop-java/src/test/java/sop/util/UTF8UtilTest.java b/sop-java/src/test/java/sop/util/UTF8UtilTest.java index 775d273..95906bc 100644 --- a/sop-java/src/test/java/sop/util/UTF8UtilTest.java +++ b/sop-java/src/test/java/sop/util/UTF8UtilTest.java @@ -5,8 +5,8 @@ package sop.util; import org.junit.jupiter.api.Test; -import sop.exception.SOPGPException; +import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class UTF8UtilTest { @Test - public void testValidUtf8Decoding() { + public void testValidUtf8Decoding() throws CharacterCodingException { String utf8String = "Hello, World\n"; String decoded = UTF8Util.decodeUTF8(utf8String.getBytes(StandardCharsets.UTF_8)); @@ -29,11 +29,11 @@ public class UTF8UtilTest { */ @Test public void testInvalidUtf8StringThrows() { - assertThrows(SOPGPException.PasswordNotHumanReadable.class, + assertThrows(CharacterCodingException.class, () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xa0, (byte) 0xa1})); - assertThrows(SOPGPException.PasswordNotHumanReadable.class, + assertThrows(CharacterCodingException.class, () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0xc0, (byte) 0xaf})); - assertThrows(SOPGPException.PasswordNotHumanReadable.class, + assertThrows(CharacterCodingException.class, () -> UTF8Util.decodeUTF8(new byte[] {(byte) 0x80, (byte) 0xbf})); } } From 8aded17f1091d8ca1e97e0065df06572c1d62bb1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 13:17:58 +0200 Subject: [PATCH 177/444] Use UTF8Util.UTF8 constant --- sop-java/src/main/java/sop/Profile.java | 4 ++-- .../src/main/java/sop/operation/AbstractSign.java | 4 ++-- sop-java/src/main/java/sop/operation/Decrypt.java | 4 ++-- sop-java/src/main/java/sop/operation/Encrypt.java | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java index 801f748..4a981f4 100644 --- a/sop-java/src/main/java/sop/Profile.java +++ b/sop-java/src/main/java/sop/Profile.java @@ -4,7 +4,7 @@ package sop; -import java.nio.charset.Charset; +import sop.util.UTF8Util; /** * Tuple class bundling a profile name and description. @@ -77,6 +77,6 @@ public class Profile { */ private static boolean exceeds1000CharLineLimit(Profile profile) { String line = profile.toString(); - return line.getBytes(Charset.forName("UTF8")).length > 1000; + return line.getBytes(UTF8Util.UTF8).length > 1000; } } diff --git a/sop-java/src/main/java/sop/operation/AbstractSign.java b/sop-java/src/main/java/sop/operation/AbstractSign.java index 3f9d6fc..508d741 100644 --- a/sop-java/src/main/java/sop/operation/AbstractSign.java +++ b/sop-java/src/main/java/sop/operation/AbstractSign.java @@ -5,11 +5,11 @@ package sop.operation; import sop.exception.SOPGPException; +import sop.util.UTF8Util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; public interface AbstractSign { @@ -67,7 +67,7 @@ public interface AbstractSign { default T withKeyPassword(String password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(Charset.forName("UTF8"))); + return withKeyPassword(password.getBytes(UTF8Util.UTF8)); } /** diff --git a/sop-java/src/main/java/sop/operation/Decrypt.java b/sop-java/src/main/java/sop/operation/Decrypt.java index d695032..0123bbc 100644 --- a/sop-java/src/main/java/sop/operation/Decrypt.java +++ b/sop-java/src/main/java/sop/operation/Decrypt.java @@ -8,11 +8,11 @@ import sop.DecryptionResult; import sop.ReadyWithResult; import sop.SessionKey; import sop.exception.SOPGPException; +import sop.util.UTF8Util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; import java.util.Date; public interface Decrypt { @@ -138,7 +138,7 @@ public interface Decrypt { default Decrypt withKeyPassword(String password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(Charset.forName("UTF8"))); + return withKeyPassword(password.getBytes(UTF8Util.UTF8)); } /** diff --git a/sop-java/src/main/java/sop/operation/Encrypt.java b/sop-java/src/main/java/sop/operation/Encrypt.java index fed1210..b380d32 100644 --- a/sop-java/src/main/java/sop/operation/Encrypt.java +++ b/sop-java/src/main/java/sop/operation/Encrypt.java @@ -4,15 +4,15 @@ package sop.operation; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; - import sop.Profile; import sop.Ready; import sop.enums.EncryptAs; import sop.exception.SOPGPException; +import sop.util.UTF8Util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; public interface Encrypt { @@ -82,7 +82,7 @@ public interface Encrypt { default Encrypt withKeyPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - return withKeyPassword(password.getBytes(Charset.forName("UTF8"))); + return withKeyPassword(password.getBytes(UTF8Util.UTF8)); } /** From 49fd7143cf71d0565daa73b1f31ee79195e758d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 13:26:01 +0200 Subject: [PATCH 178/444] Update CHANGELOG --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a4e918..b9bc020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 6.0.1-SNAPSHOT +## 6.1.0-SNAPSHOT - `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` - Add DSL for testing `Verification` results -- `external-sop`: Properly map error codes to new exception types: +- `Verification`: Return `Optional<>` for `getSignatureMode()` and `getDescription()` +- `sop-java`: Add dependency on `com.google.code.findbugs:jsr305` for `@Nullable`, `@Nonnull` annotations +- `UTCUtil`: `parseUTCDate()` is now `@Nonnull` and throws a `ParseException` for invalid inputs +- `UTF8Util`: `decodeUTF8()` now throws `CharacterCodingException` instead of `SOPGPException.PasswordNotHumanReadable` +- `external-sop`: Properly map error codes to new exception types (ported from `5.0.1`): - `UNSUPPORTED_PROFILE` - `INCOMPATIBLE_OPTIONS` From 7ea46a1916eefbd4b50c8be496dcb4300794b580 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:23:58 +0200 Subject: [PATCH 179/444] Move tests --- .../src/test/java/sop/{util => }/ByteArrayAndResultTest.java | 3 ++- sop-java/src/test/java/sop/{util => }/MicAlgTest.java | 2 +- sop-java/src/test/java/sop/{util => }/ReadyTest.java | 2 +- sop-java/src/test/java/sop/{util => }/ReadyWithResultTest.java | 3 ++- sop-java/src/test/java/sop/{util => }/SessionKeyTest.java | 3 ++- sop-java/src/test/java/sop/{util => }/SigningResultTest.java | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) rename sop-java/src/test/java/sop/{util => }/ByteArrayAndResultTest.java (96%) rename sop-java/src/test/java/sop/{util => }/MicAlgTest.java (98%) rename sop-java/src/test/java/sop/{util => }/ReadyTest.java (97%) rename sop-java/src/test/java/sop/{util => }/ReadyWithResultTest.java (97%) rename sop-java/src/test/java/sop/{util => }/SessionKeyTest.java (98%) rename sop-java/src/test/java/sop/{util => }/SigningResultTest.java (96%) diff --git a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java b/sop-java/src/test/java/sop/ByteArrayAndResultTest.java similarity index 96% rename from sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java rename to sop-java/src/test/java/sop/ByteArrayAndResultTest.java index a1413b4..8d89842 100644 --- a/sop-java/src/test/java/sop/util/ByteArrayAndResultTest.java +++ b/sop-java/src/test/java/sop/ByteArrayAndResultTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -15,6 +15,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import sop.ByteArrayAndResult; import sop.Verification; +import sop.util.UTCUtil; public class ByteArrayAndResultTest { diff --git a/sop-java/src/test/java/sop/util/MicAlgTest.java b/sop-java/src/test/java/sop/MicAlgTest.java similarity index 98% rename from sop-java/src/test/java/sop/util/MicAlgTest.java rename to sop-java/src/test/java/sop/MicAlgTest.java index f720c85..a2cbf73 100644 --- a/sop-java/src/test/java/sop/util/MicAlgTest.java +++ b/sop-java/src/test/java/sop/MicAlgTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/sop-java/src/test/java/sop/util/ReadyTest.java b/sop-java/src/test/java/sop/ReadyTest.java similarity index 97% rename from sop-java/src/test/java/sop/util/ReadyTest.java rename to sop-java/src/test/java/sop/ReadyTest.java index 07fa090..409bfc0 100644 --- a/sop-java/src/test/java/sop/util/ReadyTest.java +++ b/sop-java/src/test/java/sop/ReadyTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertArrayEquals; diff --git a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java b/sop-java/src/test/java/sop/ReadyWithResultTest.java similarity index 97% rename from sop-java/src/test/java/sop/util/ReadyWithResultTest.java rename to sop-java/src/test/java/sop/ReadyWithResultTest.java index 1207c5c..072314a 100644 --- a/sop-java/src/test/java/sop/util/ReadyWithResultTest.java +++ b/sop-java/src/test/java/sop/ReadyWithResultTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,6 +19,7 @@ import sop.ByteArrayAndResult; import sop.ReadyWithResult; import sop.Verification; import sop.exception.SOPGPException; +import sop.util.UTCUtil; public class ReadyWithResultTest { diff --git a/sop-java/src/test/java/sop/util/SessionKeyTest.java b/sop-java/src/test/java/sop/SessionKeyTest.java similarity index 98% rename from sop-java/src/test/java/sop/util/SessionKeyTest.java rename to sop-java/src/test/java/sop/SessionKeyTest.java index db7e89f..7737a29 100644 --- a/sop-java/src/test/java/sop/util/SessionKeyTest.java +++ b/sop-java/src/test/java/sop/SessionKeyTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import sop.SessionKey; +import sop.util.HexUtil; public class SessionKeyTest { diff --git a/sop-java/src/test/java/sop/util/SigningResultTest.java b/sop-java/src/test/java/sop/SigningResultTest.java similarity index 96% rename from sop-java/src/test/java/sop/util/SigningResultTest.java rename to sop-java/src/test/java/sop/SigningResultTest.java index 0d35cdc..a2f2d77 100644 --- a/sop-java/src/test/java/sop/util/SigningResultTest.java +++ b/sop-java/src/test/java/sop/SigningResultTest.java @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import static org.junit.jupiter.api.Assertions.assertEquals; From aa88904711becc0ae087e1be47251b3780bb416c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:24:59 +0200 Subject: [PATCH 180/444] Add tests for Verification parsing --- .../java/sop/{util => }/VerificationTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) rename sop-java/src/test/java/sop/{util => }/VerificationTest.java (83%) diff --git a/sop-java/src/test/java/sop/util/VerificationTest.java b/sop-java/src/test/java/sop/VerificationTest.java similarity index 83% rename from sop-java/src/test/java/sop/util/VerificationTest.java rename to sop-java/src/test/java/sop/VerificationTest.java index c4f864e..c0b0cbc 100644 --- a/sop-java/src/test/java/sop/util/VerificationTest.java +++ b/sop-java/src/test/java/sop/VerificationTest.java @@ -2,17 +2,19 @@ // // SPDX-License-Identifier: Apache-2.0 -package sop.util; +package sop; import org.junit.jupiter.api.Test; import sop.Verification; import sop.enums.SignatureMode; import sop.testsuite.assertions.VerificationAssert; +import sop.util.UTCUtil; import java.text.ParseException; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class VerificationTest { @@ -89,4 +91,20 @@ public class VerificationTest { .hasMode(null) .hasDescription("certificate from dkg.asc"); } + + @Test + public void missingFingerprintFails() { + String string = "2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209"; + assertThrows(IllegalArgumentException.class, () -> Verification.fromString(string)); + } + + @Test + public void malformedTimestampFails() { + String shorter = "'99-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B"; + assertThrows(IllegalArgumentException.class, () -> Verification.fromString(shorter)); + + String longer = "'99-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B mode:binary certificate from dkg.asc"; + assertThrows(IllegalArgumentException.class, () -> Verification.fromString(longer)); + + } } From c479cc8ef3ac2ec4e1da810379fd0ac426a566d7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:25:16 +0200 Subject: [PATCH 181/444] Profile: Use Optional for description --- .../operation/ListProfilesExternal.java | 6 +- sop-java/src/main/java/sop/Profile.java | 71 +++++++++++- sop-java/src/test/java/sop/ProfileTest.java | 101 ++++++++++++++++++ 3 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 sop-java/src/test/java/sop/ProfileTest.java diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java index adc0e11..0c76b63 100644 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -38,8 +38,10 @@ public class ListProfilesExternal implements ListProfiles { private static List toProfiles(String output) { List profiles = new ArrayList<>(); for (String line : output.split("\n")) { - String[] split = line.split(": "); - profiles.add(new Profile(split[0], split[1])); + if (line.trim().isEmpty()) { + continue; + } + profiles.add(Profile.parse(line)); } return profiles; } diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java index 4a981f4..4ea9e71 100644 --- a/sop-java/src/main/java/sop/Profile.java +++ b/sop-java/src/main/java/sop/Profile.java @@ -4,8 +4,12 @@ package sop; +import sop.util.Optional; import sop.util.UTF8Util; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * Tuple class bundling a profile name and description. * @@ -15,7 +19,7 @@ import sop.util.UTF8Util; public class Profile { private final String name; - private final String description; + private final Optional description; /** * Create a new {@link Profile} object. @@ -24,15 +28,62 @@ public class Profile { * @param name profile name * @param description profile description */ - public Profile(String name, String description) { + public Profile(@Nonnull String name, @Nullable String description) { + if (name.trim().isEmpty()) { + throw new IllegalArgumentException("Name cannot be empty."); + } + if (name.contains(":")) { + throw new IllegalArgumentException("Name cannot contain ':'."); + } + if (name.contains(" ") || name.contains("\n") || name.contains("\t") || name.contains("\r")) { + throw new IllegalArgumentException("Name cannot contain whitespace characters."); + } + this.name = name; - this.description = description; + + if (description == null) { + this.description = Optional.ofEmpty(); + } else { + String trimmedDescription = description.trim(); + if (trimmedDescription.isEmpty()) { + this.description = Optional.ofEmpty(); + } else { + this.description = Optional.of(trimmedDescription); + } + } if (exceeds1000CharLineLimit(this)) { throw new IllegalArgumentException("The line representation of a profile MUST NOT exceed 1000 bytes."); } } + public Profile(String name) { + this(name, null); + } + + /** + * Parse a {@link Profile} from its string representation. + * + * @param string string representation + * @return profile + */ + public static Profile parse(String string) { + if (string.contains(": ")) { + // description after colon, e.g. "default: Use implementers recommendations." + String name = string.substring(0, string.indexOf(": ")); + String description = string.substring(string.indexOf(": ") + 2); + return new Profile(name, description.trim()); + } + + if (string.endsWith(":")) { + // empty description, e.g. "default:" + return new Profile(string.substring(0, string.length() - 1)); + } + + // no description + return new Profile(string.trim()); + } + /** * Return the name (also known as identifier) of the profile. * A profile name is a UTF-8 string that has no whitespace in it. @@ -48,6 +99,7 @@ public class Profile { * * @return name */ + @Nonnull public String getName() { return name; } @@ -57,17 +109,26 @@ public class Profile { * * @return description */ - public String getDescription() { + @Nonnull + public Optional getDescription() { return description; } + public boolean hasDescription() { + return description.isPresent(); + } + /** * Convert the profile into a String for displaying. * * @return string */ + @Override public String toString() { - return getName() + ": " + getDescription(); + if (getDescription().isEmpty()) { + return getName(); + } + return getName() + ": " + getDescription().get(); } /** diff --git a/sop-java/src/test/java/sop/ProfileTest.java b/sop-java/src/test/java/sop/ProfileTest.java new file mode 100644 index 0000000..d76d270 --- /dev/null +++ b/sop-java/src/test/java/sop/ProfileTest.java @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop; + +import org.junit.jupiter.api.Test; +import sop.Profile; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ProfileTest { + + @Test + public void toStringFull() { + Profile profile = new Profile("default", "Use the implementers recommendations."); + assertEquals("default: Use the implementers recommendations.", profile.toString()); + } + + @Test + public void toStringNameOnly() { + Profile profile = new Profile("default"); + assertEquals("default", profile.toString()); + } + + @Test + public void parseFull() { + String string = "default: Use the implementers recommendations."; + Profile profile = Profile.parse(string); + assertEquals("default", profile.getName()); + assertTrue(profile.hasDescription()); + assertEquals("Use the implementers recommendations.", profile.getDescription().get()); + } + + @Test + public void parseNameOnly() { + String string = "rfc4880"; + Profile profile = Profile.parse(string); + assertEquals("rfc4880", profile.getName()); + assertFalse(profile.hasDescription()); + } + + @Test + public void parseEmptyDescription() { + String string = "rfc4880: "; + Profile profile = Profile.parse(string); + assertEquals("rfc4880", profile.getName()); + assertFalse(profile.hasDescription()); + + string = "rfc4880:"; + profile = Profile.parse(string); + assertEquals("rfc4880", profile.getName()); + assertFalse(profile.hasDescription()); + } + + @Test + public void parseTooLongProfile() { + // 1200 chars + String string = "longDescription: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; + assertThrows(IllegalArgumentException.class, () -> Profile.parse(string)); + } + + @Test + public void constructTooLongProfile() { + // name + description = 1200 chars + String name = "longDescription"; + String description = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; + assertThrows(IllegalArgumentException.class, () -> new Profile(name, description)); + } + + @Test + public void nameCannotBeEmpty() { + assertThrows(IllegalArgumentException.class, () -> new Profile("")); + assertThrows(IllegalArgumentException.class, () -> new Profile(""), "Description Text."); + } + + @Test + public void nameCannotContainColons() { + assertThrows(IllegalArgumentException.class, () -> new Profile("default:")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default:", "DescriptionText")); + assertThrows(IllegalArgumentException.class, () -> new Profile("rfc:4880")); + assertThrows(IllegalArgumentException.class, () -> new Profile("rfc:4880", "OpenPGP Message Format")); + } + + @Test + public void nameCannotContainWhitespace() { + assertThrows(IllegalArgumentException.class, () -> new Profile("default profile")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default profile", "With description.")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\nprofile")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\nprofile", "With description")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\tprofile")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\tprofile", "With description")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\r\nprofile")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\r\nprofile", "With description")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\rprofile")); + assertThrows(IllegalArgumentException.class, () -> new Profile("default\rprofile", "With description")); + } +} From 312cdb69c92e30a41d1fdb059de2f8f0a47c7b93 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:28:07 +0200 Subject: [PATCH 182/444] Update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9bc020..b78eafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,13 @@ SPDX-License-Identifier: Apache-2.0 ## 6.1.0-SNAPSHOT - `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` - Add DSL for testing `Verification` results -- `Verification`: Return `Optional<>` for `getSignatureMode()` and `getDescription()` +- `Verification` + - Return `Optional` for `getSignatureMode()` + - Return `Optional` for `getDescription()` +- `Profile` + - Add support for profiles without description + - Return `Optional` for `getDescription()` + - Add `parse(String)` method for parsing profile lines - `sop-java`: Add dependency on `com.google.code.findbugs:jsr305` for `@Nullable`, `@Nonnull` annotations - `UTCUtil`: `parseUTCDate()` is now `@Nonnull` and throws a `ParseException` for invalid inputs - `UTF8Util`: `decodeUTF8()` now throws `CharacterCodingException` instead of `SOPGPException.PasswordNotHumanReadable` From 419056ba4c391cef4dc23e28e133d96727631bf5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:32:13 +0200 Subject: [PATCH 183/444] Fix checkstyle issues --- .../src/test/java/sop/ByteArrayAndResultTest.java | 10 ++++------ sop-java/src/test/java/sop/MicAlgTest.java | 11 +++++------ sop-java/src/test/java/sop/ProfileTest.java | 1 - sop-java/src/test/java/sop/ReadyTest.java | 5 ++--- sop-java/src/test/java/sop/ReadyWithResultTest.java | 13 +++++-------- sop-java/src/test/java/sop/SessionKeyTest.java | 7 +++---- sop-java/src/test/java/sop/SigningResultTest.java | 6 ++---- sop-java/src/test/java/sop/VerificationTest.java | 1 - 8 files changed, 21 insertions(+), 33 deletions(-) diff --git a/sop-java/src/test/java/sop/ByteArrayAndResultTest.java b/sop-java/src/test/java/sop/ByteArrayAndResultTest.java index 8d89842..9f9d9ce 100644 --- a/sop-java/src/test/java/sop/ByteArrayAndResultTest.java +++ b/sop-java/src/test/java/sop/ByteArrayAndResultTest.java @@ -4,18 +4,16 @@ package sop; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import sop.util.UTCUtil; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.Test; -import sop.ByteArrayAndResult; -import sop.Verification; -import sop.util.UTCUtil; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ByteArrayAndResultTest { diff --git a/sop-java/src/test/java/sop/MicAlgTest.java b/sop-java/src/test/java/sop/MicAlgTest.java index a2cbf73..16f54ef 100644 --- a/sop-java/src/test/java/sop/MicAlgTest.java +++ b/sop-java/src/test/java/sop/MicAlgTest.java @@ -4,16 +4,15 @@ package sop; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; -import org.junit.jupiter.api.Test; -import sop.MicAlg; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class MicAlgTest { diff --git a/sop-java/src/test/java/sop/ProfileTest.java b/sop-java/src/test/java/sop/ProfileTest.java index d76d270..564a6af 100644 --- a/sop-java/src/test/java/sop/ProfileTest.java +++ b/sop-java/src/test/java/sop/ProfileTest.java @@ -5,7 +5,6 @@ package sop; import org.junit.jupiter.api.Test; -import sop.Profile; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/sop-java/src/test/java/sop/ReadyTest.java b/sop-java/src/test/java/sop/ReadyTest.java index 409bfc0..49c297b 100644 --- a/sop-java/src/test/java/sop/ReadyTest.java +++ b/sop-java/src/test/java/sop/ReadyTest.java @@ -4,14 +4,13 @@ package sop; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; -import sop.Ready; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; public class ReadyTest { diff --git a/sop-java/src/test/java/sop/ReadyWithResultTest.java b/sop-java/src/test/java/sop/ReadyWithResultTest.java index 072314a..88231dc 100644 --- a/sop-java/src/test/java/sop/ReadyWithResultTest.java +++ b/sop-java/src/test/java/sop/ReadyWithResultTest.java @@ -4,8 +4,9 @@ package sop; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import sop.exception.SOPGPException; +import sop.util.UTCUtil; import java.io.IOException; import java.io.OutputStream; @@ -14,12 +15,8 @@ import java.text.ParseException; import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.Test; -import sop.ByteArrayAndResult; -import sop.ReadyWithResult; -import sop.Verification; -import sop.exception.SOPGPException; -import sop.util.UTCUtil; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ReadyWithResultTest { diff --git a/sop-java/src/test/java/sop/SessionKeyTest.java b/sop-java/src/test/java/sop/SessionKeyTest.java index 7737a29..6dd3c90 100644 --- a/sop-java/src/test/java/sop/SessionKeyTest.java +++ b/sop-java/src/test/java/sop/SessionKeyTest.java @@ -4,14 +4,13 @@ package sop; +import org.junit.jupiter.api.Test; +import sop.util.HexUtil; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.jupiter.api.Test; -import sop.SessionKey; -import sop.util.HexUtil; - public class SessionKeyTest { @Test diff --git a/sop-java/src/test/java/sop/SigningResultTest.java b/sop-java/src/test/java/sop/SigningResultTest.java index a2f2d77..b0e1afd 100644 --- a/sop-java/src/test/java/sop/SigningResultTest.java +++ b/sop-java/src/test/java/sop/SigningResultTest.java @@ -4,11 +4,9 @@ package sop; -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.Test; -import sop.MicAlg; -import sop.SigningResult; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class SigningResultTest { diff --git a/sop-java/src/test/java/sop/VerificationTest.java b/sop-java/src/test/java/sop/VerificationTest.java index c0b0cbc..e956435 100644 --- a/sop-java/src/test/java/sop/VerificationTest.java +++ b/sop-java/src/test/java/sop/VerificationTest.java @@ -5,7 +5,6 @@ package sop; import org.junit.jupiter.api.Test; -import sop.Verification; import sop.enums.SignatureMode; import sop.testsuite.assertions.VerificationAssert; import sop.util.UTCUtil; From ab8f44138d0042fc611e4ee4e14304d626a12acc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:45:42 +0200 Subject: [PATCH 184/444] Add missing @throws javadoc --- sop-java/src/main/java/sop/util/UTCUtil.java | 1 + sop-java/src/main/java/sop/util/UTF8Util.java | 1 + 2 files changed, 2 insertions(+) diff --git a/sop-java/src/main/java/sop/util/UTCUtil.java b/sop-java/src/main/java/sop/util/UTCUtil.java index 3849258..96a6b9c 100644 --- a/sop-java/src/main/java/sop/util/UTCUtil.java +++ b/sop-java/src/main/java/sop/util/UTCUtil.java @@ -33,6 +33,7 @@ public class UTCUtil { * * @param dateString string * @return date + * @throws ParseException if the date string is malformed and cannot be parsed */ @Nonnull public static Date parseUTCDate(String dateString) throws ParseException { diff --git a/sop-java/src/main/java/sop/util/UTF8Util.java b/sop-java/src/main/java/sop/util/UTF8Util.java index a59ff43..1b4941b 100644 --- a/sop-java/src/main/java/sop/util/UTF8Util.java +++ b/sop-java/src/main/java/sop/util/UTF8Util.java @@ -26,6 +26,7 @@ public class UTF8Util { * @param data utf-8 encoded bytes * * @return decoded string + * @throws CharacterCodingException if the input data does not resemble UTF8 */ public static String decodeUTF8(byte[] data) throws CharacterCodingException { From b8ad6d77a25a6ab41a5b8c28eb242439ea134a27 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:46:52 +0200 Subject: [PATCH 185/444] SOP-Java 6.1.0 --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b78eafc..6d15e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 6.1.0-SNAPSHOT +## 6.1.0 - `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` - Add DSL for testing `Verification` results - `Verification` diff --git a/version.gradle b/version.gradle index 095db6c..092f937 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '6.0.1' - isSnapshot = true + shortVersion = '6.1.0' + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 7ab65f63a4aac669f5acea98c13960adfc34b05e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 14:49:13 +0200 Subject: [PATCH 186/444] SOP-Java 6.1.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 092f937..ca55347 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '6.1.0' - isSnapshot = false + shortVersion = '6.1.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From bfaba69222d7c18ef2a2a931c8a57d618b34a323 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 21:18:37 +0200 Subject: [PATCH 187/444] Add Dearmor.data(String) default method --- .../src/main/java/sop/operation/Dearmor.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/sop-java/src/main/java/sop/operation/Dearmor.java b/sop-java/src/main/java/sop/operation/Dearmor.java index 380c4fc..524dc8c 100644 --- a/sop-java/src/main/java/sop/operation/Dearmor.java +++ b/sop-java/src/main/java/sop/operation/Dearmor.java @@ -10,6 +10,7 @@ import java.io.InputStream; import sop.Ready; import sop.exception.SOPGPException; +import sop.util.UTF8Util; public interface Dearmor { @@ -19,7 +20,7 @@ public interface Dearmor { * @param data armored OpenPGP data * @return input stream of unarmored data * - * @throws sop.exception.SOPGPException.BadData in case of non-OpenPGP data + * @throws SOPGPException.BadData in case of non-OpenPGP data * @throws IOException in case of an IO error */ Ready data(InputStream data) @@ -32,7 +33,7 @@ public interface Dearmor { * @param data armored OpenPGP data * @return input stream of unarmored data * - * @throws sop.exception.SOPGPException.BadData in case of non-OpenPGP data + * @throws SOPGPException.BadData in case of non-OpenPGP data * @throws IOException in case of an IO error */ default Ready data(byte[] data) @@ -40,4 +41,19 @@ public interface Dearmor { IOException { return data(new ByteArrayInputStream(data)); } + + /** + * Dearmor amored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * + * @throws SOPGPException.BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + default Ready data(String data) + throws SOPGPException.BadData, + IOException { + return data(data.getBytes(UTF8Util.UTF8)); + } } From f65ddba4b40786268ed065770879303eb3bd4cf2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 21:27:51 +0200 Subject: [PATCH 188/444] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d15e9a..ec9e7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 6.1.1-SNAPSHOT +- Add `dearmor.data(String)` utility method + ## 6.1.0 - `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` - Add DSL for testing `Verification` results From ab2e4aa8e7b0cd5b000aeb05b1ffd1413a90babc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 22:41:58 +0200 Subject: [PATCH 189/444] Bump version to 7.0.0-SNAPSHOT --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index ca55347..7614d8f 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '6.1.1' + shortVersion = '7.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From e6393b44b984a83a020daa17c99c29e7dcc26d85 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 22:42:16 +0200 Subject: [PATCH 190/444] Initial implementation of 'revoke-key' command --- .../main/java/sop/external/ExternalSOP.java | 6 ++ .../src/main/java/sop/cli/picocli/SopCLI.java | 2 + .../cli/picocli/commands/RevokeKeyCmd.java | 62 +++++++++++++++++++ .../main/resources/msg_revoke-key.properties | 9 +++ .../resources/msg_revoke-key_de.properties | 9 +++ .../test/java/sop/cli/picocli/SOPTest.java | 6 ++ sop-java/src/main/java/sop/SOP.java | 8 +++ .../main/java/sop/operation/RevokeKey.java | 49 +++++++++++++++ 8 files changed, 151 insertions(+) create mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java create mode 100644 sop-java-picocli/src/main/resources/msg_revoke-key.properties create mode 100644 sop-java-picocli/src/main/resources/msg_revoke-key_de.properties create mode 100644 sop-java/src/main/java/sop/operation/RevokeKey.java diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index bc5eb46..189b648 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -32,6 +32,7 @@ import sop.operation.InlineDetach; import sop.operation.InlineSign; import sop.operation.InlineVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; import javax.annotation.Nonnull; @@ -161,6 +162,11 @@ public class ExternalSOP implements SOP { return new ListProfilesExternal(binaryName, properties); } + @Override + public RevokeKey revokeKey() { + return null; + } + @Override public Dearmor dearmor() { return new DearmorExternal(binaryName, properties); diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index fab99e0..2f98061 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -17,6 +17,7 @@ import sop.cli.picocli.commands.GenerateKeyCmd; import sop.cli.picocli.commands.InlineSignCmd; import sop.cli.picocli.commands.InlineVerifyCmd; import sop.cli.picocli.commands.ListProfilesCmd; +import sop.cli.picocli.commands.RevokeKeyCmd; import sop.cli.picocli.commands.SignCmd; import sop.cli.picocli.commands.VerifyCmd; import sop.cli.picocli.commands.VersionCmd; @@ -43,6 +44,7 @@ import java.util.ResourceBundle; InlineSignCmd.class, InlineVerifyCmd.class, ListProfilesCmd.class, + RevokeKeyCmd.class, VersionCmd.class, AutoComplete.GenerateCompletion.class } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java new file mode 100644 index 0000000..28d1890 --- /dev/null +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import picocli.CommandLine; +import sop.Ready; +import sop.cli.picocli.SopCLI; +import sop.exception.SOPGPException; +import sop.operation.RevokeKey; + +import java.io.IOException; + +@CommandLine.Command(name = "revoke-key", + resourceBundle = "msg_revoke-key", + exitCodeOnInvalidInput = 37) +public class RevokeKeyCmd extends AbstractSopCmd { + + @CommandLine.Option(names = "--no-armor", + negatable = true) + boolean armor = true; + + @CommandLine.Option(names = "--with-key-password", + paramLabel = "PASSWORD") + String withKeyPassword; + + @Override + public void run() { + RevokeKey revokeKey = throwIfUnsupportedSubcommand( + SopCLI.getSop().revokeKey(), "revoke-key"); + + if (!armor) { + revokeKey.noArmor(); + } + + if (withKeyPassword != null) { + try { + String password = stringFromInputStream(getInput(withKeyPassword)); + revokeKey.withKeyPassword(password); + } catch (SOPGPException.UnsupportedOption e) { + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); + throw new SOPGPException.UnsupportedOption(errorMsg, e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + Ready ready; + try { + ready = revokeKey.keys(System.in); + } catch (SOPGPException.KeyIsProtected e) { + String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", "STANDARD_IN"); + throw new SOPGPException.KeyIsProtected(errorMsg, e); + } + try { + ready.writeTo(System.out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties new file mode 100644 index 0000000..e90cd48 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Generate revocation certificate for private keys +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties new file mode 100644 index 0000000..f073213 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Erzeuge Widerrufszertifikate für private Schlüssel +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index c05440e..67a951c 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -27,6 +27,7 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; public class SOPTest { @@ -101,6 +102,11 @@ public class SOPTest { return null; } + @Override + public RevokeKey revokeKey() { + return null; + } + @Override public InlineDetach inlineDetach() { return null; diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 804cd28..460e6c1 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -16,6 +16,7 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.RevokeKey; import sop.operation.Version; /** @@ -158,4 +159,11 @@ public interface SOP { * @return builder instance */ ListProfiles listProfiles(); + + /** + * Revoke one or more secret keys. + * + * @return builder instance + */ + RevokeKey revokeKey(); } diff --git a/sop-java/src/main/java/sop/operation/RevokeKey.java b/sop-java/src/main/java/sop/operation/RevokeKey.java new file mode 100644 index 0000000..e2aa433 --- /dev/null +++ b/sop-java/src/main/java/sop/operation/RevokeKey.java @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.util.UTF8Util; + +import java.io.InputStream; + +public interface RevokeKey { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + RevokeKey noArmor(); + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords + * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable + */ + default RevokeKey withKeyPassword(String password) + throws SOPGPException.UnsupportedOption, + SOPGPException.PasswordNotHumanReadable { + return withKeyPassword(password.getBytes(UTF8Util.UTF8)); + } + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords + * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable + */ + RevokeKey withKeyPassword(byte[] password) + throws SOPGPException.UnsupportedOption, + SOPGPException.PasswordNotHumanReadable; + + Ready keys(InputStream keys); +} From 618d123a7beaf0b448afee6835851c07a7395997 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Jul 2023 22:58:39 +0200 Subject: [PATCH 191/444] Add RevokeKeyExternal implementation and some basic tests --- .../main/java/sop/external/ExternalSOP.java | 3 +- .../external/operation/RevokeKeyExternal.java | 48 ++++++++++++ .../operation/ExternalRevokeKeyTest.java | 11 +++ .../main/java/sop/operation/RevokeKey.java | 5 ++ .../testsuite/operation/RevokeKeyTest.java | 77 +++++++++++++++++++ 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 189b648..c3041ae 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -19,6 +19,7 @@ import sop.external.operation.InlineDetachExternal; import sop.external.operation.InlineSignExternal; import sop.external.operation.InlineVerifyExternal; import sop.external.operation.ListProfilesExternal; +import sop.external.operation.RevokeKeyExternal; import sop.external.operation.VersionExternal; import sop.operation.Armor; import sop.operation.Dearmor; @@ -164,7 +165,7 @@ public class ExternalSOP implements SOP { @Override public RevokeKey revokeKey() { - return null; + return new RevokeKeyExternal(binaryName, properties); } @Override diff --git a/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java b/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java new file mode 100644 index 0000000..e7aad9d --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.RevokeKey; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class RevokeKeyExternal implements RevokeKey { + + private final List commandList = new ArrayList<>(); + private final List envList; + + private int withKeyPasswordCounter = 0; + + public RevokeKeyExternal(String binary, Properties environment) { + this.commandList.add(binary); + this.commandList.add("revoke-key"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public RevokeKey noArmor() { + this.commandList.add("--no-armor"); + return this; + } + + @Override + public RevokeKey withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; + commandList.add("--with-key-password=@ENV:" + envVar); + envList.add(envVar + "=" + new String(password)); + return this; + } + + @Override + public Ready keys(InputStream keys) { + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keys); + } +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java new file mode 100644 index 0000000..2318abd --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import sop.testsuite.operation.RevokeKeyTest; + +public class ExternalRevokeKeyTest extends RevokeKeyTest { + +} diff --git a/sop-java/src/main/java/sop/operation/RevokeKey.java b/sop-java/src/main/java/sop/operation/RevokeKey.java index e2aa433..3ceb5b3 100644 --- a/sop-java/src/main/java/sop/operation/RevokeKey.java +++ b/sop-java/src/main/java/sop/operation/RevokeKey.java @@ -8,6 +8,7 @@ import sop.Ready; import sop.exception.SOPGPException; import sop.util.UTF8Util; +import java.io.ByteArrayInputStream; import java.io.InputStream; public interface RevokeKey { @@ -45,5 +46,9 @@ public interface RevokeKey { throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable; + default Ready keys(byte[] bytes) { + return keys(new ByteArrayInputStream(bytes)); + } + Ready keys(InputStream keys); } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java new file mode 100644 index 0000000..920f877 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; +import sop.exception.SOPGPException; +import sop.util.UTF8Util; + +import java.io.IOException; +import java.util.Arrays; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class RevokeKeyTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeUnprotectedKey(SOP sop) throws IOException { + byte[] secretKey = sop.generateKey().userId("Alice ").generate().getBytes(); + byte[] revocation = sop.revokeKey().keys(secretKey).getBytes(); + + assertFalse(Arrays.equals(secretKey, revocation)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeProtectedKey(SOP sop) throws IOException { + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] revocation = sop.revokeKey().withKeyPassword(password).keys(secretKey).getBytes(); + + assertFalse(Arrays.equals(secretKey, revocation)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeProtectedKeyWithMultiplePasswordOptions(SOP sop) throws IOException { + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); + byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] revocation = sop.revokeKey().withKeyPassword(wrongPassword).withKeyPassword(password).keys(secretKey).getBytes(); + + assertFalse(Arrays.equals(secretKey, revocation)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeProtectedKeyWithMissingPassphraseFails(SOP sop) throws IOException { + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + + assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().keys(secretKey).getBytes()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeProtectedKeyWithWrongPassphraseFails(SOP sop) throws IOException { + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + String wrongPassword = "or4ng3"; + byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + + assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().withKeyPassword(wrongPassword).keys(secretKey).getBytes()); + } +} From 7e1377a28cb15eb42d2386757346f9f8688eb405 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 00:42:02 +0200 Subject: [PATCH 192/444] Initial implementation of 'change-key-password' command of SOP-07 --- .../main/java/sop/external/ExternalSOP.java | 7 ++ .../operation/ChangeKeyPasswordExternal.java | 57 +++++++++++ .../operation/ExternalRevokeKeyTest.java | 2 + .../src/main/java/sop/cli/picocli/SopCLI.java | 2 + .../commands/ChangeKeyPasswordCmd.java | 54 ++++++++++ .../test/java/sop/cli/picocli/SOPTest.java | 6 ++ sop-java/src/main/java/sop/SOP.java | 8 ++ .../java/sop/operation/ChangeKeyPassword.java | 83 ++++++++++++++++ .../operation/ChangeKeyPasswordTest.java | 98 +++++++++++++++++++ 9 files changed, 317 insertions(+) create mode 100644 external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java create mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java create mode 100644 sop-java/src/main/java/sop/operation/ChangeKeyPassword.java create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index c3041ae..376e54c 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -8,6 +8,7 @@ import sop.Ready; import sop.SOP; import sop.exception.SOPGPException; import sop.external.operation.ArmorExternal; +import sop.external.operation.ChangeKeyPasswordExternal; import sop.external.operation.DearmorExternal; import sop.external.operation.DecryptExternal; import sop.external.operation.DetachedSignExternal; @@ -22,6 +23,7 @@ import sop.external.operation.ListProfilesExternal; import sop.external.operation.RevokeKeyExternal; import sop.external.operation.VersionExternal; import sop.operation.Armor; +import sop.operation.ChangeKeyPassword; import sop.operation.Dearmor; import sop.operation.Decrypt; import sop.operation.DetachedSign; @@ -168,6 +170,11 @@ public class ExternalSOP implements SOP { return new RevokeKeyExternal(binaryName, properties); } + @Override + public ChangeKeyPassword changeKeyPassword() { + return new ChangeKeyPasswordExternal(binaryName, properties); + } + @Override public Dearmor dearmor() { return new DearmorExternal(binaryName, properties); diff --git a/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java b/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java new file mode 100644 index 0000000..210152f --- /dev/null +++ b/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.external.ExternalSOP; +import sop.operation.ChangeKeyPassword; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class ChangeKeyPasswordExternal implements ChangeKeyPassword { + private final List commandList = new ArrayList<>(); + private final List envList; + + private int keyPasswordCounter = 0; + + public ChangeKeyPasswordExternal(String binary, Properties environment) { + this.commandList.add(binary); + this.commandList.add("decrypt"); + this.envList = ExternalSOP.propertiesToEnv(environment); + } + + @Override + public ChangeKeyPassword noArmor() { + this.commandList.add("--no-armor"); + return this; + } + + @Override + public ChangeKeyPassword oldKeyPassphrase(String oldPassphrase) { + this.commandList.add("--old-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); + this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + oldPassphrase); + keyPasswordCounter++; + + return this; + } + + @Override + public ChangeKeyPassword newKeyPassphrase(String newPassphrase) { + this.commandList.add("--new-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); + this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + newPassphrase); + keyPasswordCounter++; + + return this; + } + + @Override + public Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { + return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, inputStream); + } +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java index 2318abd..e2efe03 100644 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java @@ -4,8 +4,10 @@ package sop.testsuite.external.operation; +import org.junit.jupiter.api.condition.EnabledIf; import sop.testsuite.operation.RevokeKeyTest; +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class ExternalRevokeKeyTest extends RevokeKeyTest { } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 2f98061..e80c887 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -8,6 +8,7 @@ import picocli.AutoComplete; import picocli.CommandLine; import sop.SOP; import sop.cli.picocli.commands.ArmorCmd; +import sop.cli.picocli.commands.ChangeKeyPasswordCmd; import sop.cli.picocli.commands.DearmorCmd; import sop.cli.picocli.commands.DecryptCmd; import sop.cli.picocli.commands.InlineDetachCmd; @@ -45,6 +46,7 @@ import java.util.ResourceBundle; InlineVerifyCmd.class, ListProfilesCmd.class, RevokeKeyCmd.class, + ChangeKeyPasswordCmd.class, VersionCmd.class, AutoComplete.GenerateCompletion.class } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java new file mode 100644 index 0000000..2f44ec7 --- /dev/null +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands; + +import picocli.CommandLine; +import sop.cli.picocli.SopCLI; +import sop.exception.SOPGPException; +import sop.operation.ChangeKeyPassword; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@CommandLine.Command(name = "change-key-password", + resourceBundle = "msg_dearmor", // TODO + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +public class ChangeKeyPasswordCmd extends AbstractSopCmd { + + @CommandLine.Option(names = "--no-armor", + negatable = true) + boolean armor = true; + + @CommandLine.Option(names = {"--old-key-password"}) + List oldKeyPasswords = new ArrayList<>(); + + @CommandLine.Option(names = {"--new-key-password"}, arity = "0..1") + String newKeyPassword = null; + + @Override + public void run() { + ChangeKeyPassword changeKeyPassword = throwIfUnsupportedSubcommand( + SopCLI.getSop().changeKeyPassword(), "change-key-password"); + + if (!armor) { + changeKeyPassword.noArmor(); + } + + for (String oldKeyPassword : oldKeyPasswords) { + changeKeyPassword.oldKeyPassphrase(oldKeyPassword); + } + + if (newKeyPassword != null) { + changeKeyPassword.newKeyPassphrase(newKeyPassword); + } + + try { + changeKeyPassword.keys(System.in).writeTo(System.out); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 67a951c..47b6123 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; import sop.operation.Armor; +import sop.operation.ChangeKeyPassword; import sop.operation.Dearmor; import sop.operation.Decrypt; import sop.operation.InlineDetach; @@ -107,6 +108,11 @@ public class SOPTest { return null; } + @Override + public ChangeKeyPassword changeKeyPassword() { + return null; + } + @Override public InlineDetach inlineDetach() { return null; diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 460e6c1..1200e21 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -5,6 +5,7 @@ package sop; import sop.operation.Armor; +import sop.operation.ChangeKeyPassword; import sop.operation.Dearmor; import sop.operation.Decrypt; import sop.operation.Encrypt; @@ -166,4 +167,11 @@ public interface SOP { * @return builder instance */ RevokeKey revokeKey(); + + /** + * Update a key's password. + * + * @return builder instance + */ + ChangeKeyPassword changeKeyPassword(); } diff --git a/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java b/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java new file mode 100644 index 0000000..460a20a --- /dev/null +++ b/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation; + +import sop.Ready; +import sop.exception.SOPGPException; +import sop.util.UTF8Util; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.CharacterCodingException; + +public interface ChangeKeyPassword { + + /** + * Disable ASCII armoring of the output. + * + * @return builder instance + */ + ChangeKeyPassword noArmor(); + + default ChangeKeyPassword oldKeyPassphrase(byte[] password) { + try { + return oldKeyPassphrase(UTF8Util.decodeUTF8(password)); + } catch (CharacterCodingException e) { + throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); + } + } + + /** + * Provide a passphrase to unlock the secret key. + * This method can be provided multiple times to provide separate passphrases that are tried as a + * means to unlock any secret key material encountered. + * + * @param oldPassphrase old passphrase + * @return builder instance + */ + ChangeKeyPassword oldKeyPassphrase(String oldPassphrase); + + /** + * Provide a passphrase to re-lock the secret key with. + * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. + * If this method is not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + default ChangeKeyPassword newKeyPassphrase(byte[] newPassphrase) { + try { + return newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)); + } catch (CharacterCodingException e) { + throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); + } + } + + /** + * Provide a passphrase to re-lock the secret key with. + * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. + * If this method is not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + ChangeKeyPassword newKeyPassphrase(String newPassphrase); + + default Ready keys(byte[] keys) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { + return keys(new ByteArrayInputStream(keys)); + } + + /** + * Provide the key material. + * + * @param inputStream input stream of secret key material + * @return ready + * + * @throws sop.exception.SOPGPException.KeyIsProtected if any (sub-) key encountered cannot be unlocked. + * @throws sop.exception.SOPGPException.BadData if the key material is malformed + */ + Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData; + +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java new file mode 100644 index 0000000..b575047 --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; +import sop.exception.SOPGPException; +import sop.util.UTF8Util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class ChangeKeyPasswordTest extends AbstractSOPTest { + + static Stream provideInstances() { + return AbstractSOPTest.provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void changePasswordFromUnprotectedToProtected(SOP sop) throws IOException { + byte[] unprotectedKey = sop.generateKey().generate().getBytes(); + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] protectedKey = sop.changeKeyPassword().newKeyPassphrase(password).keys(unprotectedKey).getBytes(); + + sop.sign().withKeyPassword(password).key(protectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void changePasswordFromUnprotectedToUnprotected(SOP sop) throws IOException { + byte[] unprotectedKey = sop.generateKey().noArmor().generate().getBytes(); + byte[] stillUnprotectedKey = sop.changeKeyPassword().noArmor().keys(unprotectedKey).getBytes(); + + assertArrayEquals(unprotectedKey, stillUnprotectedKey); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void changePasswordFromProtectedToUnprotected(SOP sop) throws IOException { + byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] protectedKey = sop.generateKey().withKeyPassword(password).generate().getBytes(); + byte[] unprotectedKey = sop.changeKeyPassword() + .oldKeyPassphrase(password) + .keys(protectedKey).getBytes(); + + sop.sign().key(unprotectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void changePasswordFromProtectedToDifferentProtected(SOP sop) throws IOException { + byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); + byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); + byte[] reprotectedKey = sop.changeKeyPassword() + .oldKeyPassphrase(oldPassword) + .newKeyPassphrase(newPassword) + .keys(protectedKey).getBytes(); + + sop.sign().key(reprotectedKey).withKeyPassword(newPassword).data("Test123".getBytes(StandardCharsets.UTF_8)); + } + + + @ParameterizedTest + @MethodSource("provideInstances") + public void changePasswordWithWrongOldPasswordFails(SOP sop) throws IOException { + byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] newPassword = "monkey123".getBytes(UTF8Util.UTF8); + byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); + + byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); + assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.changeKeyPassword() + .oldKeyPassphrase(wrongPassword) + .newKeyPassphrase(newPassword) + .keys(protectedKey).getBytes()); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void nonUtf8PasswordsFail(SOP sop) { + assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> + sop.changeKeyPassword().oldKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); + assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> + sop.changeKeyPassword().newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); + + } +} From 6afe6896d8a8d9cef6dbc79e38dd461c0662a33f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 01:06:41 +0200 Subject: [PATCH 193/444] Implement '--signing-only' option for 'generate-key' command --- .../operation/GenerateKeyExternal.java | 6 ++++++ .../cli/picocli/commands/GenerateKeyCmd.java | 7 +++++++ .../main/java/sop/operation/GenerateKey.java | 7 +++++++ .../testsuite/operation/GenerateKeyTest.java | 21 +++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index e0ca97e..c46dfb3 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -57,6 +57,12 @@ public class GenerateKeyExternal implements GenerateKey { return this; } + @Override + public GenerateKey signingOnly() { + commandList.add("--signing-only"); + return this; + } + @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index aac7124..a41f086 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -34,6 +34,9 @@ public class GenerateKeyCmd extends AbstractSopCmd { paramLabel = "PROFILE") String profile; + @CommandLine.Option(names = "--signing-only") + boolean signingOnly = false; + @Override public void run() { GenerateKey generateKey = throwIfUnsupportedSubcommand( @@ -48,6 +51,10 @@ public class GenerateKeyCmd extends AbstractSopCmd { } } + if (signingOnly) { + generateKey.signingOnly(); + } + for (String userId : userId) { generateKey.userId(userId); } diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java index b788785..77afea1 100644 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ b/sop-java/src/main/java/sop/operation/GenerateKey.java @@ -80,6 +80,13 @@ public interface GenerateKey { */ GenerateKey profile(String profile); + /** + * If this options is set, the generated key will not be capable of encryption / decryption. + * + * @return builder instance + */ + GenerateKey signingOnly(); + /** * Generate the OpenPGP key and return it encoded as an {@link InputStream}. * diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java index ce10763..4a5da58 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java @@ -10,12 +10,16 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import sop.exception.SOPGPException; import sop.testsuite.JUtils; import sop.testsuite.TestData; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertThrows; + @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class GenerateKeyTest extends AbstractSOPTest { @@ -97,4 +101,21 @@ public class GenerateKeyTest extends AbstractSOPTest { JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); JUtils.assertArrayEndsWithIgnoreNewlines(key, TestData.END_PGP_PRIVATE_KEY_BLOCK); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void generateSigningOnlyKey(SOP sop) throws IOException { + byte[] signingOnlyKey = sop.generateKey() + .signingOnly() + .userId("Alice ") + .generate() + .getBytes(); + byte[] signingOnlyCert = sop.extractCert() + .key(signingOnlyKey) + .getBytes(); + + assertThrows(SOPGPException.CertCannotEncrypt.class, () -> + sop.encrypt().withCert(signingOnlyCert) + .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))); + } } From 9ba005f7cc648f2da76d0db294ba2788966af9da Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 01:10:47 +0200 Subject: [PATCH 194/444] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9e7d9..c7c3476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 7.0.0-SNAPSHOT +- Update implementation to [SOP Specification revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html). + - Add support for new `revoke-key` subcommand + - Add support for new `change-key-password` subcommand + - Add support for new `--signing-only` option of `generate-key` subcommand + ## 6.1.1-SNAPSHOT - Add `dearmor.data(String)` utility method From 6a579bff032b41068558b0efbe310563e70a6ba4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 01:13:01 +0200 Subject: [PATCH 195/444] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d130a48..6dd9a81 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 6](https://img.shields.io/badge/Spec%20Revision-6-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/06/) +[![Spec Revision: 7](https://img.shields.io/badge/Spec%20Revision-7-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/07/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) From d1c614344c0b767139b77c3fd62e6e759606fa93 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 01:35:25 +0200 Subject: [PATCH 196/444] Update i18n --- .../picocli/commands/ChangeKeyPasswordCmd.java | 2 +- .../resources/msg_change-key-password.properties | 15 +++++++++++++++ .../msg_change-key-password_de.properties | 15 +++++++++++++++ .../main/resources/msg_generate-key.properties | 1 + .../main/resources/msg_generate-key_de.properties | 1 + .../src/main/resources/msg_revoke-key.properties | 1 + .../main/resources/msg_revoke-key_de.properties | 1 + 7 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 sop-java-picocli/src/main/resources/msg_change-key-password.properties create mode 100644 sop-java-picocli/src/main/resources/msg_change-key-password_de.properties diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java index 2f44ec7..04e0d3a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java @@ -14,7 +14,7 @@ import java.util.ArrayList; import java.util.List; @CommandLine.Command(name = "change-key-password", - resourceBundle = "msg_dearmor", // TODO + resourceBundle = "msg_change-key-password", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ChangeKeyPasswordCmd extends AbstractSopCmd { diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password.properties b/sop-java-picocli/src/main/resources/msg_change-key-password.properties new file mode 100644 index 0000000..effa2d1 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_change-key-password.properties @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Update a key's password +no-armor=ASCII armor the output +new-key-password.0=New password to lock the key(s) with. +new-key-password.1=If no new password is given, the result will be unprotected. +old-key-password.0=Old passwords to unlock the key(s) with. +old-key-password.1=You can provide more than one old password. + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties new file mode 100644 index 0000000..17b829d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2023 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Ändere das Passwort eines Schlüssels +no-armor=Schütze Ausgabe mit ASCII Armor +new-key-password.0=Neues Passwort zur Sicherung des Schlüssels. +new-key-password.1=Falls kein neues Passwort angegeben wird, wird der Schlüssel ungesichert ausgegeben. +old-key-password.0=Altes Passwort zum Entsichern des Schlüssels. +old-key-password.1=Es kann mehr als ein altes Passwort angegeben werden. + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 4647791..e38bb65 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -5,6 +5,7 @@ usage.header=Generate a secret key no-armor=ASCII armor the output USERID[0..*]=User-ID, e.g. "Alice " profile=Profile identifier to switch between profiles +signing-only=Generate a key that is not capable of encryption with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index 41a493b..c5f98e0 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -5,6 +5,7 @@ usage.header=Generiere einen privaten Schl no-armor=Schütze Ausgabe mit ASCII Armor USERID[0..*]=Nutzer-ID, z.B.. "Alice " profile=Profil-Identifikator um zwischen Profilen zu wechseln +signing-only=Generiere einen Schlüssel, der nicht Verschlüsseln kann with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties index e90cd48..d3e59cc 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Generate revocation certificate for private keys +no-armor=ASCII armor the output # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties index f073213..2f7e294 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Erzeuge Widerrufszertifikate für private Schlüssel +no-armor=Schütze Ausgabe mit ASCII Armor # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n From f13aade98ed09988acb29a71b38b9b58004eff5d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:15:47 +0200 Subject: [PATCH 197/444] Add missing parameter labels --- .../java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java | 6 ++++-- .../main/java/sop/cli/picocli/commands/InlineVerifyCmd.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java index 04e0d3a..5a6aa2a 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java @@ -22,10 +22,12 @@ public class ChangeKeyPasswordCmd extends AbstractSopCmd { negatable = true) boolean armor = true; - @CommandLine.Option(names = {"--old-key-password"}) + @CommandLine.Option(names = {"--old-key-password"}, + paramLabel = "PASSWORD") List oldKeyPasswords = new ArrayList<>(); - @CommandLine.Option(names = {"--new-key-password"}, arity = "0..1") + @CommandLine.Option(names = {"--new-key-password"}, arity = "0..1", + paramLabel = "PASSWORD") String newKeyPassword = null; @Override diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index 38ad975..c3b73c5 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -35,7 +35,7 @@ public class InlineVerifyCmd extends AbstractSopCmd { paramLabel = "DATE") String notAfter = "now"; - @CommandLine.Option(names = "--verifications-out") + @CommandLine.Option(names = "--verifications-out", paramLabel = "VERIFICATIONS") String verificationsOut; @Override From 009364b21794afa39eceda633ae0ee254bd5efbf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:17:28 +0200 Subject: [PATCH 198/444] Add missing i18n and fix broken strings --- .../src/main/resources/msg_armor.properties | 1 + .../src/main/resources/msg_armor_de.properties | 1 + .../resources/msg_change-key-password.properties | 14 ++++++++++---- .../msg_change-key-password_de.properties | 14 ++++++++++---- .../src/main/resources/msg_dearmor.properties | 1 + .../src/main/resources/msg_dearmor_de.properties | 1 + .../src/main/resources/msg_decrypt.properties | 16 +++++++++------- .../src/main/resources/msg_decrypt_de.properties | 16 +++++++++------- .../main/resources/msg_detached-sign.properties | 2 ++ .../resources/msg_detached-sign_de.properties | 2 ++ .../resources/msg_detached-verify.properties | 4 +++- .../resources/msg_detached-verify_de.properties | 4 +++- .../src/main/resources/msg_encrypt.properties | 2 ++ .../src/main/resources/msg_encrypt_de.properties | 2 ++ .../main/resources/msg_extract-cert.properties | 1 + .../resources/msg_extract-cert_de.properties | 1 + .../main/resources/msg_generate-key.properties | 4 +++- .../resources/msg_generate-key_de.properties | 4 +++- .../src/main/resources/msg_help.properties | 1 + .../src/main/resources/msg_help_de.properties | 1 + .../main/resources/msg_inline-detach.properties | 1 + .../resources/msg_inline-detach_de.properties | 1 + .../main/resources/msg_inline-sign.properties | 2 ++ .../main/resources/msg_inline-sign_de.properties | 2 ++ .../main/resources/msg_inline-verify.properties | 2 ++ .../resources/msg_inline-verify_de.properties | 2 ++ .../main/resources/msg_list-profiles.properties | 3 +++ .../resources/msg_list-profiles_de.properties | 5 ++++- .../src/main/resources/msg_revoke-key.properties | 8 +++++++- .../main/resources/msg_revoke-key_de.properties | 8 +++++++- .../src/main/resources/msg_version.properties | 1 + .../src/main/resources/msg_version_de.properties | 1 + 32 files changed, 99 insertions(+), 29 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties index 74feb8b..2f4e217 100644 --- a/sop-java-picocli/src/main/resources/msg_armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -4,6 +4,7 @@ usage.header=Add ASCII Armor to standard input label=Label to be used in the header and tail of the armoring +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties index 4dbf43b..a2303e9 100644 --- a/sop-java-picocli/src/main/resources/msg_armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -4,6 +4,7 @@ usage.header=Schütze Standard-Eingabe mit ASCII Armor label=Label für Kopf- und Fußzeile der ASCII Armor +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password.properties b/sop-java-picocli/src/main/resources/msg_change-key-password.properties index effa2d1..0c6eff8 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password.properties @@ -2,13 +2,19 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Update a key's password +usage.description.0=Unlock all secret keys from STDIN using the given old passwords and emit them re-locked using the new password to STDOUT. +usage.description.1=If any (sub-) key cannot be unlocked, this operation will exit with error code 67. no-armor=ASCII armor the output -new-key-password.0=New password to lock the key(s) with. -new-key-password.1=If no new password is given, the result will be unprotected. -old-key-password.0=Old passwords to unlock the key(s) with. -old-key-password.1=You can provide more than one old password. +new-key-password.0=New password to lock the keys with. +new-key-password.1=If no new password is passed in, the keys will be emitted unlocked. +new-key-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +old-key-password.0=Old passwords to unlock the keys with. +old-key-password.1=Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys. +old-key-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties index 17b829d..014c3e7 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties @@ -2,13 +2,19 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Ändere das Passwort eines Schlüssels +usage.description.0=Entsperre alle Schlüssel von Standard-Eingabe mithilfe der alten Passwörter und gebe sie mit dem neuen Passwort gesperrt auf Standard-Ausgabe aus. +usage.description.1=Falls einer oder mehrere (Unter-)Schlüssel nicht entsperrt werden können, gibt diese Operation den Fehlercode 67 aus. no-armor=Schütze Ausgabe mit ASCII Armor -new-key-password.0=Neues Passwort zur Sicherung des Schlüssels. -new-key-password.1=Falls kein neues Passwort angegeben wird, wird der Schlüssel ungesichert ausgegeben. -old-key-password.0=Altes Passwort zum Entsichern des Schlüssels. -old-key-password.1=Es kann mehr als ein altes Passwort angegeben werden. +new-key-password.0=Neues Passwort zur Sperrung der Schlüssel. +new-key-password.1=Falls kein neues Passwort angegeben wird, werden die Schlüssel entsperrt ausgegeben. +new-key-password.2=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +old-key-password.0=Alte Passwörter zum Entsperren der Schlüssel. +old-key-password.1=Mehrere Passwortkandidaten können übergeben werden, welche der Reihe nach durchprobiert werden, um Unterschlüssel zu entsperren. +old-key-password.2=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor.properties b/sop-java-picocli/src/main/resources/msg_dearmor.properties index 734c61a..b088de1 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor.properties @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Remove ASCII Armor from standard input +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties index b601e43..362ccef 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Entferne ASCII Armor von Standard-Eingabe +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index c68a1b8..75adca6 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -11,18 +11,20 @@ with-password.1=Enables decryption based on any "SKESK" packets in the "CIPHERTE with-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). verify-out=Emits signature verification status to the designated output verify-with=Certificates for signature verification -not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -not-before.1=Reject signatures with a creation date not in range. -not-before.2=Defaults to beginning of time ('-'). -not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) -not-after.1=Reject signatures with a creation date not in range. -not-after.2=Defaults to current system time ('now'). -not-after.3=Accepts special value '-' for end of time. +verify-not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +verify-not-before.1=Reject signatures with a creation date not in range. +verify-not-before.2=Defaults to beginning of time ('-'). +verify-not-after.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) +verify-not-after.1=Reject signatures with a creation date not in range. +verify-not-after.2=Defaults to current system time ('now'). +verify-not-after.3=Accepts special value '-' for end of time. with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). KEY[0..*]=Secret keys to attempt decryption with +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index 249e39c..0aefcee 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -11,18 +11,20 @@ with-password.1=Erm with-password.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). verify-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe verify-with=Zertifikate zur Signaturprüfung -not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). -not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) -not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). -not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. +verify-not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +verify-not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +verify-not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). +verify-not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) +verify-not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. +verify-not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +verify-not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index f21b79a..e5ea4e7 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -11,7 +11,9 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index 7c39ebd..2992c02 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -11,7 +11,9 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index ecc75b3..27ba2ad 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -10,9 +10,11 @@ not-after.1=Reject signatures with a creation date not in range. not-after.2=Defaults to current system time ("now"). not-after.3=Accepts special value "-" for end of time. SIGNATURE[0]=Detached signature -CERT[0..*]=Public key certificates for signature verification +CERT[1..*]=Public key certificates for signature verification +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index e13ccba..76adc60 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -10,9 +10,11 @@ not-after.1=Lehne Signaturen mit Erstellungsdatum au not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. SIGNATURE[0]=Abgetrennte Signatur -CERT[0..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung +CERT[1..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index 7c3b218..c0f7f7d 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -12,7 +12,9 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). CERTS[0..*]=Certificates the message gets encrypted to +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index c2ae6ef..6a3055c 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -12,7 +12,9 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index 76ee718..d661d2c 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -4,6 +4,7 @@ usage.header=Extract a public key certificate from a secret key from standard input no-armor=ASCII armor the output +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index ce16f7c..c536b93 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -4,6 +4,7 @@ usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe no-armor=Schütze Ausgabe mit ASCII Armor +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index e38bb65..60ff4a4 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -5,11 +5,13 @@ usage.header=Generate a secret key no-armor=ASCII armor the output USERID[0..*]=User-ID, e.g. "Alice " profile=Profile identifier to switch between profiles -signing-only=Generate a key that is not capable of encryption +signing-only=Generate a key that can only be used for signing with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index c5f98e0..6a0ce13 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -5,11 +5,13 @@ usage.header=Generiere einen privaten Schl no-armor=Schütze Ausgabe mit ASCII Armor USERID[0..*]=Nutzer-ID, z.B.. "Alice " profile=Profil-Identifikator um zwischen Profilen zu wechseln -signing-only=Generiere einen Schlüssel, der nicht Verschlüsseln kann +signing-only=Generiere einen Schlüssel, der nur zum Signieren genutzt werden kann with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_help.properties b/sop-java-picocli/src/main/resources/msg_help.properties index 1146a3e..797cc79 100644 --- a/sop-java-picocli/src/main/resources/msg_help.properties +++ b/sop-java-picocli/src/main/resources/msg_help.properties @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Display usage information for the specified subcommand +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_help_de.properties b/sop-java-picocli/src/main/resources/msg_help_de.properties index 9262114..beea45c 100644 --- a/sop-java-picocli/src/main/resources/msg_help_de.properties +++ b/sop-java-picocli/src/main/resources/msg_help_de.properties @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Zeige Nutzungshilfen für den angegebenen Unterbefehl an +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties index 44f530d..c100c51 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -5,6 +5,7 @@ usage.header=Split signatures from a clearsigned message no-armor=ASCII armor the output signatures-out=Destination to which a detached signatures block will be written +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index 5177a2f..e59aa34 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -5,6 +5,7 @@ usage.header=Trenne Signaturen von Klartext-signierter Nachricht no-armor=Schütze Ausgabe mit ASCII Armor signatures-out=Schreibe abgetrennte Signaturen in Ausgabe +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index 888cbfb..5e7c59e 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -13,7 +13,9 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index d0606d7..9d360d5 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -13,7 +13,9 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index 901fd07..c016e57 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -12,7 +12,9 @@ not-after.3=Accepts special value "-" for end of time. verifications-out=File to write details over successful verifications to CERT[0..*]=Public key certificates for signature verification +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index e5ea22a..3e340c0 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -12,7 +12,9 @@ not-after.3=Akzeptiert speziellen Wert '-' f verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles.properties b/sop-java-picocli/src/main/resources/msg_list-profiles.properties index 71bf5aa..6d5f1a8 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles.properties @@ -3,7 +3,10 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Emit a list of profiles supported by the identified subcommand subcommand=Subcommand for which to list profiles + +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties index e6d8836..ac03c0d 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties @@ -2,8 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden -subcommand=Unterbefehl für welchen Profile gelistet werden sollen +subcommand=Unterbefehl, für welchen Profile gelistet werden sollen + +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties index d3e59cc..c7d72b3 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -1,9 +1,15 @@ # SPDX-FileCopyrightText: 2023 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Generate revocation certificate for private keys +usage.header=Generate revocation certificates +usage.description=Emit revocation certificates for secret keys from STDIN to STDOUT. no-armor=ASCII armor the output +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). + +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties index 2f7e294..95db272 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -1,9 +1,15 @@ # SPDX-FileCopyrightText: 2023 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Erzeuge Widerrufszertifikate für private Schlüssel +usage.header=Erzeuge Widerrufszertifikate +usage.description=Gebe Widerrufszertifikate für Schlüssel von Standard-Eingabe auf Standard-Ausgabe aus. no-armor=Schütze Ausgabe mit ASCII Armor +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). + +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index a4c75ca..9e1451b 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -6,6 +6,7 @@ extended=Print an extended version string backend=Print information about the cryptographic backend sop-spec=Print the latest revision of the SOP specification targeted by the implementation +stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index 573a82f..608b0c6 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -6,6 +6,7 @@ extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird +stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n From feb9efc7335cd85744a982c463b0902992c92cf5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:20:53 +0200 Subject: [PATCH 199/444] Reorder subcommands --- .../src/main/java/sop/cli/picocli/SopCLI.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index e80c887..8eb18bf 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -32,22 +32,27 @@ import java.util.ResourceBundle; resourceBundle = "msg_sop", exitCodeOnInvalidInput = 69, subcommands = { - CommandLine.HelpCommand.class, - ArmorCmd.class, - DearmorCmd.class, - DecryptCmd.class, - InlineDetachCmd.class, - EncryptCmd.class, - ExtractCertCmd.class, + // Meta Subcommands + VersionCmd.class, + ListProfilesCmd.class, + // Key and Certificate Management Subcommands GenerateKeyCmd.class, + ChangeKeyPasswordCmd.class, + RevokeKeyCmd.class, + ExtractCertCmd.class, + // Messaging Subcommands SignCmd.class, VerifyCmd.class, + EncryptCmd.class, + DecryptCmd.class, + InlineDetachCmd.class, InlineSignCmd.class, InlineVerifyCmd.class, - ListProfilesCmd.class, - RevokeKeyCmd.class, - ChangeKeyPasswordCmd.class, - VersionCmd.class, + // Transport Subcommands + ArmorCmd.class, + DearmorCmd.class, + // Miscellaneous Subcommands + CommandLine.HelpCommand.class, AutoComplete.GenerateCompletion.class } ) From edb384d1572e3e38dc72280975453278ad65c0da Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:22:57 +0200 Subject: [PATCH 200/444] Reference exit codes by Exception --- sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java | 3 ++- .../src/main/java/sop/cli/picocli/commands/EncryptCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/InlineSignCmd.java | 2 +- .../main/java/sop/cli/picocli/commands/InlineVerifyCmd.java | 2 +- .../main/java/sop/cli/picocli/commands/ListProfilesCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/SignCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/VerifyCmd.java | 2 +- .../src/main/java/sop/cli/picocli/commands/VersionCmd.java | 3 ++- 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java index 8eb18bf..5420dea 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java @@ -22,6 +22,7 @@ import sop.cli.picocli.commands.RevokeKeyCmd; import sop.cli.picocli.commands.SignCmd; import sop.cli.picocli.commands.VerifyCmd; import sop.cli.picocli.commands.VersionCmd; +import sop.exception.SOPGPException; import java.util.List; import java.util.Locale; @@ -30,7 +31,7 @@ import java.util.ResourceBundle; @CommandLine.Command( name = "sop", resourceBundle = "msg_sop", - exitCodeOnInvalidInput = 69, + exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, subcommands = { // Meta Subcommands VersionCmd.class, diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index 079f9d8..efda26f 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java @@ -18,7 +18,7 @@ import java.util.List; @CommandLine.Command(name = "encrypt", resourceBundle = "msg_encrypt", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class EncryptCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java index 28a74b0..64a7a84 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java @@ -14,7 +14,7 @@ import sop.operation.ExtractCert; @CommandLine.Command(name = "extract-cert", resourceBundle = "msg_extract-cert", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ExtractCertCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index a41f086..eea992e 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java @@ -16,7 +16,7 @@ import java.util.List; @CommandLine.Command(name = "generate-key", resourceBundle = "msg_generate-key", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class GenerateKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java index 773e6af..1865bcf 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java @@ -18,7 +18,7 @@ import java.util.List; @CommandLine.Command(name = "inline-sign", resourceBundle = "msg_inline-sign", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class InlineSignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java index c3b73c5..c413c85 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java @@ -20,7 +20,7 @@ import java.util.List; @CommandLine.Command(name = "inline-verify", resourceBundle = "msg_inline-verify", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class InlineVerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(arity = "0..*", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java index 46464f8..53ec024 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java @@ -13,7 +13,7 @@ import sop.operation.ListProfiles; @CommandLine.Command(name = "list-profiles", resourceBundle = "msg_list-profiles", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ListProfilesCmd extends AbstractSopCmd { @CommandLine.Parameters(paramLabel = "COMMAND", arity = "1", descriptionKey = "subcommand") diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java index 28d1890..45f22fa 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java @@ -14,7 +14,7 @@ import java.io.IOException; @CommandLine.Command(name = "revoke-key", resourceBundle = "msg_revoke-key", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class RevokeKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java index 521262b..cad9d6e 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java @@ -21,7 +21,7 @@ import java.util.List; @CommandLine.Command(name = "sign", resourceBundle = "msg_detached-sign", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class SignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java index 1bcd0a3..d76bb37 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java @@ -18,7 +18,7 @@ import java.util.List; @CommandLine.Command(name = "verify", resourceBundle = "msg_detached-verify", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class VerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(index = "0", diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java index c11d050..6ccb8f7 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java @@ -7,10 +7,11 @@ package sop.cli.picocli.commands; import picocli.CommandLine; import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; +import sop.exception.SOPGPException; import sop.operation.Version; @CommandLine.Command(name = "version", resourceBundle = "msg_version", - exitCodeOnInvalidInput = 37) + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class VersionCmd extends AbstractSopCmd { @CommandLine.ArgGroup() From cd2c62ce2bce6f57274364cde662d76bfd1863d7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:30:23 +0200 Subject: [PATCH 201/444] Improve description of change-key-password --- .../src/main/resources/msg_change-key-password.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password.properties b/sop-java-picocli/src/main/resources/msg_change-key-password.properties index 0c6eff8..3de3608 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2023 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Update a key's password +usage.header=Update the password of a key usage.description.0=Unlock all secret keys from STDIN using the given old passwords and emit them re-locked using the new password to STDOUT. usage.description.1=If any (sub-) key cannot be unlocked, this operation will exit with error code 67. no-armor=ASCII armor the output From 9ad59abb2a62741ddba6338e5ee895917226616d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:30:45 +0200 Subject: [PATCH 202/444] Improve description of extract-cert --- .../src/main/resources/msg_extract-cert.properties | 4 +++- .../src/main/resources/msg_extract-cert_de.properties | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index d661d2c..82cac0f 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -1,11 +1,13 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Extract a public key certificate from a secret key from standard input +usage.header=Extract a public key certificate from a secret key +usage.description=Read a secret key from STDIN and emit the public key certificate to STDOUT. no-armor=ASCII armor the output stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n usage.optionListHeading = %nOptions:%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index c536b93..0946cfc 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -1,11 +1,13 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel von Standard-Eingabe +usage.header=Extrahiere Zertifikat (öffentlichen Schlüssel) aus privatem Schlüssel +usage.description=Lese einen Schlüssel von Standard-Eingabe und gebe das Zertifikat auf Standard-Ausgabe aus. no-armor=Schütze Ausgabe mit ASCII Armor stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading = %nOptionen:%n From 530c44ad16aaef50f70b737615786c74396becc9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 14:57:51 +0200 Subject: [PATCH 203/444] Typos and improvements --- sop-java-picocli/src/main/resources/msg_decrypt.properties | 2 +- .../src/main/resources/msg_decrypt_de.properties | 6 +++--- .../src/main/resources/msg_detached-sign.properties | 2 +- .../src/main/resources/msg_detached-sign_de.properties | 2 +- .../src/main/resources/msg_detached-verify.properties | 4 +++- .../src/main/resources/msg_detached-verify_de.properties | 6 ++++-- .../src/main/resources/msg_inline-sign.properties | 2 +- .../src/main/resources/msg_inline-sign_de.properties | 2 +- .../src/main/resources/msg_inline-verify.properties | 2 +- .../src/main/resources/msg_inline-verify_de.properties | 4 ++-- 10 files changed, 18 insertions(+), 14 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index 75adca6..5903ded 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Decrypt a message from standard input +usage.header=Decrypt a message session-key-out=Can be used to learn the session key on successful decryption with-session-key.0=Symmetric message key (session key). with-session-key.1=Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet. diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index 0aefcee..ba40897 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -1,10 +1,10 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Entschlüssle eine Nachricht von Standard-Eingabe +usage.header=Entschlüssle eine Nachricht session-key-out=Extrahiere den Nachrichtenschlüssel nach erfolgreicher Entschlüsselung with-session-key.0=Symmetrischer Nachrichtenschlüssel (Sitzungsschlüssel). -with-session-key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enhaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. +with-session-key.1=Ermöglicht direkte Entschlüsselung des im "CIPHERTEXT" enthaltenen "SEIPD" Paketes mithilfe des Nachrichtenschlüssels. with-session-key.2=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). with-password.0=Symmetrisches Passwort zur Entschlüsselung der Nachricht. with-password.1=Ermöglicht Entschlüsselung basierend auf im "CIPHERTEXT" enthaltenen "SKESK" Paketen. @@ -16,7 +16,7 @@ verify-not-before.1=Lehne Signaturen mit Erstellungsdatum au verify-not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). verify-not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) verify-not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -verify-not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +verify-not-after.2=Standardmäßig: Aktueller Zeitpunkt ('now'). verify-not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index e5ea4e7..83359a6 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Create a detached signature on the data from standard input +usage.header=Create a detached message signature no-armor=ASCII armor the output as.0=Specify the output format of the signed message. as.1=Defaults to 'binary'. diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index 2992c02..b943da5 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Erstelle abgetrennte Signatur über Nachricht von Standard-Eingabe +usage.header=Erstelle abgetrennte Nachrichten-Signatur no-armor=Schütze Ausgabe mit ASCII Armor as.0=Bestimme Signaturformat der Nachricht. as.1=Standardmäßig: 'binary'. diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index 27ba2ad..ee1a468 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Verify a detached signature over the data from standard input +usage.header=Verify a detached signature +usage.description=Verify a detached signature over some data from STDIN. not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) not-before.1=Reject signatures with a creation date not in range. not-before.2=Defaults to beginning of time ("-"). @@ -14,6 +15,7 @@ CERT[1..*]=Public key certificates for signature verification stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nDescription:%n usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 usage.commandListHeading = %nCommands:%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index 76adc60..332bff6 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -1,19 +1,21 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe +usage.header=Prüfe eine abgetrennte Nachrichten-Signatur +usage.description=Prüfe eine abgetrennte Signatur über eine Nachricht von Standard-Eingabe. not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +not-after.2=Standardmäßig: Aktueller Zeitpunkt ('now'). not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. SIGNATURE[0]=Abgetrennte Signatur CERT[1..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 +usage.descriptionHeading=%nBeschreibung:%n usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index 5e7c59e..f5143bb 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Create an inline-signed message from data on standard input +usage.header=Create an inline-signed message no-armor=ASCII armor the output as.0=Specify the signature format of the signed message. as.1='text' and 'binary' will produce inline-signed messages. diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index 9d360d5..b09b7e4 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Signiere eine Nachricht von Standard-Eingabe mit eingebetteten Signaturen +usage.header=Signiere eine Nachricht mit eingebetteten Signaturen no-armor=Schütze Ausgabe mit ASCII Armor as.0=Bestimme Signaturformat der Nachricht. as.1='text' und 'binary' resultieren in eingebettete Signaturen. diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index c016e57..dfa94d7 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Verify inline-signed data from standard input +usage.header=Verify an inline-signed message not-before.0=ISO-8601 formatted UTC date (e.g. '2020-11-23T16:35Z) not-before.1=Reject signatures with a creation date not in range. not-before.2=Defaults to beginning of time ("-"). diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index 3e340c0..a9a5722 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -1,13 +1,13 @@ # SPDX-FileCopyrightText: 2022 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.header=Prüfe eingebettete Signaturen einer Nachricht von Standard-Eingabe +usage.header=Prüfe eingebettete Signaturen einer Nachricht not-before.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) not-before.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. not-before.2=Standardmäßig: Anbeginn der Zeit ('-'). not-after.0=Nach ISO-8601 formatierter UTC Zeitstempel (z.B.. '2020-11-23T16:35Z) not-after.1=Lehne Signaturen mit Erstellungsdatum außerhalb des Gültigkeitsbereichs ab. -not-after.2=Standardmäßig: Aktueller Zeitunkt ('now'). +not-after.2=Standardmäßig: Aktueller Zeitpunkt ('now'). not-after.3=Akzeptiert speziellen Wert '-' für das Ende aller Zeiten. verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung From be351616b68cf3759418156a233293e5384e217e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 15:08:33 +0200 Subject: [PATCH 204/444] Add some more RevokeKey tests --- .../testsuite/operation/RevokeKeyTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java index 920f877..10472ca 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; import sop.exception.SOPGPException; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import sop.util.UTF8Util; import java.io.IOException; @@ -18,6 +20,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class RevokeKeyTest extends AbstractSOPTest { @@ -32,9 +35,29 @@ public class RevokeKeyTest extends AbstractSOPTest { byte[] secretKey = sop.generateKey().userId("Alice ").generate().getBytes(); byte[] revocation = sop.revokeKey().keys(secretKey).getBytes(); + assertTrue(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertFalse(Arrays.equals(secretKey, revocation)); } + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeUnprotectedKeyUnarmored(SOP sop) throws IOException { + byte[] secretKey = sop.generateKey().userId("Alice ").noArmor().generate().getBytes(); + byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); + + assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); + assertFalse(Arrays.equals(secretKey, revocation)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeCertificateFails(SOP sop) throws IOException { + byte[] secretKey = sop.generateKey().generate().getBytes(); + byte[] certificate = sop.extractCert().key(secretKey).getBytes(); + + assertThrows(SOPGPException.BadData.class, () -> sop.revokeKey().keys(certificate).getBytes()); + } + @ParameterizedTest @MethodSource("provideInstances") public void revokeProtectedKey(SOP sop) throws IOException { From 308c4b452f9639bb2cfaa36620c5e40eab7959fa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 15:36:39 +0200 Subject: [PATCH 205/444] Add test for signature verification with hard-revoked cert --- .../testsuite/operation/RevokeKeyTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java index 10472ca..6595133 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java @@ -9,13 +9,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; +import sop.Verification; import sop.exception.SOPGPException; import sop.testsuite.JUtils; import sop.testsuite.TestData; +import sop.testsuite.assertions.VerificationListAssert; import sop.util.UTF8Util; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -97,4 +101,23 @@ public class RevokeKeyTest extends AbstractSOPTest { assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().withKeyPassword(wrongPassword).keys(secretKey).getBytes()); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeKeyIsNowHardRevoked(SOP sop) throws IOException { + byte[] key = sop.generateKey().generate().getBytes(); + byte[] cert = sop.extractCert().key(key).getBytes(); + + // Sign a message with the key + byte[] msg = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); + byte[] signedMsg = sop.inlineSign().key(key).data(msg).getBytes(); + + // Verifying the message with the valid cert works + List result = sop.inlineVerify().cert(cert).data(signedMsg).toByteArrayAndResult().getResult(); + VerificationListAssert.assertThatVerificationList(result).hasSingleItem(); + + // Now hard revoke the key and re-check signature, expecting no valid certification + byte[] revokedCert = sop.revokeKey().keys(key).getBytes(); + assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify().cert(revokedCert).data(signedMsg).toByteArrayAndResult()); + } } From f2073dcbf4d1bdf1535a09c30e719c47504d7a4f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 15:41:47 +0200 Subject: [PATCH 206/444] SOP-Java 7.0.0 --- CHANGELOG.md | 5 ++--- version.gradle | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c3476..3ca12a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,13 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 7.0.0-SNAPSHOT +## 7.0.0 - Update implementation to [SOP Specification revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html). - Add support for new `revoke-key` subcommand - Add support for new `change-key-password` subcommand - Add support for new `--signing-only` option of `generate-key` subcommand - -## 6.1.1-SNAPSHOT - Add `dearmor.data(String)` utility method +- Fix typos in, and improve i18n of CLI help pages ## 6.1.0 - `listProfiles()`: Add shortcut methods `generateKey()` and `encrypt()` diff --git a/version.gradle b/version.gradle index 7614d8f..ed3e547 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '7.0.0' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From cf1d39643d3c0d7d56379eba9a1f5c6368d0bc12 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 12 Jul 2023 15:44:05 +0200 Subject: [PATCH 207/444] SOP-Java 7.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index ed3e547..d8ddfd6 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '7.0.0' - isSnapshot = false + shortVersion = '7.0.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 4bd46579068977ef12efc8ac8cc65ed73c8be747 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:24:27 +0100 Subject: [PATCH 208/444] Add kotlin plugin --- build.gradle | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/build.gradle b/build.gradle index 56730fd..577c2aa 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,8 @@ buildscript { plugins { id 'ru.vyarus.animalsniffer' version '1.5.3' + id 'org.jetbrains.kotlin.jvm' version "1.8.10" + id 'com.diffplug.spotless' version '6.22.0' apply false } apply from: 'version.gradle' @@ -29,6 +31,8 @@ allprojects { apply plugin: 'eclipse' apply plugin: 'jacoco' apply plugin: 'checkstyle' + apply plugin: 'kotlin' + apply plugin: 'com.diffplug.spotless' // For non-cli modules enable android api compatibility check if (it.name.equals('sop-java')) { @@ -53,6 +57,12 @@ allprojects { toolVersion = '8.18' } + spotless { + kotlin { + ktfmt().dropboxStyle() + } + } + group 'org.pgpainless' description = "Stateless OpenPGP Protocol API for Java" version = shortVersion @@ -69,6 +79,13 @@ allprojects { reproducibleFileOrder = true } + // Compatibility of default implementations in kotlin interfaces with Java implementations. + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + freeCompilerArgs += ["-Xjvm-default=all-compatibility"] + } + } + project.ext { rootConfigDir = new File(rootDir, 'config') gitCommit = getGitCommit() From 0f5270c28da4f63c364a9f1d2561355a0f2f6fa9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:24:36 +0100 Subject: [PATCH 209/444] Kotlin conversion: ByteArrayAndResult --- .../src/main/java/sop/ByteArrayAndResult.java | 50 ------------------- .../src/main/kotlin/sop/ByteArrayAndResult.kt | 43 ++++++++++++++++ 2 files changed, 43 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/ByteArrayAndResult.java create mode 100644 sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt diff --git a/sop-java/src/main/java/sop/ByteArrayAndResult.java b/sop-java/src/main/java/sop/ByteArrayAndResult.java deleted file mode 100644 index fd2b39a..0000000 --- a/sop-java/src/main/java/sop/ByteArrayAndResult.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * Tuple of a byte array and associated result object. - * @param type of result - */ -public class ByteArrayAndResult { - - private final byte[] bytes; - private final T result; - - public ByteArrayAndResult(byte[] bytes, T result) { - this.bytes = bytes; - this.result = result; - } - - /** - * Return the byte array part. - * - * @return bytes - */ - public byte[] getBytes() { - return bytes; - } - - /** - * Return the result part. - * - * @return result - */ - public T getResult() { - return result; - } - - /** - * Return the byte array part as an {@link InputStream}. - * - * @return input stream - */ - public InputStream getInputStream() { - return new ByteArrayInputStream(getBytes()); - } -} diff --git a/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt b/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt new file mode 100644 index 0000000..021a2d9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/ByteArrayAndResult.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.InputStream + +/** + * Tuple of a [ByteArray] and associated result object. + * + * @param bytes byte array + * @param result result object + * @param type of result + */ +data class ByteArrayAndResult(val bytes: ByteArray, val result: T) { + + /** + * [InputStream] returning the contents of [bytes]. + * + * @return input stream + */ + val inputStream: InputStream + get() = bytes.inputStream() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ByteArrayAndResult<*> + + if (!bytes.contentEquals(other.bytes)) return false + if (result != other.result) return false + + return true + } + + override fun hashCode(): Int { + var hashCode = bytes.contentHashCode() + hashCode = 31 * hashCode + (result?.hashCode() ?: 0) + return hashCode + } +} From 567571cf6cbc657e6f4713dea38bda7f34eb63ac Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 11:33:15 +0100 Subject: [PATCH 210/444] Kotlin conversion: SessionKey --- sop-java/src/main/java/sop/SessionKey.java | 80 ---------------------- sop-java/src/main/kotlin/sop/SessionKey.kt | 48 +++++++++++++ 2 files changed, 48 insertions(+), 80 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SessionKey.java create mode 100644 sop-java/src/main/kotlin/sop/SessionKey.kt diff --git a/sop-java/src/main/java/sop/SessionKey.java b/sop-java/src/main/java/sop/SessionKey.java deleted file mode 100644 index 722666d..0000000 --- a/sop-java/src/main/java/sop/SessionKey.java +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import sop.util.HexUtil; - -public class SessionKey { - - private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9A-F]+)$"); - - private final byte algorithm; - private final byte[] sessionKey; - - public SessionKey(byte algorithm, byte[] sessionKey) { - this.algorithm = algorithm; - this.sessionKey = sessionKey; - } - - /** - * Return the symmetric algorithm octet. - * - * @return algorithm id - */ - public byte getAlgorithm() { - return algorithm; - } - - /** - * Return the session key. - * - * @return session key - */ - public byte[] getKey() { - return sessionKey; - } - - @Override - public int hashCode() { - return getAlgorithm() * 17 + Arrays.hashCode(getKey()); - } - - @Override - public boolean equals(Object other) { - if (other == null) { - return false; - } - if (this == other) { - return true; - } - if (!(other instanceof SessionKey)) { - return false; - } - - SessionKey otherKey = (SessionKey) other; - return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey()); - } - - public static SessionKey fromString(String string) { - string = string.trim().toUpperCase().replace("\n", ""); - Matcher matcher = PATTERN.matcher(string); - if (!matcher.matches()) { - throw new IllegalArgumentException("Provided session key does not match expected format."); - } - byte algorithm = Byte.parseByte(matcher.group(1)); - String key = matcher.group(2); - - return new SessionKey(algorithm, HexUtil.hexToBytes(key)); - } - - @Override - public String toString() { - return Integer.toString(getAlgorithm()) + ':' + HexUtil.bytesToHex(sessionKey); - } -} diff --git a/sop-java/src/main/kotlin/sop/SessionKey.kt b/sop-java/src/main/kotlin/sop/SessionKey.kt new file mode 100644 index 0000000..af230f3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SessionKey.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.HexUtil + +/** + * Class representing a symmetric session key. + * + * @param algorithm symmetric key algorithm ID + * @param key [ByteArray] containing the session key + */ +data class SessionKey(val algorithm: Byte, val key: ByteArray) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SessionKey + + if (algorithm != other.algorithm) return false + if (!key.contentEquals(other.key)) return false + + return true + } + + override fun hashCode(): Int { + var hashCode = algorithm.toInt() + hashCode = 31 * hashCode + key.contentHashCode() + return hashCode + } + + override fun toString(): String = "$algorithm:${HexUtil.bytesToHex(key)}" + + companion object { + + @JvmStatic private val PATTERN = "^(\\d):([0-9A-F]+)$".toPattern() + + @JvmStatic + fun fromString(string: String): SessionKey { + val matcher = PATTERN.matcher(string.trim().uppercase().replace("\n", "")) + require(matcher.matches()) { "Provided session key does not match expected format." } + return SessionKey(matcher.group(1).toByte(), HexUtil.hexToBytes(matcher.group(2))) + } + } +} From 6c5c4b3d981aa62f28dfde36a2e56821d279620f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:25:54 +0100 Subject: [PATCH 211/444] Kotlin conversion: Verification --- sop-java/src/main/java/sop/Verification.java | 222 ------------------- sop-java/src/main/kotlin/sop/Verification.kt | 76 +++++++ 2 files changed, 76 insertions(+), 222 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Verification.java create mode 100644 sop-java/src/main/kotlin/sop/Verification.kt diff --git a/sop-java/src/main/java/sop/Verification.java b/sop-java/src/main/java/sop/Verification.java deleted file mode 100644 index 140e23b..0000000 --- a/sop-java/src/main/java/sop/Verification.java +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.enums.SignatureMode; -import sop.util.Optional; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.text.ParseException; -import java.util.Date; - -/** - * Class bundling information about a verified signature. - */ -public class Verification { - - private final Date creationTime; - private final String signingKeyFingerprint; - private final String signingCertFingerprint; - private final Optional signatureMode; - private final Optional description; - - private static final String MODE = "mode:"; - - /** - * Create a new {@link Verification} without mode and description. - * - * @param creationTime signature creation time - * @param signingKeyFingerprint fingerprint of the signing (sub-) key - * @param signingCertFingerprint fingerprint of the certificate - */ - public Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint) { - this(creationTime, signingKeyFingerprint, signingCertFingerprint, Optional.ofEmpty(), Optional.ofEmpty()); - } - - /** - * Create a new {@link Verification}. - * - * @param creationTime signature creation time - * @param signingKeyFingerprint fingerprint of the signing (sub-) key - * @param signingCertFingerprint fingerprint of the certificate - * @param signatureMode signature mode (optional, may be
null
) - * @param description free-form description, e.g.
certificate from dkg.asc
(optional, may be
null
) - */ - public Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint, - @Nullable SignatureMode signatureMode, - @Nullable String description) { - this( - creationTime, - signingKeyFingerprint, - signingCertFingerprint, - Optional.ofNullable(signatureMode), - Optional.ofNullable(nullSafeTrim(description)) - ); - } - - private Verification(@Nonnull Date creationTime, - @Nonnull String signingKeyFingerprint, - @Nonnull String signingCertFingerprint, - @Nonnull Optional signatureMode, - @Nonnull Optional description) { - this.creationTime = creationTime; - this.signingKeyFingerprint = signingKeyFingerprint; - this.signingCertFingerprint = signingCertFingerprint; - this.signatureMode = signatureMode; - this.description = description; - } - - private static String nullSafeTrim(@Nullable String string) { - if (string == null) { - return null; - } - return string.trim(); - } - - @Nonnull - public static Verification fromString(@Nonnull String toString) { - String[] split = toString.trim().split(" "); - if (split.length < 3) { - throw new IllegalArgumentException("Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'"); - } - - if (split.length == 3) { - return new Verification( - parseUTCDate(split[0]), // timestamp - split[1], // key FP - split[2] // cert FP - ); - } - - SignatureMode mode = null; - int index = 3; - if (split[index].startsWith(MODE)) { - mode = SignatureMode.valueOf(split[3].substring(MODE.length())); - index++; - } - - StringBuilder sb = new StringBuilder(); - for (int i = index; i < split.length; i++) { - if (sb.length() != 0) { - sb.append(' '); - } - sb.append(split[i]); - } - - return new Verification( - parseUTCDate(split[0]), // timestamp - split[1], // key FP - split[2], // cert FP - mode, // signature mode - sb.length() != 0 ? sb.toString() : null // description - ); - } - - private static Date parseUTCDate(String utcFormatted) { - try { - return UTCUtil.parseUTCDate(utcFormatted); - } catch (ParseException e) { - throw new IllegalArgumentException("Malformed UTC timestamp.", e); - } - } - - /** - * Return the signatures' creation time. - * - * @return signature creation time - */ - @Nonnull - public Date getCreationTime() { - return creationTime; - } - - /** - * Return the fingerprint of the signing (sub)key. - * - * @return signing key fingerprint - */ - @Nonnull - public String getSigningKeyFingerprint() { - return signingKeyFingerprint; - } - - /** - * Return the fingerprint fo the signing certificate. - * - * @return signing certificate fingerprint - */ - @Nonnull - public String getSigningCertFingerprint() { - return signingCertFingerprint; - } - - /** - * Return the mode of the signature. - * Optional, may return
null
. - * - * @return signature mode - */ - @Nonnull - public Optional getSignatureMode() { - return signatureMode; - } - - /** - * Return an optional description. - * Optional, may return
null
. - * - * @return description - */ - @Nonnull - public Optional getDescription() { - return description; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder() - .append(UTCUtil.formatUTCDate(getCreationTime())) - .append(' ') - .append(getSigningKeyFingerprint()) - .append(' ') - .append(getSigningCertFingerprint()); - - if (signatureMode.isPresent()) { - sb.append(' ').append(MODE).append(signatureMode.get()); - } - - if (description.isPresent()) { - sb.append(' ').append(description.get()); - } - - return sb.toString(); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (!(obj instanceof Verification)) { - return false; - } - Verification other = (Verification) obj; - return toString().equals(other.toString()); - } -} diff --git a/sop-java/src/main/kotlin/sop/Verification.kt b/sop-java/src/main/kotlin/sop/Verification.kt new file mode 100644 index 0000000..20401a9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Verification.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.text.ParseException +import java.util.Date +import sop.enums.SignatureMode +import sop.util.Optional +import sop.util.UTCUtil + +data class Verification( + val creationTime: Date, + val signingKeyFingerprint: String, + val signingCertFingerprint: String, + val signatureMode: Optional, + val description: Optional +) { + @JvmOverloads + constructor( + creationTime: Date, + signingKeyFingerprint: String, + signingCertFingerprint: String, + signatureMode: SignatureMode? = null, + description: String? = null + ) : this( + creationTime, + signingKeyFingerprint, + signingCertFingerprint, + Optional.ofNullable(signatureMode), + Optional.ofNullable(description?.trim())) + + override fun toString(): String = + "${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" + + (if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") + + (if (description.isPresent) " ${description.get()}" else "") + + companion object { + @JvmStatic + fun fromString(string: String): Verification { + val split = string.trim().split(" ") + require(split.size >= 3) { + "Verification must be of the format 'UTC-DATE OpenPGPFingerprint OpenPGPFingerprint [mode] [info]'." + } + if (split.size == 3) { + return Verification(parseUTCDate(split[0]), split[1], split[2]) + } + + var index = 3 + val mode = + if (split[3].startsWith("mode:")) { + index += 1 + SignatureMode.valueOf(split[3].substring("mode:".length)) + } else null + + val description = split.subList(index, split.size).joinToString(" ").ifBlank { null } + + return Verification( + parseUTCDate(split[0]), + split[1], + split[2], + Optional.ofNullable(mode), + Optional.ofNullable(description)) + } + + @JvmStatic + private fun parseUTCDate(string: String): Date { + return try { + UTCUtil.parseUTCDate(string) + } catch (e: ParseException) { + throw IllegalArgumentException("Malformed UTC timestamp.", e) + } + } + } +} From ef4b01c6bd73d74a3b3d203ec5da2a0228431f4e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:42:13 +0100 Subject: [PATCH 212/444] Kotlin conversion: Profile --- sop-java/src/main/java/sop/Profile.java | 143 ------------------------ sop-java/src/main/kotlin/sop/Profile.kt | 87 ++++++++++++++ 2 files changed, 87 insertions(+), 143 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Profile.java create mode 100644 sop-java/src/main/kotlin/sop/Profile.kt diff --git a/sop-java/src/main/java/sop/Profile.java b/sop-java/src/main/java/sop/Profile.java deleted file mode 100644 index 4ea9e71..0000000 --- a/sop-java/src/main/java/sop/Profile.java +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.util.Optional; -import sop.util.UTF8Util; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -/** - * Tuple class bundling a profile name and description. - * - * @see - * SOP Spec - Profile - */ -public class Profile { - - private final String name; - private final Optional description; - - /** - * Create a new {@link Profile} object. - * The {@link #toString()} representation MUST NOT exceed a length of 1000 bytes. - * - * @param name profile name - * @param description profile description - */ - public Profile(@Nonnull String name, @Nullable String description) { - if (name.trim().isEmpty()) { - throw new IllegalArgumentException("Name cannot be empty."); - } - if (name.contains(":")) { - throw new IllegalArgumentException("Name cannot contain ':'."); - } - if (name.contains(" ") || name.contains("\n") || name.contains("\t") || name.contains("\r")) { - throw new IllegalArgumentException("Name cannot contain whitespace characters."); - } - - this.name = name; - - if (description == null) { - this.description = Optional.ofEmpty(); - } else { - String trimmedDescription = description.trim(); - if (trimmedDescription.isEmpty()) { - this.description = Optional.ofEmpty(); - } else { - this.description = Optional.of(trimmedDescription); - } - } - - if (exceeds1000CharLineLimit(this)) { - throw new IllegalArgumentException("The line representation of a profile MUST NOT exceed 1000 bytes."); - } - } - - public Profile(String name) { - this(name, null); - } - - /** - * Parse a {@link Profile} from its string representation. - * - * @param string string representation - * @return profile - */ - public static Profile parse(String string) { - if (string.contains(": ")) { - // description after colon, e.g. "default: Use implementers recommendations." - String name = string.substring(0, string.indexOf(": ")); - String description = string.substring(string.indexOf(": ") + 2); - return new Profile(name, description.trim()); - } - - if (string.endsWith(":")) { - // empty description, e.g. "default:" - return new Profile(string.substring(0, string.length() - 1)); - } - - // no description - return new Profile(string.trim()); - } - - /** - * Return the name (also known as identifier) of the profile. - * A profile name is a UTF-8 string that has no whitespace in it. - * Similar to OpenPGP Notation names, profile names are divided into two namespaces: - * The IETF namespace and the user namespace. - * A profile name in the user namespace ends with the
@
character (0x40) followed by a DNS domain name. - * A profile name in the IETF namespace does not have an
@
character. - * A profile name in the user space is owned and controlled by the owner of the domain in the suffix. - * A profile name in the IETF namespace that begins with the string
rfc
should have semantics that hew as - * closely as possible to the referenced RFC. - * Similarly, a profile name in the IETF namespace that begins with the string
draft-
should have - * semantics that hew as closely as possible to the referenced Internet Draft. - * - * @return name - */ - @Nonnull - public String getName() { - return name; - } - - /** - * Return a free-form description of the profile. - * - * @return description - */ - @Nonnull - public Optional getDescription() { - return description; - } - - public boolean hasDescription() { - return description.isPresent(); - } - - /** - * Convert the profile into a String for displaying. - * - * @return string - */ - @Override - public String toString() { - if (getDescription().isEmpty()) { - return getName(); - } - return getName() + ": " + getDescription().get(); - } - - /** - * Test if the string representation of the profile exceeds the limit of 1000 bytes length. - * @param profile profile - * @return
true
if the profile exceeds 1000 bytes,
false
otherwise. - */ - private static boolean exceeds1000CharLineLimit(Profile profile) { - String line = profile.toString(); - return line.getBytes(UTF8Util.UTF8).length > 1000; - } -} diff --git a/sop-java/src/main/kotlin/sop/Profile.kt b/sop-java/src/main/kotlin/sop/Profile.kt new file mode 100644 index 0000000..2125c57 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Profile.kt @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.Optional +import sop.util.UTF8Util + +/** + * Tuple class bundling a profile name and description. + * + * @param name profile name. A profile name is a UTF-8 string that has no whitespace in it. Similar + * to OpenPGP Notation names, profile names are divided into two namespaces: The IETF namespace + * and the user namespace. A profile name in the user namespace ends with the `@` character (0x40) + * followed by a DNS domain name. A profile name in the IETF namespace does not have an `@` + * character. A profile name in the user space is owned and controlled by the owner of the domain + * in the suffix. A profile name in the IETF namespace that begins with the string `rfc` should + * have semantics that hew as closely as possible to the referenced RFC. Similarly, a profile name + * in the IETF namespace that begins with the string `draft-` should have semantics that hew as + * closely as possible to the referenced Internet Draft. + * @param description a free-form description of the profile. + * @see + * SOP Spec - Profile + */ +data class Profile(val name: String, val description: Optional) { + + @JvmOverloads + constructor( + name: String, + description: String? = null + ) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null })) + + init { + require(name.trim().isNotBlank()) { "Name cannot be empty." } + require(!name.contains(":")) { "Name cannot contain ':'." } + require(listOf(" ", "\n", "\t", "\r").none { name.contains(it) }) { + "Name cannot contain whitespace characters." + } + require(!exceeds1000CharLineLimit(this)) { + "The line representation of a profile MUST NOT exceed 1000 bytes." + } + } + + fun hasDescription() = description.isPresent + + /** + * Convert the profile into a String for displaying. + * + * @return string + */ + override fun toString(): String = + if (description.isEmpty) name else "$name: ${description.get()}" + + companion object { + + /** + * Parse a [Profile] from its string representation. + * + * @param string string representation + * @return profile + */ + @JvmStatic + fun parse(string: String): Profile { + return if (string.contains(": ")) { + Profile( + string.substring(0, string.indexOf(": ")), + string.substring(string.indexOf(": ") + 2).trim()) + } else if (string.endsWith(":")) { + Profile(string.substring(0, string.length - 1)) + } else { + Profile(string.trim()) + } + } + + /** + * Test if the string representation of the profile exceeds the limit of 1000 bytes length. + * + * @param profile profile + * @return `true` if the profile exceeds 1000 bytes, `false` otherwise. + */ + @JvmStatic + private fun exceeds1000CharLineLimit(profile: Profile): Boolean = + profile.toString().toByteArray(UTF8Util.UTF8).size > 1000 + } +} From 0cb5c74a11109cbe7a3b266a95f7655b8ef68441 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:48:23 +0100 Subject: [PATCH 213/444] Kotlin conversion: Optional --- sop-java/src/main/java/sop/util/Optional.java | 50 ------------------- sop-java/src/main/kotlin/sop/util/Optional.kt | 26 ++++++++++ 2 files changed, 26 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/Optional.java create mode 100644 sop-java/src/main/kotlin/sop/util/Optional.kt diff --git a/sop-java/src/main/java/sop/util/Optional.java b/sop-java/src/main/java/sop/util/Optional.java deleted file mode 100644 index 00eb201..0000000 --- a/sop-java/src/main/java/sop/util/Optional.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -/** - * Backport of java.util.Optional for older Android versions. - * - * @param item type - */ -public class Optional { - - private final T item; - - public Optional() { - this(null); - } - - public Optional(T item) { - this.item = item; - } - - public static Optional of(T item) { - if (item == null) { - throw new NullPointerException("Item cannot be null."); - } - return new Optional<>(item); - } - - public static Optional ofNullable(T item) { - return new Optional<>(item); - } - - public static Optional ofEmpty() { - return new Optional<>(null); - } - - public T get() { - return item; - } - - public boolean isPresent() { - return item != null; - } - - public boolean isEmpty() { - return item == null; - } -} diff --git a/sop-java/src/main/kotlin/sop/util/Optional.kt b/sop-java/src/main/kotlin/sop/util/Optional.kt new file mode 100644 index 0000000..0344d0b --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/Optional.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +/** + * Backport of java.util.Optional for older Android versions. + * + * @param item type + */ +data class Optional(val item: T? = null) { + + val isPresent: Boolean = item != null + val isEmpty: Boolean = item == null + + fun get() = item + + companion object { + @JvmStatic fun of(item: T) = Optional(item!!) + + @JvmStatic fun ofNullable(item: T?) = Optional(item) + + @JvmStatic fun ofEmpty() = Optional(null as T?) + } +} From bbe159e88c5442a61ae5dcbe7fca695f408cf785 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 12:56:12 +0100 Subject: [PATCH 214/444] Kotlin conversion: SigningResult --- sop-java/src/main/java/sop/SigningResult.java | 50 ------------------- sop-java/src/main/kotlin/sop/SigningResult.kt | 28 +++++++++++ 2 files changed, 28 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SigningResult.java create mode 100644 sop-java/src/main/kotlin/sop/SigningResult.kt diff --git a/sop-java/src/main/java/sop/SigningResult.java b/sop-java/src/main/java/sop/SigningResult.java deleted file mode 100644 index 1ea1ba8..0000000 --- a/sop-java/src/main/java/sop/SigningResult.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -/** - * This class contains various information about a signed message. - */ -public final class SigningResult { - - private final MicAlg micAlg; - - private SigningResult(MicAlg micAlg) { - this.micAlg = micAlg; - } - - /** - * Return a string identifying the digest mechanism used to create the signed message. - * This is useful for setting the micalg= parameter for the multipart/signed - * content type of a PGP/MIME object as described in section 5 of [RFC3156]. - *

- * If more than one signature was generated and different digest mechanisms were used, - * the value of the micalg object is an empty string. - * - * @return micalg - */ - public MicAlg getMicAlg() { - return micAlg; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private MicAlg micAlg; - - public Builder setMicAlg(MicAlg micAlg) { - this.micAlg = micAlg; - return this; - } - - public SigningResult build() { - SigningResult signingResult = new SigningResult(micAlg); - return signingResult; - } - } -} diff --git a/sop-java/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt new file mode 100644 index 0000000..29304ea --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +/** + * This class contains various information about a signed message. + * + * @param micAlg string identifying the digest mechanism used to create the signed message. This is + * useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME + * object as described in section 5 of [RFC3156]. If more than one signature was generated and + * different digest mechanisms were used, the value of the micalg object is an empty string. + */ +data class SigningResult(val micAlg: MicAlg) { + + class Builder internal constructor() { + private var micAlg = MicAlg.empty() + + fun setMicAlg(micAlg: MicAlg) = apply { this.micAlg = micAlg } + + fun build() = SigningResult(micAlg) + } + + companion object { + @JvmStatic fun builder() = Builder() + } +} From 9dbb93e13dd14083d48622b2ff2594db41ec9de8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:05:30 +0100 Subject: [PATCH 215/444] Kotlin conversion: MicAlg --- sop-java/src/main/java/sop/MicAlg.java | 55 ---------------------- sop-java/src/main/kotlin/sop/MicAlg.kt | 34 +++++++++++++ sop-java/src/test/java/sop/MicAlgTest.java | 2 +- 3 files changed, 35 insertions(+), 56 deletions(-) delete mode 100644 sop-java/src/main/java/sop/MicAlg.java create mode 100644 sop-java/src/main/kotlin/sop/MicAlg.kt diff --git a/sop-java/src/main/java/sop/MicAlg.java b/sop-java/src/main/java/sop/MicAlg.java deleted file mode 100644 index 5bee787..0000000 --- a/sop-java/src/main/java/sop/MicAlg.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.OutputStream; -import java.io.PrintWriter; - -public class MicAlg { - - private final String micAlg; - - public MicAlg(String micAlg) { - if (micAlg == null) { - throw new IllegalArgumentException("MicAlg String cannot be null."); - } - this.micAlg = micAlg; - } - - public static MicAlg empty() { - return new MicAlg(""); - } - - public static MicAlg fromHashAlgorithmId(int id) { - switch (id) { - case 1: - return new MicAlg("pgp-md5"); - case 2: - return new MicAlg("pgp-sha1"); - case 3: - return new MicAlg("pgp-ripemd160"); - case 8: - return new MicAlg("pgp-sha256"); - case 9: - return new MicAlg("pgp-sha384"); - case 10: - return new MicAlg("pgp-sha512"); - case 11: - return new MicAlg("pgp-sha224"); - default: - throw new IllegalArgumentException("Unsupported hash algorithm ID: " + id); - } - } - - public String getMicAlg() { - return micAlg; - } - - public void writeTo(OutputStream outputStream) { - PrintWriter pw = new PrintWriter(outputStream); - pw.write(getMicAlg()); - pw.close(); - } -} diff --git a/sop-java/src/main/kotlin/sop/MicAlg.kt b/sop-java/src/main/kotlin/sop/MicAlg.kt new file mode 100644 index 0000000..58ce7b5 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/MicAlg.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.OutputStream +import java.io.PrintWriter + +data class MicAlg(val micAlg: String) { + + fun writeTo(outputStream: OutputStream) { + PrintWriter(outputStream).use { it.write(micAlg) } + } + + companion object { + @JvmStatic fun empty() = MicAlg("") + + @JvmStatic + fun fromHashAlgorithmId(id: Int) = + when (id) { + 1 -> "pgp-md5" + 2 -> "pgp-sha1" + 3 -> "pgp-ripemd160" + 8 -> "pgp-sha256" + 9 -> "pgp-sha384" + 10 -> "pgp-sha512" + 11 -> "pgp-sha224" + 12 -> "pgp-sha3-256" + 14 -> "pgp-sha3-512" + else -> throw IllegalArgumentException("Unsupported hash algorithm ID: $id") + }.let { MicAlg(it) } + } +} diff --git a/sop-java/src/test/java/sop/MicAlgTest.java b/sop-java/src/test/java/sop/MicAlgTest.java index 16f54ef..7c6b30a 100644 --- a/sop-java/src/test/java/sop/MicAlgTest.java +++ b/sop-java/src/test/java/sop/MicAlgTest.java @@ -18,7 +18,7 @@ public class MicAlgTest { @Test public void constructorNullArgThrows() { - assertThrows(IllegalArgumentException.class, () -> new MicAlg(null)); + assertThrows(NullPointerException.class, () -> new MicAlg(null)); } @Test From e6562cecff7e8c444f8d410101a6a2c70378399f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:16:37 +0100 Subject: [PATCH 216/444] Kotlin conversion: Ready --- sop-java/src/main/java/sop/Ready.java | 45 ------------------------ sop-java/src/main/kotlin/sop/Ready.kt | 49 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Ready.java create mode 100644 sop-java/src/main/kotlin/sop/Ready.kt diff --git a/sop-java/src/main/java/sop/Ready.java b/sop-java/src/main/java/sop/Ready.java deleted file mode 100644 index 71ab26e..0000000 --- a/sop-java/src/main/java/sop/Ready.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public abstract class Ready { - - /** - * Write the data to the provided output stream. - * - * @param outputStream output stream - * @throws IOException in case of an IO error - */ - public abstract void writeTo(OutputStream outputStream) throws IOException; - - /** - * Return the data as a byte array by writing it to a {@link ByteArrayOutputStream} first and then returning - * the array. - * - * @return data as byte array - * @throws IOException in case of an IO error - */ - public byte[] getBytes() throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - writeTo(bytes); - return bytes.toByteArray(); - } - - /** - * Return an input stream containing the data. - * - * @return input stream - * @throws IOException in case of an IO error - */ - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(getBytes()); - } -} diff --git a/sop-java/src/main/kotlin/sop/Ready.kt b/sop-java/src/main/kotlin/sop/Ready.kt new file mode 100644 index 0000000..1eb3fb3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Ready.kt @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +/** Abstract class that encapsulates output data, waiting to be consumed. */ +abstract class Ready { + + /** + * Write the data to the provided output stream. + * + * @param outputStream output stream + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) abstract fun writeTo(outputStream: OutputStream) + + /** + * Return the data as a byte array by writing it to a [ByteArrayOutputStream] first and then + * returning the array. + * + * @return data as byte array + * @throws IOException in case of an IO error + */ + val bytes: ByteArray + @Throws(IOException::class) + get() = + ByteArrayOutputStream() + .let { + writeTo(it) + it + } + .toByteArray() + + /** + * Return an input stream containing the data. + * + * @return input stream + * @throws IOException in case of an IO error + */ + val inputStream: InputStream + @Throws(IOException::class) get() = ByteArrayInputStream(bytes) +} From a89e70c19e63362d52b0946aeacad73e6c1bfdc9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:23:26 +0100 Subject: [PATCH 217/444] Kotlin conversion: ReadyWithResult --- .../src/main/java/sop/ReadyWithResult.java | 41 ------------------- .../src/main/kotlin/sop/ReadyWithResult.kt | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 41 deletions(-) delete mode 100644 sop-java/src/main/java/sop/ReadyWithResult.java create mode 100644 sop-java/src/main/kotlin/sop/ReadyWithResult.kt diff --git a/sop-java/src/main/java/sop/ReadyWithResult.java b/sop-java/src/main/java/sop/ReadyWithResult.java deleted file mode 100644 index 9feedda..0000000 --- a/sop-java/src/main/java/sop/ReadyWithResult.java +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import sop.exception.SOPGPException; - -public abstract class ReadyWithResult { - - /** - * Write the data e.g. decrypted plaintext to the provided output stream and return the result of the - * processing operation. - * - * @param outputStream output stream - * @return result, eg. signatures - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature if there are no valid signatures found - */ - public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature; - - /** - * Return the data as a {@link ByteArrayAndResult}. - * Calling {@link ByteArrayAndResult#getBytes()} will give you access to the data as byte array, while - * {@link ByteArrayAndResult#getResult()} will grant access to the appended result. - * - * @return byte array and result - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature if there are no valid signatures found - */ - public ByteArrayAndResult toByteArrayAndResult() throws IOException, SOPGPException.NoSignature { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - T result = writeTo(bytes); - return new ByteArrayAndResult<>(bytes.toByteArray(), result); - } -} diff --git a/sop-java/src/main/kotlin/sop/ReadyWithResult.kt b/sop-java/src/main/kotlin/sop/ReadyWithResult.kt new file mode 100644 index 0000000..d309c76 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/ReadyWithResult.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream +import sop.exception.SOPGPException + +abstract class ReadyWithResult { + + /** + * Write the data e.g. decrypted plaintext to the provided output stream and return the result + * of the processing operation. + * + * @param outputStream output stream + * @return result, eg. signatures + * @throws IOException in case of an IO error + * @throws SOPGPException in case of a SOP protocol error + */ + @Throws(IOException::class, SOPGPException::class) + abstract fun writeTo(outputStream: OutputStream): T + + /** + * Return the data as a [ByteArrayAndResult]. Calling [ByteArrayAndResult.bytes] will give you + * access to the data as byte array, while [ByteArrayAndResult.result] will grant access to the + * appended result. + * + * @return byte array and result + * @throws IOException in case of an IO error + * @throws SOPGPException.NoSignature if there are no valid signatures found + */ + @Throws(IOException::class, SOPGPException::class) + fun toByteArrayAndResult() = + ByteArrayOutputStream().let { + val result = writeTo(it) + ByteArrayAndResult(it.toByteArray(), result) + } +} From 2391ffc9b2c5e6585f8eb24219e741eb7cfdd031 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:26:33 +0100 Subject: [PATCH 218/444] Kotlin conversion: DecryptionResult --- .../src/main/java/sop/DecryptionResult.java | 29 ------------------- .../src/main/kotlin/sop/DecryptionResult.kt | 16 ++++++++++ 2 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 sop-java/src/main/java/sop/DecryptionResult.java create mode 100644 sop-java/src/main/kotlin/sop/DecryptionResult.kt diff --git a/sop-java/src/main/java/sop/DecryptionResult.java b/sop-java/src/main/java/sop/DecryptionResult.java deleted file mode 100644 index 4f0e1ab..0000000 --- a/sop-java/src/main/java/sop/DecryptionResult.java +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import sop.util.Optional; - -public class DecryptionResult { - - private final Optional sessionKey; - private final List verifications; - - public DecryptionResult(SessionKey sessionKey, List verifications) { - this.sessionKey = Optional.ofNullable(sessionKey); - this.verifications = Collections.unmodifiableList(verifications); - } - - public Optional getSessionKey() { - return sessionKey; - } - - public List getVerifications() { - return new ArrayList<>(verifications); - } -} diff --git a/sop-java/src/main/kotlin/sop/DecryptionResult.kt b/sop-java/src/main/kotlin/sop/DecryptionResult.kt new file mode 100644 index 0000000..1ba98bb --- /dev/null +++ b/sop-java/src/main/kotlin/sop/DecryptionResult.kt @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.Optional + +data class DecryptionResult +internal constructor(val sessionKey: Optional, val verifications: List) { + + constructor( + sessionKey: SessionKey?, + verifications: List + ) : this(Optional.ofNullable(sessionKey), verifications) +} From dc23c8aa98a9f141af6051da1415b82dfe3a9f70 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:29:58 +0100 Subject: [PATCH 219/444] Kotlin conversion: Signatures --- sop-java/src/main/java/sop/Signatures.java | 21 --------------------- sop-java/src/main/kotlin/sop/Signatures.kt | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 21 deletions(-) delete mode 100644 sop-java/src/main/java/sop/Signatures.java create mode 100644 sop-java/src/main/kotlin/sop/Signatures.kt diff --git a/sop-java/src/main/java/sop/Signatures.java b/sop-java/src/main/java/sop/Signatures.java deleted file mode 100644 index dd3f000..0000000 --- a/sop-java/src/main/java/sop/Signatures.java +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import java.io.IOException; -import java.io.OutputStream; - -public abstract class Signatures extends Ready { - - /** - * Write OpenPGP signatures to the provided output stream. - * - * @param signatureOutputStream output stream - * @throws IOException in case of an IO error - */ - @Override - public abstract void writeTo(OutputStream signatureOutputStream) throws IOException; - -} diff --git a/sop-java/src/main/kotlin/sop/Signatures.kt b/sop-java/src/main/kotlin/sop/Signatures.kt new file mode 100644 index 0000000..63aafe9 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/Signatures.kt @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import java.io.IOException +import java.io.OutputStream + +abstract class Signatures : Ready() { + + /** + * Write OpenPGP signatures to the provided output stream. + * + * @param outputStream signature output stream + * @throws IOException in case of an IO error + */ + @Throws(IOException::class) abstract override fun writeTo(outputStream: OutputStream) +} From 31409b7949415b995c5a3a7cf52ad4af40b640bd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:38:53 +0100 Subject: [PATCH 220/444] Kotlin conversion: SOP --- sop-java/src/main/java/sop/SOP.java | 177 ---------------------------- sop-java/src/main/kotlin/sop/SOP.kt | 97 +++++++++++++++ 2 files changed, 97 insertions(+), 177 deletions(-) delete mode 100644 sop-java/src/main/java/sop/SOP.java create mode 100644 sop-java/src/main/kotlin/sop/SOP.kt diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java deleted file mode 100644 index 1200e21..0000000 --- a/sop-java/src/main/java/sop/SOP.java +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop; - -import sop.operation.Armor; -import sop.operation.ChangeKeyPassword; -import sop.operation.Dearmor; -import sop.operation.Decrypt; -import sop.operation.Encrypt; -import sop.operation.ExtractCert; -import sop.operation.GenerateKey; -import sop.operation.InlineDetach; -import sop.operation.InlineSign; -import sop.operation.InlineVerify; -import sop.operation.DetachedSign; -import sop.operation.DetachedVerify; -import sop.operation.ListProfiles; -import sop.operation.RevokeKey; -import sop.operation.Version; - -/** - * Stateless OpenPGP Interface. - * This class provides a stateless interface to various OpenPGP related operations. - *
- * Note: Subcommand objects acquired by calling any method of this interface are not intended for reuse. - * If you for example need to generate multiple keys, make a dedicated call to {@link #generateKey()} once per - * key generation. - */ -public interface SOP { - - /** - * Get information about the implementations name and version. - * - * @return version - */ - Version version(); - - /** - * Generate a secret key. - * Customize the operation using the builder {@link GenerateKey}. - * - * @return builder instance - */ - GenerateKey generateKey(); - - /** - * Extract a certificate (public key) from a secret key. - * Customize the operation using the builder {@link ExtractCert}. - * - * @return builder instance - */ - ExtractCert extractCert(); - - /** - * Create detached signatures. - * Customize the operation using the builder {@link DetachedSign}. - *

- * If you want to sign a message inline, use {@link #inlineSign()} instead. - * - * @return builder instance - */ - default DetachedSign sign() { - return detachedSign(); - } - - /** - * Create detached signatures. - * Customize the operation using the builder {@link DetachedSign}. - *

- * If you want to sign a message inline, use {@link #inlineSign()} instead. - * - * @return builder instance - */ - DetachedSign detachedSign(); - - /** - * Sign a message using inline signatures. - *

- * If you need to create detached signatures, use {@link #detachedSign()} instead. - * - * @return builder instance - */ - InlineSign inlineSign(); - - /** - * Verify detached signatures. - * Customize the operation using the builder {@link DetachedVerify}. - *

- * If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. - * - * @return builder instance - */ - default DetachedVerify verify() { - return detachedVerify(); - } - - /** - * Verify detached signatures. - * Customize the operation using the builder {@link DetachedVerify}. - *

- * If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. - * - * @return builder instance - */ - DetachedVerify detachedVerify(); - - /** - * Verify signatures of an inline-signed message. - *

- * If you need to verify detached signatures over a message, use {@link #detachedVerify()} instead. - * - * @return builder instance - */ - InlineVerify inlineVerify(); - - /** - * Detach signatures from an inline signed message. - * - * @return builder instance - */ - InlineDetach inlineDetach(); - - /** - * Encrypt a message. - * Customize the operation using the builder {@link Encrypt}. - * - * @return builder instance - */ - Encrypt encrypt(); - - /** - * Decrypt a message. - * Customize the operation using the builder {@link Decrypt}. - * - * @return builder instance - */ - Decrypt decrypt(); - - /** - * Convert binary OpenPGP data to ASCII. - * Customize the operation using the builder {@link Armor}. - * - * @return builder instance - */ - Armor armor(); - - /** - * Converts ASCII armored OpenPGP data to binary. - * Customize the operation using the builder {@link Dearmor}. - * - * @return builder instance - */ - Dearmor dearmor(); - - /** - * List supported {@link Profile Profiles} of a subcommand. - * - * @return builder instance - */ - ListProfiles listProfiles(); - - /** - * Revoke one or more secret keys. - * - * @return builder instance - */ - RevokeKey revokeKey(); - - /** - * Update a key's password. - * - * @return builder instance - */ - ChangeKeyPassword changeKeyPassword(); -} diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt new file mode 100644 index 0000000..e01763a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.operation.Armor +import sop.operation.ChangeKeyPassword +import sop.operation.Dearmor +import sop.operation.Decrypt +import sop.operation.DetachedSign +import sop.operation.DetachedVerify +import sop.operation.Encrypt +import sop.operation.ExtractCert +import sop.operation.GenerateKey +import sop.operation.InlineDetach +import sop.operation.InlineSign +import sop.operation.InlineVerify +import sop.operation.ListProfiles +import sop.operation.RevokeKey +import sop.operation.Version + +/** + * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related + * operations. Note: Subcommand objects acquired by calling any method of this interface are not + * intended for reuse. If you for example need to generate multiple keys, make a dedicated call to + * [generateKey] once per key generation. + */ +interface SOP { + + /** Get information about the implementations name and version. */ + fun version(): Version + + /** Generate a secret key. */ + fun generateKey(): GenerateKey + + /** Extract a certificate (public key) from a secret key. */ + fun extractCert(): ExtractCert + + /** + * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. + */ + fun sign(): DetachedSign = detachedSign() + + /** + * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. + */ + fun detachedSign(): DetachedSign + + /** + * Sign a message using inline signatures. If you need to create detached signatures, use + * [detachedSign] instead. + */ + fun inlineSign(): InlineSign + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun verify(): DetachedVerify = detachedVerify() + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun detachedVerify(): DetachedVerify + + /** + * Verify signatures of an inline-signed message. If you need to verify detached signatures over + * a message, use [detachedVerify] instead. + */ + fun inlineVerify(): InlineVerify + + /** Detach signatures from an inline signed message. */ + fun inlineDetach(): InlineDetach + + /** Encrypt a message. */ + fun encrypt(): Encrypt + + /** Decrypt a message. */ + fun decrypt(): Decrypt + + /** Convert binary OpenPGP data to ASCII. */ + fun armor(): Armor + + /** Converts ASCII armored OpenPGP data to binary. */ + fun dearmor(): Dearmor + + /** List supported [Profiles][Profile] of a subcommand. */ + fun listProfiles(): ListProfiles + + /** Revoke one or more secret keys. */ + fun revokeKey(): RevokeKey + + /** Update a key's password. */ + fun changeKeyPassword(): ChangeKeyPassword +} From e68d6df57f86da9b4881205fe7f6b455d25c8690 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:50:46 +0100 Subject: [PATCH 221/444] Kotlin conversion: AbstractSign --- .../main/java/sop/operation/AbstractSign.java | 85 ------------------- .../main/kotlin/sop/operation/AbstractSign.kt | 79 +++++++++++++++++ 2 files changed, 79 insertions(+), 85 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/AbstractSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/AbstractSign.kt diff --git a/sop-java/src/main/java/sop/operation/AbstractSign.java b/sop-java/src/main/java/sop/operation/AbstractSign.java deleted file mode 100644 index 508d741..0000000 --- a/sop-java/src/main/java/sop/operation/AbstractSign.java +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface AbstractSign { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - T noArmor(); - - /** - * Add one or more signing keys. - * - * @param key input stream containing encoded keys - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - T key(InputStream key) - throws SOPGPException.KeyCannotSign, - SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Add one or more signing keys. - * - * @param key byte array containing encoded keys - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default T key(byte[] key) - throws SOPGPException.KeyCannotSign, - SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return key(new ByteArrayInputStream(key)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable - */ - default T withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the provided passphrase is not human-readable - */ - T withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - -} diff --git a/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt new file mode 100644 index 0000000..0258432 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.KeyCannotSign +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.exception.SOPGPException.UnsupportedAsymmetricAlgo +import sop.exception.SOPGPException.UnsupportedOption +import sop.util.UTF8Util + +/** + * Interface for signing operations. + * + * @param builder subclass + */ +interface AbstractSign { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): T + + /** + * Add one or more signing keys. + * + * @param key input stream containing encoded keys + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws BadData if the [InputStream] does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun key(key: InputStream): T + + /** + * Add one or more signing keys. + * + * @param key byte array containing encoded keys + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun key(key: ByteArray): T = key(key.inputStream()) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the provided passphrase is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): T = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the provided passphrase is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): T +} From 08ddc5d8a529aa1acfb65b7e849a82b2ca596534 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:51:30 +0100 Subject: [PATCH 222/444] Kotlin conversion: AbstractVerify --- .../java/sop/operation/AbstractVerify.java | 68 ------------------- .../kotlin/sop/operation/AbstractVerify.kt | 57 ++++++++++++++++ 2 files changed, 57 insertions(+), 68 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/AbstractVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt diff --git a/sop-java/src/main/java/sop/operation/AbstractVerify.java b/sop-java/src/main/java/sop/operation/AbstractVerify.java deleted file mode 100644 index 51d84b7..0000000 --- a/sop-java/src/main/java/sop/operation/AbstractVerify.java +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -/** - * Common API methods shared between verification of inline signatures ({@link InlineVerify}) - * and verification of detached signatures ({@link DetachedVerify}). - * - * @param Builder type ({@link DetachedVerify}, {@link InlineVerify}) - */ -public interface AbstractVerify { - - /** - * Makes the SOP implementation consider signatures before this date invalid. - * - * @param timestamp timestamp - * @return builder instance - */ - T notBefore(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Makes the SOP implementation consider signatures after this date invalid. - * - * @param timestamp timestamp - * @return builder instance - */ - T notAfter(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Add one or more verification cert. - * - * @param cert input stream containing the encoded certs - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - T cert(InputStream cert) - throws SOPGPException.BadData, - IOException; - - /** - * Add one or more verification cert. - * - * @param cert byte array containing the encoded certs - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - default T cert(byte[] cert) - throws SOPGPException.BadData, - IOException { - return cert(new ByteArrayInputStream(cert)); - } - -} diff --git a/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt b/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt new file mode 100644 index 0000000..3554ea3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/AbstractVerify.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import java.util.* +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +/** + * Common API methods shared between verification of inline signatures ([InlineVerify]) and + * verification of detached signatures ([DetachedVerify]). + * + * @param Builder type ([DetachedVerify], [InlineVerify]) + */ +interface AbstractVerify { + + /** + * Makes the SOP implementation consider signatures before this date invalid. + * + * @param timestamp timestamp + * @return builder instance + */ + @Throws(UnsupportedOption::class) fun notBefore(timestamp: Date): T + + /** + * Makes the SOP implementation consider signatures after this date invalid. + * + * @param timestamp timestamp + * @return builder instance + */ + @Throws(UnsupportedOption::class) fun notAfter(timestamp: Date): T + + /** + * Add one or more verification cert. + * + * @param cert input stream containing the encoded certs + * @return builder instance + * @throws BadData if the input stream does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun cert(cert: InputStream): T + + /** + * Add one or more verification cert. + * + * @param cert byte array containing the encoded certs + * @return builder instance + * @throws BadData if the byte array does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun cert(cert: ByteArray): T = cert(cert.inputStream()) +} From 4a123a198078f81c80640716d51c574a956dccda Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 13:54:24 +0100 Subject: [PATCH 223/444] Kotlin conversion: Armor --- .../src/main/java/sop/operation/Armor.java | 53 ------------------- .../src/main/kotlin/sop/operation/Armor.kt | 46 ++++++++++++++++ 2 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Armor.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Armor.kt diff --git a/sop-java/src/main/java/sop/operation/Armor.java b/sop-java/src/main/java/sop/operation/Armor.java deleted file mode 100644 index a625808..0000000 --- a/sop-java/src/main/java/sop/operation/Armor.java +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.enums.ArmorLabel; -import sop.exception.SOPGPException; - -public interface Armor { - - /** - * Overrides automatic detection of label. - * - * @param label armor label - * @return builder instance - */ - Armor label(ArmorLabel label) - throws SOPGPException.UnsupportedOption; - - /** - * Armor the provided data. - * - * @param data input stream of unarmored OpenPGP data - * @return armored data - * - * @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken - * @throws IOException in case of an IO error - */ - Ready data(InputStream data) - throws SOPGPException.BadData, - IOException; - - /** - * Armor the provided data. - * - * @param data unarmored OpenPGP data - * @return armored data - * - * @throws sop.exception.SOPGPException.BadData if the data appears to be OpenPGP packets, but those are broken - * @throws IOException in case of an IO error - */ - default Ready data(byte[] data) - throws SOPGPException.BadData, - IOException { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Armor.kt b/sop-java/src/main/kotlin/sop/operation/Armor.kt new file mode 100644 index 0000000..e89708b --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Armor.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.enums.ArmorLabel +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +interface Armor { + + /** + * Overrides automatic detection of label. + * + * @param label armor label + * @return builder instance + */ + @Deprecated("Use of armor labels is deprecated and will be removed in a future release.") + @Throws(UnsupportedOption::class) + fun label(label: ArmorLabel): Armor + + /** + * Armor the provided data. + * + * @param data input stream of unarmored OpenPGP data + * @return armored data + * @throws BadData if the data appears to be OpenPGP packets, but those are broken + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready + + /** + * Armor the provided data. + * + * @param data unarmored OpenPGP data + * @return armored data + * @throws BadData if the data appears to be OpenPGP packets, but those are broken + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) +} From 34e1d8992f2b6adb626dd25dd6c940a233a687bc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:00:49 +0100 Subject: [PATCH 224/444] Kotlin conversion: ChangeKeyPassword --- .../java/sop/operation/ChangeKeyPassword.java | 83 ---------------- .../kotlin/sop/operation/ChangeKeyPassword.kt | 95 +++++++++++++++++++ 2 files changed, 95 insertions(+), 83 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ChangeKeyPassword.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt diff --git a/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java b/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java deleted file mode 100644 index 460a20a..0000000 --- a/sop-java/src/main/java/sop/operation/ChangeKeyPassword.java +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.nio.charset.CharacterCodingException; - -public interface ChangeKeyPassword { - - /** - * Disable ASCII armoring of the output. - * - * @return builder instance - */ - ChangeKeyPassword noArmor(); - - default ChangeKeyPassword oldKeyPassphrase(byte[] password) { - try { - return oldKeyPassphrase(UTF8Util.decodeUTF8(password)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); - } - } - - /** - * Provide a passphrase to unlock the secret key. - * This method can be provided multiple times to provide separate passphrases that are tried as a - * means to unlock any secret key material encountered. - * - * @param oldPassphrase old passphrase - * @return builder instance - */ - ChangeKeyPassword oldKeyPassphrase(String oldPassphrase); - - /** - * Provide a passphrase to re-lock the secret key with. - * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. - * If this method is not called, the key material will not be protected. - * - * @param newPassphrase new passphrase - * @return builder instance - */ - default ChangeKeyPassword newKeyPassphrase(byte[] newPassphrase) { - try { - return newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable("Password MUST be a valid UTF8 string."); - } - } - - /** - * Provide a passphrase to re-lock the secret key with. - * This method can only be used once, and all key material encountered will be encrypted with the given passphrase. - * If this method is not called, the key material will not be protected. - * - * @param newPassphrase new passphrase - * @return builder instance - */ - ChangeKeyPassword newKeyPassphrase(String newPassphrase); - - default Ready keys(byte[] keys) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { - return keys(new ByteArrayInputStream(keys)); - } - - /** - * Provide the key material. - * - * @param inputStream input stream of secret key material - * @return ready - * - * @throws sop.exception.SOPGPException.KeyIsProtected if any (sub-) key encountered cannot be unlocked. - * @throws sop.exception.SOPGPException.BadData if the key material is malformed - */ - Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData; - -} diff --git a/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt new file mode 100644 index 0000000..224e0f4 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.KeyIsProtected +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.util.UTF8Util + +interface ChangeKeyPassword { + + /** + * Disable ASCII armoring of the output. + * + * @return builder instance + */ + fun noArmor(): ChangeKeyPassword + + /** + * Provide a passphrase to unlock the secret key. This method can be provided multiple times to + * provide separate passphrases that are tried as a means to unlock any secret key material + * encountered. + * + * @param oldPassphrase old passphrase + * @return builder instance + */ + @Throws(PasswordNotHumanReadable::class) + fun oldKeyPassphrase(oldPassphrase: ByteArray): ChangeKeyPassword = + try { + oldKeyPassphrase(UTF8Util.decodeUTF8(oldPassphrase)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") + } + + /** + * Provide a passphrase to unlock the secret key. This method can be provided multiple times to + * provide separate passphrases that are tried as a means to unlock any secret key material + * encountered. + * + * @param oldPassphrase old passphrase + * @return builder instance + */ + fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword + + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + @Throws(PasswordNotHumanReadable::class) + fun newKeyPassphrase(newPassphrase: ByteArray): ChangeKeyPassword = + try { + newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") + } + + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + fun newKeyPassphrase(newPassphrase: String): ChangeKeyPassword + + /** + * Provide the key material. + * + * @param keys input stream of secret key material + * @return ready + * @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked. + * @throws BadData if the key material is malformed + */ + @Throws(KeyIsProtected::class, BadData::class) + fun keys(keys: ByteArray): Ready = keys(keys.inputStream()) + + /** + * Provide the key material. + * + * @param keys input stream of secret key material + * @return ready + * @throws KeyIsProtected if any (sub-) key encountered cannot be unlocked. + * @throws BadData if the key material is malformed + */ + @Throws(KeyIsProtected::class, BadData::class) fun keys(keys: InputStream): Ready +} From 39c222dfc893d780d8daaee8fd47854ecb17d143 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:03:18 +0100 Subject: [PATCH 225/444] Kotlin conversion: Dearmor --- .../src/main/java/sop/operation/Dearmor.java | 59 ------------------- .../src/main/kotlin/sop/operation/Dearmor.kt | 46 +++++++++++++++ 2 files changed, 46 insertions(+), 59 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Dearmor.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Dearmor.kt diff --git a/sop-java/src/main/java/sop/operation/Dearmor.java b/sop-java/src/main/java/sop/operation/Dearmor.java deleted file mode 100644 index 524dc8c..0000000 --- a/sop-java/src/main/java/sop/operation/Dearmor.java +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -public interface Dearmor { - - /** - * Dearmor armored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - Ready data(InputStream data) - throws SOPGPException.BadData, - IOException; - - /** - * Dearmor armored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - default Ready data(byte[] data) - throws SOPGPException.BadData, - IOException { - return data(new ByteArrayInputStream(data)); - } - - /** - * Dearmor amored OpenPGP data. - * - * @param data armored OpenPGP data - * @return input stream of unarmored data - * - * @throws SOPGPException.BadData in case of non-OpenPGP data - * @throws IOException in case of an IO error - */ - default Ready data(String data) - throws SOPGPException.BadData, - IOException { - return data(data.getBytes(UTF8Util.UTF8)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Dearmor.kt b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt new file mode 100644 index 0000000..cc5e98d --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData +import sop.util.UTF8Util + +interface Dearmor { + + /** + * Dearmor armored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) fun data(data: InputStream): Ready + + /** + * Dearmor armored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) + + /** + * Dearmor amored OpenPGP data. + * + * @param data armored OpenPGP data + * @return input stream of unarmored data + * @throws BadData in case of non-OpenPGP data + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun data(data: String): Ready = data(data.toByteArray(UTF8Util.UTF8)) +} From 91a861b5c366d54ddff4b1625e920e0e75d476cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:08:03 +0100 Subject: [PATCH 226/444] Kotlin conversion: Decrypt --- .../src/main/java/sop/operation/Decrypt.java | 193 ------------------ .../src/main/kotlin/sop/operation/Decrypt.kt | 169 +++++++++++++++ 2 files changed, 169 insertions(+), 193 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Decrypt.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Decrypt.kt diff --git a/sop-java/src/main/java/sop/operation/Decrypt.java b/sop-java/src/main/java/sop/operation/Decrypt.java deleted file mode 100644 index 0123bbc..0000000 --- a/sop-java/src/main/java/sop/operation/Decrypt.java +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -public interface Decrypt { - - /** - * Makes the SOP consider signatures before this date invalid. - * - * @param timestamp timestamp - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt verifyNotBefore(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Makes the SOP consider signatures after this date invalid. - * - * @param timestamp timestamp - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt verifyNotAfter(Date timestamp) - throws SOPGPException.UnsupportedOption; - - /** - * Adds one or more verification cert. - * - * @param cert input stream containing the cert(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} doesn't provide an OpenPGP certificate - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Decrypt verifyWithCert(InputStream cert) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Adds one or more verification cert. - * - * @param cert byte array containing the cert(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array doesn't contain an OpenPGP certificate - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default Decrypt verifyWithCert(byte[] cert) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return verifyWithCert(new ByteArrayInputStream(cert)); - } - - /** - * Tries to decrypt with the given session key. - * - * @param sessionKey session key - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt withSessionKey(SessionKey sessionKey) - throws SOPGPException.UnsupportedOption; - - /** - * Tries to decrypt with the given password. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Decrypt withPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Adds one or more decryption key. - * - * @param key input stream containing the key(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Decrypt withKey(InputStream key) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; - - /** - * Adds one or more decryption key. - * - * @param key byte array containing the key(s) - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - default Decrypt withKey(byte[] key) - throws SOPGPException.BadData, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException { - return withKey(new ByteArrayInputStream(key)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - default Decrypt withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - Decrypt withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - - /** - * Decrypts the given ciphertext, returning verification results and plaintext. - * @param ciphertext ciphertext - * @return ready with result - * - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not provide an OpenPGP message - * @throws sop.exception.SOPGPException.MissingArg if an argument required for decryption was not provided - * @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason - * @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) - * @throws IOException in case of an IO error - */ - ReadyWithResult ciphertext(InputStream ciphertext) - throws SOPGPException.BadData, - SOPGPException.MissingArg, - SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, - IOException; - - /** - * Decrypts the given ciphertext, returning verification results and plaintext. - * @param ciphertext ciphertext - * @return ready with result - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an encrypted OpenPGP message - * @throws sop.exception.SOPGPException.MissingArg in case of missing decryption method (password or key required) - * @throws sop.exception.SOPGPException.CannotDecrypt in case decryption fails for some reason - * @throws sop.exception.SOPGPException.KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) - * @throws IOException in case of an IO error - */ - default ReadyWithResult ciphertext(byte[] ciphertext) - throws SOPGPException.BadData, - SOPGPException.MissingArg, - SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, - IOException { - return ciphertext(new ByteArrayInputStream(ciphertext)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt new file mode 100644 index 0000000..9c24c54 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import java.util.* +import sop.DecryptionResult +import sop.ReadyWithResult +import sop.SessionKey +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface Decrypt { + + /** + * Makes the SOP consider signatures before this date invalid. + * + * @param timestamp timestamp + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun verifyNotBefore(timestamp: Date): Decrypt + + /** + * Makes the SOP consider signatures after this date invalid. + * + * @param timestamp timestamp + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun verifyNotAfter(timestamp: Date): Decrypt + + /** + * Adds one or more verification cert. + * + * @param cert input stream containing the cert(s) + * @return builder instance + * @throws BadData if the [InputStream] doesn't provide an OpenPGP certificate + * @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun verifyWithCert(cert: InputStream): Decrypt + + /** + * Adds one or more verification cert. + * + * @param cert byte array containing the cert(s) + * @return builder instance + * @throws BadData if the byte array doesn't contain an OpenPGP certificate + * @throws UnsupportedAsymmetricAlgo if the cert uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun verifyWithCert(cert: ByteArray): Decrypt = verifyWithCert(cert.inputStream()) + + /** + * Tries to decrypt with the given session key. + * + * @param sessionKey session key + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun withSessionKey(sessionKey: SessionKey): Decrypt + + /** + * Tries to decrypt with the given password. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if this option is not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withPassword(password: String): Decrypt + + /** + * Adds one or more decryption key. + * + * @param key input stream containing the key(s) + * @return builder instance + * @throws BadData if the [InputStream] does not provide an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun withKey(key: InputStream): Decrypt + + /** + * Adds one or more decryption key. + * + * @param key byte array containing the key(s) + * @return builder instance + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun withKey(key: ByteArray): Decrypt { + return withKey(key.inputStream()) + } + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): Decrypt { + return withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + } + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): Decrypt + + /** + * Decrypts the given ciphertext, returning verification results and plaintext. + * + * @param ciphertext ciphertext + * @return ready with result + * @throws BadData if the [InputStream] does not provide an OpenPGP message + * @throws MissingArg if an argument required for decryption was not provided + * @throws CannotDecrypt in case decryption fails for some reason + * @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) + * @throws IOException in case of an IO error + */ + @Throws( + BadData::class, + MissingArg::class, + CannotDecrypt::class, + KeyIsProtected::class, + IOException::class) + fun ciphertext(ciphertext: InputStream): ReadyWithResult + + /** + * Decrypts the given ciphertext, returning verification results and plaintext. + * + * @param ciphertext ciphertext + * @return ready with result + * @throws BadData if the byte array does not contain an encrypted OpenPGP message + * @throws MissingArg in case of missing decryption method (password or key required) + * @throws CannotDecrypt in case decryption fails for some reason + * @throws KeyIsProtected if the decryption key cannot be unlocked (e.g. missing passphrase) + * @throws IOException in case of an IO error + */ + @Throws( + BadData::class, + MissingArg::class, + CannotDecrypt::class, + KeyIsProtected::class, + IOException::class) + fun ciphertext(ciphertext: ByteArray): ReadyWithResult { + return ciphertext(ciphertext.inputStream()) + } +} From 4dc1779a06be4aadffdd39855cb6e4da081cde00 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:10:06 +0100 Subject: [PATCH 227/444] Kotlin conversion: DetachedSign --- .../main/java/sop/operation/DetachedSign.java | 61 ------------------- .../main/kotlin/sop/operation/DetachedSign.kt | 50 +++++++++++++++ 2 files changed, 50 insertions(+), 61 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/DetachedSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/DetachedSign.kt diff --git a/sop-java/src/main/java/sop/operation/DetachedSign.java b/sop-java/src/main/java/sop/operation/DetachedSign.java deleted file mode 100644 index 745077d..0000000 --- a/sop-java/src/main/java/sop/operation/DetachedSign.java +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.ReadyWithResult; -import sop.SigningResult; -import sop.enums.SignAs; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface DetachedSign extends AbstractSign { - - /** - * Sets the signature mode. - * Note: This method has to be called before {@link #key(InputStream)} is called. - * - * @param mode signature mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - DetachedSign mode(SignAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Signs data. - * - * @param data input stream containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - ReadyWithResult data(InputStream data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText; - - /** - * Signs data. - * - * @param data byte array containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - default ReadyWithResult data(byte[] data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt new file mode 100644 index 0000000..c0e62dd --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.SigningResult +import sop.enums.SignAs +import sop.exception.SOPGPException.* + +interface DetachedSign : AbstractSign { + + /** + * Sets the signature mode. Note: This method has to be called before [key] is called. + * + * @param mode signature mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: SignAs): DetachedSign + + /** + * Signs data. + * + * @param data input stream containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: InputStream): ReadyWithResult + + /** + * Signs data. + * + * @param data byte array containing data + * @return ready + * @throws IOException in case of an IO error + * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be + * unlocked + * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data + * was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: ByteArray): ReadyWithResult = data(data.inputStream()) +} From ee6975c7d3ab039b917aef476de44af7f542bef2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:11:11 +0100 Subject: [PATCH 228/444] Decrypt: Use return statement --- sop-java/src/main/kotlin/sop/operation/Decrypt.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt index 9c24c54..ae228e9 100644 --- a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt @@ -99,9 +99,7 @@ interface Decrypt { * @throws IOException in case of an IO error */ @Throws(BadData::class, UnsupportedAsymmetricAlgo::class, IOException::class) - fun withKey(key: ByteArray): Decrypt { - return withKey(key.inputStream()) - } + fun withKey(key: ByteArray): Decrypt = withKey(key.inputStream()) /** * Provide the decryption password for the secret key. @@ -112,9 +110,8 @@ interface Decrypt { * @throws PasswordNotHumanReadable if the password is not human-readable */ @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) - fun withKeyPassword(password: String): Decrypt { - return withKeyPassword(password.toByteArray(UTF8Util.UTF8)) - } + fun withKeyPassword(password: String): Decrypt = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) /** * Provide the decryption password for the secret key. @@ -163,7 +160,6 @@ interface Decrypt { CannotDecrypt::class, KeyIsProtected::class, IOException::class) - fun ciphertext(ciphertext: ByteArray): ReadyWithResult { - return ciphertext(ciphertext.inputStream()) - } + fun ciphertext(ciphertext: ByteArray): ReadyWithResult = + ciphertext(ciphertext.inputStream()) } From e681090757312dedcc90cc9fc35ee7f64118c99d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:12:56 +0100 Subject: [PATCH 229/444] Kotlin conversion DetachedVerify --- .../java/sop/operation/DetachedVerify.java | 45 ------------------- .../kotlin/sop/operation/DetachedVerify.kt | 36 +++++++++++++++ 2 files changed, 36 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/DetachedVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt diff --git a/sop-java/src/main/java/sop/operation/DetachedVerify.java b/sop-java/src/main/java/sop/operation/DetachedVerify.java deleted file mode 100644 index 9dee870..0000000 --- a/sop-java/src/main/java/sop/operation/DetachedVerify.java +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * API for verifying detached signatures. - */ -public interface DetachedVerify extends AbstractVerify, VerifySignatures { - - /** - * Provides the detached signatures. - * @param signatures input stream containing encoded, detached signatures. - * - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain OpenPGP signatures - * @throws IOException in case of an IO error - */ - VerifySignatures signatures(InputStream signatures) - throws SOPGPException.BadData, - IOException; - - /** - * Provides the detached signatures. - * @param signatures byte array containing encoded, detached signatures. - * - * @return builder instance - * - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain OpenPGP signatures - * @throws IOException in case of an IO error - */ - default VerifySignatures signatures(byte[] signatures) - throws SOPGPException.BadData, - IOException { - return signatures(new ByteArrayInputStream(signatures)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt new file mode 100644 index 0000000..2dd9218 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException.BadData + +interface DetachedVerify : AbstractVerify, VerifySignatures { + + /** + * Provides the detached signatures. + * + * @param signatures input stream containing encoded, detached signatures. + * @return builder instance + * @throws BadData if the input stream does not contain OpenPGP signatures + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun signatures(signatures: InputStream): VerifySignatures + + /** + * Provides the detached signatures. + * + * @param signatures byte array containing encoded, detached signatures. + * @return builder instance + * @throws BadData if the byte array does not contain OpenPGP signatures + * @throws IOException in case of an IO error + */ + @Throws(BadData::class, IOException::class) + fun signatures(signatures: ByteArray): VerifySignatures = + signatures(ByteArrayInputStream(signatures)) +} From 41db9d2ac77d7b8e5c2fe79ae02ec36efb6bc240 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:15:56 +0100 Subject: [PATCH 230/444] Kotlin conversion: Encrypt --- .../src/main/java/sop/operation/Encrypt.java | 193 ------------------ .../src/main/kotlin/sop/operation/Encrypt.kt | 166 +++++++++++++++ 2 files changed, 166 insertions(+), 193 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Encrypt.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Encrypt.kt diff --git a/sop-java/src/main/java/sop/operation/Encrypt.java b/sop-java/src/main/java/sop/operation/Encrypt.java deleted file mode 100644 index b380d32..0000000 --- a/sop-java/src/main/java/sop/operation/Encrypt.java +++ /dev/null @@ -1,193 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Profile; -import sop.Ready; -import sop.enums.EncryptAs; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface Encrypt { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - Encrypt noArmor(); - - /** - * Sets encryption mode. - * - * @param mode mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Encrypt mode(EncryptAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Adds the signer key. - * - * @param key input stream containing the encoded signer key - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - * @throws IOException in case of an IO error - */ - Encrypt signWith(InputStream key) - throws SOPGPException.KeyCannotSign, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException; - - /** - * Adds the signer key. - * - * @param key byte array containing the encoded signer key - * @return builder instance - * - * @throws sop.exception.SOPGPException.KeyCannotSign if the key cannot be used for signing - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - * @throws IOException in case of an IO error - */ - default Encrypt signWith(byte[] key) - throws SOPGPException.KeyCannotSign, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException { - return signWith(new ByteArrayInputStream(key)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported - */ - default Encrypt withKeyPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the password for the secret key used for signing. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key password are not supported - */ - Encrypt withKeyPassword(byte[] password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Encrypt with the given password. - * - * @param password password - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - Encrypt withPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Encrypt with the given cert. - * - * @param cert input stream containing the encoded cert. - * @return builder instance - * - * @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - Encrypt withCert(InputStream cert) - throws SOPGPException.CertCannotEncrypt, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException; - - /** - * Encrypt with the given cert. - * - * @param cert byte array containing the encoded cert. - * @return builder instance - * - * @throws sop.exception.SOPGPException.CertCannotEncrypt if the certificate is not encryption capable - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP certificate - * @throws IOException in case of an IO error - */ - default Encrypt withCert(byte[] cert) - throws SOPGPException.CertCannotEncrypt, - SOPGPException.UnsupportedAsymmetricAlgo, - SOPGPException.BadData, - IOException { - return withCert(new ByteArrayInputStream(cert)); - } - - /** - * Pass in a profile. - * - * @param profile profile - * @return builder instance - */ - default Encrypt profile(Profile profile) { - return profile(profile.getName()); - } - - /** - * Pass in a profile identifier. - * - * @param profileName profile identifier - * @return builder instance - */ - Encrypt profile(String profileName); - - /** - * Encrypt the given data yielding the ciphertext. - * @param plaintext plaintext - * @return input stream containing the ciphertext - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - */ - Ready plaintext(InputStream plaintext) - throws IOException, - SOPGPException.KeyIsProtected; - - /** - * Encrypt the given data yielding the ciphertext. - * @param plaintext plaintext - * @return input stream containing the ciphertext - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - */ - default Ready plaintext(byte[] plaintext) - throws IOException, - SOPGPException.KeyIsProtected { - return plaintext(new ByteArrayInputStream(plaintext)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt new file mode 100644 index 0000000..ad30149 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.Profile +import sop.Ready +import sop.enums.EncryptAs +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface Encrypt { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): Encrypt + + /** + * Sets encryption mode. + * + * @param mode mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: EncryptAs): Encrypt + + /** + * Adds the signer key. + * + * @param key input stream containing the encoded signer key + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws BadData if the [InputStream] does not contain an OpenPGP key + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) + fun signWith(key: InputStream): Encrypt + + /** + * Adds the signer key. + * + * @param key byte array containing the encoded signer key + * @return builder instance + * @throws KeyCannotSign if the key cannot be used for signing + * @throws UnsupportedAsymmetricAlgo if the key uses an unsupported asymmetric algorithm + * @throws BadData if the byte array does not contain an OpenPGP key + * @throws IOException in case of an IO error + */ + @Throws( + KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) + fun signWith(key: ByteArray): Encrypt = signWith(ByteArrayInputStream(key)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key password are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: String): Encrypt = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key password are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): Encrypt + + /** + * Encrypt with the given password. + * + * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if this option is not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withPassword(password: String): Encrypt + + /** + * Encrypt with the given cert. + * + * @param cert input stream containing the encoded cert. + * @return builder instance + * @throws CertCannotEncrypt if the certificate is not encryption capable + * @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm + * @throws BadData if the [InputStream] does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws( + CertCannotEncrypt::class, + UnsupportedAsymmetricAlgo::class, + BadData::class, + IOException::class) + fun withCert(cert: InputStream): Encrypt + + /** + * Encrypt with the given cert. + * + * @param cert byte array containing the encoded cert. + * @return builder instance + * @throws CertCannotEncrypt if the certificate is not encryption capable + * @throws UnsupportedAsymmetricAlgo if the certificate uses an unsupported asymmetric algorithm + * @throws BadData if the byte array does not contain an OpenPGP certificate + * @throws IOException in case of an IO error + */ + @Throws( + CertCannotEncrypt::class, + UnsupportedAsymmetricAlgo::class, + BadData::class, + IOException::class) + fun withCert(cert: ByteArray): Encrypt = withCert(ByteArrayInputStream(cert)) + + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + fun profile(profile: Profile): Encrypt = profile(profile.name) + + /** + * Pass in a profile identifier. + * + * @param profileName profile identifier + * @return builder instance + */ + fun profile(profileName: String): Encrypt + + /** + * Encrypt the given data yielding the ciphertext. + * + * @param plaintext plaintext + * @return input stream containing the ciphertext + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + */ + @Throws(IOException::class, KeyIsProtected::class) fun plaintext(plaintext: InputStream): Ready + + /** + * Encrypt the given data yielding the ciphertext. + * + * @param plaintext plaintext + * @return input stream containing the ciphertext + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + */ + @Throws(IOException::class, KeyIsProtected::class) + fun plaintext(plaintext: ByteArray): Ready = plaintext(ByteArrayInputStream(plaintext)) +} From 653675f730788593566aba75879870d4e39f652e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:17:12 +0100 Subject: [PATCH 231/444] Kotlin conversion: ExtractCert --- .../main/java/sop/operation/ExtractCert.java | 50 ------------------- .../main/kotlin/sop/operation/ExtractCert.kt | 42 ++++++++++++++++ 2 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ExtractCert.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ExtractCert.kt diff --git a/sop-java/src/main/java/sop/operation/ExtractCert.java b/sop-java/src/main/java/sop/operation/ExtractCert.java deleted file mode 100644 index a862d33..0000000 --- a/sop-java/src/main/java/sop/operation/ExtractCert.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.Ready; -import sop.exception.SOPGPException; - -public interface ExtractCert { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - ExtractCert noArmor(); - - /** - * Extract the cert(s) from the provided key(s). - * - * @param keyInputStream input stream containing the encoding of one or more OpenPGP keys - * @return result containing the encoding of the keys certs - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the {@link InputStream} does not contain an OpenPGP key - */ - Ready key(InputStream keyInputStream) - throws IOException, - SOPGPException.BadData; - - /** - * Extract the cert(s) from the provided key(s). - * - * @param key byte array containing the encoding of one or more OpenPGP key - * @return result containing the encoding of the keys certs - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain an OpenPGP key - */ - default Ready key(byte[] key) - throws IOException, - SOPGPException.BadData { - return key(new ByteArrayInputStream(key)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt new file mode 100644 index 0000000..bd48e08 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.BadData + +interface ExtractCert { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): ExtractCert + + /** + * Extract the cert(s) from the provided key(s). + * + * @param keyInputStream input stream containing the encoding of one or more OpenPGP keys + * @return result containing the encoding of the keys certs + * @throws IOException in case of an IO error + * @throws BadData if the [InputStream] does not contain an OpenPGP key + */ + @Throws(IOException::class, BadData::class) fun key(keyInputStream: InputStream): Ready + + /** + * Extract the cert(s) from the provided key(s). + * + * @param key byte array containing the encoding of one or more OpenPGP key + * @return result containing the encoding of the keys certs + * @throws IOException in case of an IO error + * @throws BadData if the byte array does not contain an OpenPGP key + */ + @Throws(IOException::class, BadData::class) + fun key(key: ByteArray): Ready = key(ByteArrayInputStream(key)) +} From 3e6ebe1cc44968b1ecad4d6244fc43f874a2aa64 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:19:08 +0100 Subject: [PATCH 232/444] Kotlin conversion: GenerateKey --- .../main/java/sop/operation/GenerateKey.java | 103 ------------------ .../main/kotlin/sop/operation/GenerateKey.kt | 91 ++++++++++++++++ 2 files changed, 91 insertions(+), 103 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/GenerateKey.java create mode 100644 sop-java/src/main/kotlin/sop/operation/GenerateKey.kt diff --git a/sop-java/src/main/java/sop/operation/GenerateKey.java b/sop-java/src/main/java/sop/operation/GenerateKey.java deleted file mode 100644 index 77afea1..0000000 --- a/sop-java/src/main/java/sop/operation/GenerateKey.java +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.CharacterCodingException; - -import sop.Profile; -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -public interface GenerateKey { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - GenerateKey noArmor(); - - /** - * Adds a user-id. - * - * @param userId user-id - * @return builder instance - */ - GenerateKey userId(String userId); - - /** - * Set a password for the key. - * - * @param password password to protect the key - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - GenerateKey withKeyPassword(String password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption; - - /** - * Set a password for the key. - * - * @param password password to protect the key - * @return builder instance - * - * @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable - * @throws sop.exception.SOPGPException.UnsupportedOption if key passwords are not supported - */ - default GenerateKey withKeyPassword(byte[] password) - throws SOPGPException.PasswordNotHumanReadable, - SOPGPException.UnsupportedOption { - try { - return withKeyPassword(UTF8Util.decodeUTF8(password)); - } catch (CharacterCodingException e) { - throw new SOPGPException.PasswordNotHumanReadable(); - } - } - - /** - * Pass in a profile. - * - * @param profile profile - * @return builder instance - */ - default GenerateKey profile(Profile profile) { - return profile(profile.getName()); - } - - /** - * Pass in a profile identifier. - * - * @param profile profile identifier - * @return builder instance - */ - GenerateKey profile(String profile); - - /** - * If this options is set, the generated key will not be capable of encryption / decryption. - * - * @return builder instance - */ - GenerateKey signingOnly(); - - /** - * Generate the OpenPGP key and return it encoded as an {@link InputStream}. - * - * @return key - * - * @throws sop.exception.SOPGPException.MissingArg if no user-id was provided - * @throws sop.exception.SOPGPException.UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric algorithm - * @throws IOException in case of an IO error - */ - Ready generate() - throws SOPGPException.MissingArg, - SOPGPException.UnsupportedAsymmetricAlgo, - IOException; -} diff --git a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt new file mode 100644 index 0000000..3b83b99 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import sop.Profile +import sop.Ready +import sop.exception.SOPGPException.* +import sop.util.UTF8Util + +interface GenerateKey { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): GenerateKey + + /** + * Adds a user-id. + * + * @param userId user-id + * @return builder instance + */ + fun userId(userId: String): GenerateKey + + /** + * Set a password for the key. + * + * @param password password to protect the key + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: String): GenerateKey + + /** + * Set a password for the key. + * + * @param password password to protect the key + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if key passwords are not supported + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): GenerateKey = + try { + withKeyPassword(UTF8Util.decodeUTF8(password)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable() + } + + /** + * Pass in a profile. + * + * @param profile profile + * @return builder instance + */ + fun profile(profile: Profile): GenerateKey = profile(profile.name) + + /** + * Pass in a profile identifier. + * + * @param profile profile identifier + * @return builder instance + */ + fun profile(profile: String): GenerateKey + + /** + * If this options is set, the generated key will not be capable of encryption / decryption. + * + * @return builder instance + */ + fun signingOnly(): GenerateKey + + /** + * Generate the OpenPGP key and return it encoded as an [InputStream]. + * + * @return key + * @throws MissingArg if no user-id was provided + * @throws UnsupportedAsymmetricAlgo if the generated key uses an unsupported asymmetric + * algorithm + * @throws IOException in case of an IO error + */ + @Throws(MissingArg::class, UnsupportedAsymmetricAlgo::class, IOException::class) + fun generate(): Ready +} From 8df4a520bdb6c2e37ea81cf71f0cf7e8b422eb7e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:20:29 +0100 Subject: [PATCH 233/444] Kotlin conversion: InlineDetach --- .../main/java/sop/operation/InlineDetach.java | 52 ------------------- .../main/kotlin/sop/operation/InlineDetach.kt | 43 +++++++++++++++ 2 files changed, 43 insertions(+), 52 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineDetach.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineDetach.kt diff --git a/sop-java/src/main/java/sop/operation/InlineDetach.java b/sop-java/src/main/java/sop/operation/InlineDetach.java deleted file mode 100644 index aba40b1..0000000 --- a/sop-java/src/main/java/sop/operation/InlineDetach.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import sop.ReadyWithResult; -import sop.Signatures; -import sop.exception.SOPGPException; - -/** - * Split cleartext signed messages up into data and signatures. - */ -public interface InlineDetach { - - /** - * Do not wrap the signatures in ASCII armor. - * @return builder - */ - InlineDetach noArmor(); - - /** - * Detach the provided signed message from its signatures. - * - * @param messageInputStream input stream containing the signed message - * @return result containing the detached message - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the input stream does not contain a signed message - */ - ReadyWithResult message(InputStream messageInputStream) - throws IOException, - SOPGPException.BadData; - - /** - * Detach the provided cleartext signed message from its signatures. - * - * @param message byte array containing the signed message - * @return result containing the detached message - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.BadData if the byte array does not contain a signed message - */ - default ReadyWithResult message(byte[] message) - throws IOException, - SOPGPException.BadData { - return message(new ByteArrayInputStream(message)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt new file mode 100644 index 0000000..941a9bf --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.Signatures +import sop.exception.SOPGPException.BadData + +interface InlineDetach { + + /** + * Do not wrap the signatures in ASCII armor. + * + * @return builder + */ + fun noArmor(): InlineDetach + + /** + * Detach the provided signed message from its signatures. + * + * @param messageInputStream input stream containing the signed message + * @return result containing the detached message + * @throws IOException in case of an IO error + * @throws BadData if the input stream does not contain a signed message + */ + @Throws(IOException::class, BadData::class) + fun message(messageInputStream: InputStream): ReadyWithResult + + /** + * Detach the provided cleartext signed message from its signatures. + * + * @param message byte array containing the signed message + * @return result containing the detached message + * @throws IOException in case of an IO error + * @throws BadData if the byte array does not contain a signed message + */ + @Throws(IOException::class, BadData::class) + fun message(message: ByteArray): ReadyWithResult = message(message.inputStream()) +} From 9283f81c56662d63c565dfd2aaac14868c4bbc3d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:22:12 +0100 Subject: [PATCH 234/444] Replace ByteArrayInputStream with inputStream() --- sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt | 4 +--- sop-java/src/main/kotlin/sop/operation/Encrypt.kt | 7 +++---- sop-java/src/main/kotlin/sop/operation/ExtractCert.kt | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt index 2dd9218..d899b54 100644 --- a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt +++ b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.exception.SOPGPException.BadData @@ -31,6 +30,5 @@ interface DetachedVerify : AbstractVerify, VerifySignatures { * @throws IOException in case of an IO error */ @Throws(BadData::class, IOException::class) - fun signatures(signatures: ByteArray): VerifySignatures = - signatures(ByteArrayInputStream(signatures)) + fun signatures(signatures: ByteArray): VerifySignatures = signatures(signatures.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt index ad30149..0daebee 100644 --- a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.Profile @@ -57,7 +56,7 @@ interface Encrypt { */ @Throws( KeyCannotSign::class, UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) - fun signWith(key: ByteArray): Encrypt = signWith(ByteArrayInputStream(key)) + fun signWith(key: ByteArray): Encrypt = signWith(key.inputStream()) /** * Provide the password for the secret key used for signing. @@ -125,7 +124,7 @@ interface Encrypt { UnsupportedAsymmetricAlgo::class, BadData::class, IOException::class) - fun withCert(cert: ByteArray): Encrypt = withCert(ByteArrayInputStream(cert)) + fun withCert(cert: ByteArray): Encrypt = withCert(cert.inputStream()) /** * Pass in a profile. @@ -162,5 +161,5 @@ interface Encrypt { * @throws KeyIsProtected if at least one signing key cannot be unlocked */ @Throws(IOException::class, KeyIsProtected::class) - fun plaintext(plaintext: ByteArray): Ready = plaintext(ByteArrayInputStream(plaintext)) + fun plaintext(plaintext: ByteArray): Ready = plaintext(plaintext.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt index bd48e08..e2ce1cc 100644 --- a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt +++ b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt @@ -4,7 +4,6 @@ package sop.operation -import java.io.ByteArrayInputStream import java.io.IOException import java.io.InputStream import sop.Ready @@ -38,5 +37,5 @@ interface ExtractCert { * @throws BadData if the byte array does not contain an OpenPGP key */ @Throws(IOException::class, BadData::class) - fun key(key: ByteArray): Ready = key(ByteArrayInputStream(key)) + fun key(key: ByteArray): Ready = key(key.inputStream()) } From be0ceb0886a9b6a51b51a4519b4098e761ad0cf3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:25:25 +0100 Subject: [PATCH 235/444] Kotlin conversion: InlineSign --- .../main/java/sop/operation/InlineSign.java | 60 ------------------- .../main/kotlin/sop/operation/InlineSign.kt | 47 +++++++++++++++ 2 files changed, 47 insertions(+), 60 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineSign.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineSign.kt diff --git a/sop-java/src/main/java/sop/operation/InlineSign.java b/sop-java/src/main/java/sop/operation/InlineSign.java deleted file mode 100644 index d45aebd..0000000 --- a/sop-java/src/main/java/sop/operation/InlineSign.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.enums.InlineSignAs; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -public interface InlineSign extends AbstractSign { - - /** - * Sets the signature mode. - * Note: This method has to be called before {@link #key(InputStream)} is called. - * - * @param mode signature mode - * @return builder instance - * - * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported - */ - InlineSign mode(InlineSignAs mode) - throws SOPGPException.UnsupportedOption; - - /** - * Signs data. - * - * @param data input stream containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - Ready data(InputStream data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText; - - /** - * Signs data. - * - * @param data byte array containing data - * @return ready - * - * @throws IOException in case of an IO error - * @throws sop.exception.SOPGPException.KeyIsProtected if at least one signing key cannot be unlocked - * @throws sop.exception.SOPGPException.ExpectedText if text data was expected, but binary data was encountered - */ - default Ready data(byte[] data) - throws IOException, - SOPGPException.KeyIsProtected, - SOPGPException.ExpectedText { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineSign.kt b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt new file mode 100644 index 0000000..11b5668 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Ready +import sop.enums.InlineSignAs +import sop.exception.SOPGPException.* + +interface InlineSign : AbstractSign { + + /** + * Sets the signature mode. Note: This method has to be called before [.key] is called. + * + * @param mode signature mode + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun mode(mode: InlineSignAs): InlineSign + + /** + * Signs data. + * + * @param data input stream containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: InputStream): Ready + + /** + * Signs data. + * + * @param data byte array containing data + * @return ready + * @throws IOException in case of an IO error + * @throws KeyIsProtected if at least one signing key cannot be unlocked + * @throws ExpectedText if text data was expected, but binary data was encountered + */ + @Throws(IOException::class, KeyIsProtected::class, ExpectedText::class) + fun data(data: ByteArray): Ready = data(data.inputStream()) +} From 6c14f249bb66ba8ca019c8235fe2c21112baf876 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:25:47 +0100 Subject: [PATCH 236/444] Kotlin conversion: InlineVerify --- .../main/java/sop/operation/InlineVerify.java | 54 ------------------- .../main/kotlin/sop/operation/InlineVerify.kt | 42 +++++++++++++++ 2 files changed, 42 insertions(+), 54 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/InlineVerify.java create mode 100644 sop-java/src/main/kotlin/sop/operation/InlineVerify.kt diff --git a/sop-java/src/main/java/sop/operation/InlineVerify.java b/sop-java/src/main/java/sop/operation/InlineVerify.java deleted file mode 100644 index ac662a0..0000000 --- a/sop-java/src/main/java/sop/operation/InlineVerify.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.ReadyWithResult; -import sop.Verification; -import sop.exception.SOPGPException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -/** - * API for verification of inline-signed messages. - */ -public interface InlineVerify extends AbstractVerify { - - /** - * Provide the inline-signed data. - * The result can be used to write the plaintext message out and to get the verifications. - * - * @param data signed data - * @return list of signature verifications - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - ReadyWithResult> data(InputStream data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData; - - /** - * Provide the inline-signed data. - * The result can be used to write the plaintext message out and to get the verifications. - * - * @param data signed data - * @return list of signature verifications - * - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - default ReadyWithResult> data(byte[] data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt new file mode 100644 index 0000000..c16b269 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.ReadyWithResult +import sop.Verification +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.NoSignature + +/** API for verification of inline-signed messages. */ +interface InlineVerify : AbstractVerify { + + /** + * Provide the inline-signed data. The result can be used to write the plaintext message out and + * to get the verifications. + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: InputStream): ReadyWithResult> + + /** + * Provide the inline-signed data. The result can be used to write the plaintext message out and + * to get the verifications. + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: ByteArray): ReadyWithResult> = data(data.inputStream()) +} From 145cadef4f7557a52529eaea1a04eb3bcb484868 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:27:17 +0100 Subject: [PATCH 237/444] Kotlin conversion: ListProfiles --- .../main/java/sop/operation/ListProfiles.java | 43 ------------------- .../main/kotlin/sop/operation/ListProfiles.kt | 34 +++++++++++++++ 2 files changed, 34 insertions(+), 43 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/ListProfiles.java create mode 100644 sop-java/src/main/kotlin/sop/operation/ListProfiles.kt diff --git a/sop-java/src/main/java/sop/operation/ListProfiles.java b/sop-java/src/main/java/sop/operation/ListProfiles.java deleted file mode 100644 index 0c17bd6..0000000 --- a/sop-java/src/main/java/sop/operation/ListProfiles.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Profile; - -import java.util.List; - -/** - * Subcommand to list supported profiles of other subcommands. - */ -public interface ListProfiles { - - /** - * Provide the name of the subcommand for which profiles shall be listed. - * The returned list of profiles MUST NOT contain more than 4 entries. - * - * @param command command name (e.g.

generate-key
) - * @return list of profiles. - */ - List subcommand(String command); - - /** - * Return a list of {@link Profile Profiles} supported by the {@link GenerateKey} implementation. - * - * @return profiles - */ - default List generateKey() { - return subcommand("generate-key"); - } - - /** - * Return a list of {@link Profile Profiles} supported by the {@link Encrypt} implementation. - * - * @return profiles - */ - default List encrypt() { - return subcommand("encrypt"); - } - -} diff --git a/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt new file mode 100644 index 0000000..315faf2 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Profile + +/** Subcommand to list supported profiles of other subcommands. */ +interface ListProfiles { + + /** + * Provide the name of the subcommand for which profiles shall be listed. The returned list of + * profiles MUST NOT contain more than 4 entries. + * + * @param command command name (e.g. `generate-key`) + * @return list of profiles. + */ + fun subcommand(command: String): List + + /** + * Return a list of [Profiles][Profile] supported by the [GenerateKey] implementation. + * + * @return profiles + */ + fun generateKey(): List = subcommand("generate-key") + + /** + * Return a list of [Profiles][Profile] supported by the [Encrypt] implementation. + * + * @return profiles + */ + fun encrypt(): List = subcommand("encrypt") +} From 0ee4638bebf88f28fc434f9c78b6fef45c8a877a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:28:59 +0100 Subject: [PATCH 238/444] Kotlin conversion: RevokeKey --- .../main/java/sop/operation/RevokeKey.java | 54 ------------------- .../main/kotlin/sop/operation/RevokeKey.kt | 48 +++++++++++++++++ 2 files changed, 48 insertions(+), 54 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/RevokeKey.java create mode 100644 sop-java/src/main/kotlin/sop/operation/RevokeKey.kt diff --git a/sop-java/src/main/java/sop/operation/RevokeKey.java b/sop-java/src/main/java/sop/operation/RevokeKey.java deleted file mode 100644 index 3ceb5b3..0000000 --- a/sop-java/src/main/java/sop/operation/RevokeKey.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -public interface RevokeKey { - - /** - * Disable ASCII armor encoding. - * - * @return builder instance - */ - RevokeKey noArmor(); - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - default RevokeKey withKeyPassword(String password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable { - return withKeyPassword(password.getBytes(UTF8Util.UTF8)); - } - - /** - * Provide the decryption password for the secret key. - * - * @param password password - * @return builder instance - * @throws SOPGPException.UnsupportedOption if the implementation does not support key passwords - * @throws SOPGPException.PasswordNotHumanReadable if the password is not human-readable - */ - RevokeKey withKeyPassword(byte[] password) - throws SOPGPException.UnsupportedOption, - SOPGPException.PasswordNotHumanReadable; - - default Ready keys(byte[] bytes) { - return keys(new ByteArrayInputStream(bytes)); - } - - Ready keys(InputStream keys); -} diff --git a/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt new file mode 100644 index 0000000..f3cbe5c --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException.PasswordNotHumanReadable +import sop.exception.SOPGPException.UnsupportedOption +import sop.util.UTF8Util + +interface RevokeKey { + + /** + * Disable ASCII armor encoding. + * + * @return builder instance + */ + fun noArmor(): RevokeKey + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: String): RevokeKey = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + * @throws PasswordNotHumanReadable if the password is not human-readable + */ + @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + fun withKeyPassword(password: ByteArray): RevokeKey + + fun keys(bytes: ByteArray): Ready = keys(bytes.inputStream()) + + fun keys(keys: InputStream): Ready +} From a8c2e72ef589bdeea16df71c2901842b6fdd5220 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:30:24 +0100 Subject: [PATCH 239/444] Kotlin conversion: VerifySignatures --- .../java/sop/operation/VerifySignatures.java | 46 ------------------- .../kotlin/sop/operation/VerifySignatures.kt | 38 +++++++++++++++ 2 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/VerifySignatures.java create mode 100644 sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt diff --git a/sop-java/src/main/java/sop/operation/VerifySignatures.java b/sop-java/src/main/java/sop/operation/VerifySignatures.java deleted file mode 100644 index 5181514..0000000 --- a/sop-java/src/main/java/sop/operation/VerifySignatures.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import sop.Verification; -import sop.exception.SOPGPException; - -public interface VerifySignatures { - - /** - * Provide the signed data (without signatures). - * - * @param data signed data - * @return list of signature verifications - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no valid signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - List data(InputStream data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData; - - /** - * Provide the signed data (without signatures). - * - * @param data signed data - * @return list of signature verifications - * @throws IOException in case of an IO error - * @throws SOPGPException.NoSignature when no valid signature is found - * @throws SOPGPException.BadData when the data is invalid OpenPGP data - */ - default List data(byte[] data) - throws IOException, - SOPGPException.NoSignature, - SOPGPException.BadData { - return data(new ByteArrayInputStream(data)); - } -} diff --git a/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt new file mode 100644 index 0000000..b75e4a5 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.Verification +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.NoSignature + +interface VerifySignatures { + + /** + * Provide the signed data (without signatures). + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no valid signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: InputStream): List + + /** + * Provide the signed data (without signatures). + * + * @param data signed data + * @return list of signature verifications + * @throws IOException in case of an IO error + * @throws NoSignature when no valid signature is found + * @throws BadData when the data is invalid OpenPGP data + */ + @Throws(IOException::class, NoSignature::class, BadData::class) + fun data(data: ByteArray): List = data(data.inputStream()) +} From d0ee9c2066ce0d72cbbd5fb9bcbdf09fbaaa3f17 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:38:34 +0100 Subject: [PATCH 240/444] Kotlin conversion: Version --- .../src/main/java/sop/operation/Version.java | 109 ------------------ .../main/java/sop/operation/package-info.java | 9 -- .../src/main/kotlin/sop/operation/Version.kt | 100 ++++++++++++++++ 3 files changed, 100 insertions(+), 118 deletions(-) delete mode 100644 sop-java/src/main/java/sop/operation/Version.java delete mode 100644 sop-java/src/main/java/sop/operation/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/operation/Version.kt diff --git a/sop-java/src/main/java/sop/operation/Version.java b/sop-java/src/main/java/sop/operation/Version.java deleted file mode 100644 index b6d66b9..0000000 --- a/sop-java/src/main/java/sop/operation/Version.java +++ /dev/null @@ -1,109 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.operation; - -public interface Version { - - /** - * Return the implementations name. - * e.g. "SOP", - * - * @return implementation name - */ - String getName(); - - /** - * Return the implementations short version string. - * e.g. "1.0" - * - * @return version string - */ - String getVersion(); - - /** - * Return version information about the used OpenPGP backend. - * e.g. "Bouncycastle 1.70" - * - * @return backend version string - */ - String getBackendVersion(); - - /** - * Return an extended version string containing multiple lines of version information. - * The first line MUST match the information produced by {@link #getName()} and {@link #getVersion()}, but the rest of the text - * has no defined structure. - * Example: - *
-     *     "SOP 1.0
-     *     Awesome PGP!
-     *     Using Bouncycastle 1.70
-     *     LibFoo 1.2.2
-     *     See https://pgp.example.org/sop/ for more information"
-     * 
- * - * @return extended version string - */ - String getExtendedVersion(); - - /** - * Return the revision of the SOP specification that this implementation is implementing, for example, - *
draft-dkg-openpgp-stateless-cli-06
. - * If the implementation targets a specific draft but the implementer knows the implementation is incomplete, - * it should prefix the draft title with a "~" (TILDE, U+007E), for example: - *
~draft-dkg-openpgp-stateless-cli-06
. - * The implementation MAY emit additional text about its relationship to the targeted draft on the lines following - * the versioned title. - * - * @return implemented SOP spec version - */ - default String getSopSpecVersion() { - StringBuilder sb = new StringBuilder(); - if (isSopSpecImplementationIncomplete()) { - sb.append('~'); - } - - sb.append(getSopSpecRevisionName()); - - if (getSopSpecImplementationRemarks() != null) { - sb.append('\n') - .append('\n') - .append(getSopSpecImplementationRemarks()); - } - - return sb.toString(); - } - - /** - * Return the version number of the latest targeted SOP spec revision. - * - * @return SOP spec revision number - */ - int getSopSpecRevisionNumber(); - - /** - * Return the name of the latest targeted revision of the SOP spec. - * - * @return SOP spec revision string - */ - default String getSopSpecRevisionName() { - return "draft-dkg-openpgp-stateless-cli-" + String.format("%02d", getSopSpecRevisionNumber()); - } - - /** - * Return
true
, if this implementation of the SOP spec is known to be incomplete or defective. - * - * @return true if incomplete, false otherwise - */ - boolean isSopSpecImplementationIncomplete(); - - /** - * Return free-form text containing remarks about the completeness of the SOP implementation. - * If there are no remarks, this method returns
null
. - * - * @return remarks or null - */ - String getSopSpecImplementationRemarks(); - -} diff --git a/sop-java/src/main/java/sop/operation/package-info.java b/sop-java/src/main/java/sop/operation/package-info.java deleted file mode 100644 index dde4d5b..0000000 --- a/sop-java/src/main/java/sop/operation/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Different cryptographic operations. - */ -package sop.operation; diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt new file mode 100644 index 0000000..9b3bd8a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +interface Version { + + /** + * Return the implementations name. e.g. `SOP`, + * + * @return implementation name + */ + fun getName(): String + + /** + * Return the implementations short version string. e.g. `1.0` + * + * @return version string + */ + fun getVersion(): String + + /** + * Return version information about the used OpenPGP backend. e.g. `Bouncycastle 1.70` + * + * @return backend version string + */ + fun getBackendVersion(): String + + /** + * Return an extended version string containing multiple lines of version information. The first + * line MUST match the information produced by [getName] and [getVersion], but the rest of the + * text has no defined structure. Example: + * ``` + * "SOP 1.0 + * Awesome PGP! + * Using Bouncycastle 1.70 + * LibFoo 1.2.2 + * See https://pgp.example.org/sop/ for more information" + * ``` + * + * @return extended version string + */ + fun getExtendedVersion(): String + + /** + * Return the revision of the SOP specification that this implementation is implementing, for + * example, `draft-dkg-openpgp-stateless-cli-06`. If the implementation targets a specific draft + * but the implementer knows the implementation is incomplete, it should prefix the draft title + * with a `~` (TILDE, U+007E), for example: `~draft-dkg-openpgp-stateless-cli-06`. The + * implementation MAY emit additional text about its relationship to the targeted draft on the + * lines following the versioned title. + * + * @return implemented SOP spec version + */ + fun getSopSpecVersion(): String { + return buildString { + if (isSopSpecImplementationIncomplete()) append('~') + append(getSopSpecRevisionName()) + if (getSopSpecImplementationRemarks() != null) { + append('\n') + append('\n') + append(getSopSpecImplementationRemarks()) + } + } + } + + /** + * Return the version number of the latest targeted SOP spec revision. + * + * @return SOP spec revision number + */ + fun getSopSpecRevisionNumber(): Int + + /** + * Return the name of the latest targeted revision of the SOP spec. + * + * @return SOP spec revision string + */ + fun getSopSpecRevisionName(): String = buildString { + append("draft-dkg-openpgp-stateless-cli-") + append(String.format("%02d", getSopSpecRevisionNumber())) + } + + /** + * Return
true
, if this implementation of the SOP spec is known to be incomplete or + * defective. + * + * @return true if incomplete, false otherwise + */ + fun isSopSpecImplementationIncomplete(): Boolean + + /** + * Return free-form text containing remarks about the completeness of the SOP implementation. If + * there are no remarks, this method returns
null
. + * + * @return remarks or null + */ + fun getSopSpecImplementationRemarks(): String? +} From 049c18c17ba3907057c65f39dbfa5bf57a0f2960 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:45:56 +0100 Subject: [PATCH 241/444] Fix sop-java-picocli jar task --- sop-java-picocli/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 664c385..438ef50 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -38,6 +38,7 @@ application { jar { dependsOn(":sop-java:jar") + duplicatesStrategy(DuplicatesStrategy.EXCLUDE) manifest { attributes 'Main-Class': "$mainClassName" From 4b9e2c206f18bcec162bb3a82674c151a61239fc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:46:41 +0100 Subject: [PATCH 242/444] Fix DecryptionResult constructor --- sop-java/src/main/kotlin/sop/DecryptionResult.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/DecryptionResult.kt b/sop-java/src/main/kotlin/sop/DecryptionResult.kt index 1ba98bb..b653297 100644 --- a/sop-java/src/main/kotlin/sop/DecryptionResult.kt +++ b/sop-java/src/main/kotlin/sop/DecryptionResult.kt @@ -6,11 +6,10 @@ package sop import sop.util.Optional -data class DecryptionResult -internal constructor(val sessionKey: Optional, val verifications: List) { +class DecryptionResult(sessionKey: SessionKey?, val verifications: List) { + val sessionKey: Optional - constructor( - sessionKey: SessionKey?, - verifications: List - ) : this(Optional.ofNullable(sessionKey), verifications) + init { + this.sessionKey = Optional.ofNullable(sessionKey) + } } From d5c0d4e390ccd0b0c0171c621eed497d8c897a3b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:47:14 +0100 Subject: [PATCH 243/444] Kotlin conversion: ArmorLabel --- .../src/main/java/sop/enums/ArmorLabel.java | 19 ------------------- .../src/main/kotlin/sop/enums/ArmorLabel.kt | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 19 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/ArmorLabel.java create mode 100644 sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt diff --git a/sop-java/src/main/java/sop/enums/ArmorLabel.java b/sop-java/src/main/java/sop/enums/ArmorLabel.java deleted file mode 100644 index bb97e84..0000000 --- a/sop-java/src/main/java/sop/enums/ArmorLabel.java +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum ArmorLabel { - Auto, - Sig, - Key, - Cert, - Message, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt b/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt new file mode 100644 index 0000000..8b4e2cd --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +@Deprecated("Use of armor labels is deprecated.") +enum class ArmorLabel { + auto, + sig, + key, + cert, + message +} From 1c290e0c8f4cae89108adcae5d79334513c49822 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:48:48 +0100 Subject: [PATCH 244/444] Kotlin conversion: EncryptAs --- sop-java/src/main/java/sop/enums/EncryptAs.java | 16 ---------------- sop-java/src/main/kotlin/sop/enums/EncryptAs.kt | 10 ++++++++++ .../testsuite/operation/EncryptDecryptTest.java | 4 ++-- 3 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/EncryptAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/EncryptAs.kt diff --git a/sop-java/src/main/java/sop/enums/EncryptAs.java b/sop-java/src/main/java/sop/enums/EncryptAs.java deleted file mode 100644 index 7e7d4d1..0000000 --- a/sop-java/src/main/java/sop/enums/EncryptAs.java +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum EncryptAs { - Binary, - Text, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt b/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt new file mode 100644 index 0000000..0b6aa8e --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/EncryptAs.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class EncryptAs { + binary, + text +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 0c382dc..9138f64 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -142,7 +142,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(EncryptAs.Binary) + .mode(EncryptAs.binary) .plaintext(message) .getBytes(); @@ -173,7 +173,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(EncryptAs.Text) + .mode(EncryptAs.text) .plaintext(message) .getBytes(); From be6be3deac79a922f24285d45b024438b22b3b04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:50:09 +0100 Subject: [PATCH 245/444] Kotlin conversion: InlineSignAs --- .../src/main/java/sop/enums/InlineSignAs.java | 24 ------------------- .../src/main/kotlin/sop/enums/InlineSignAs.kt | 17 +++++++++++++ 2 files changed, 17 insertions(+), 24 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/InlineSignAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt diff --git a/sop-java/src/main/java/sop/enums/InlineSignAs.java b/sop-java/src/main/java/sop/enums/InlineSignAs.java deleted file mode 100644 index c1097df..0000000 --- a/sop-java/src/main/java/sop/enums/InlineSignAs.java +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum InlineSignAs { - - /** - * Signature is made over the binary message. - */ - binary, - - /** - * Signature is made over the message in text mode. - */ - text, - - /** - * Signature is made using the Cleartext Signature Framework. - */ - clearsigned, -} - diff --git a/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt b/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt new file mode 100644 index 0000000..3440edf --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/InlineSignAs.kt @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class InlineSignAs { + + /** Signature is made over the binary message. */ + binary, + + /** Signature is made over the message in text mode. */ + text, + + /** Signature is made using the Cleartext Signature Framework. */ + clearsigned +} From 30c369d24a10b50b64064963b643e0cd203efec9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:51:16 +0100 Subject: [PATCH 246/444] Kotlin conversion: SignAs --- sop-java/src/main/java/sop/enums/SignAs.java | 23 ------------------- sop-java/src/main/kotlin/sop/enums/SignAs.kt | 12 ++++++++++ .../DetachedSignDetachedVerifyTest.java | 2 +- 3 files changed, 13 insertions(+), 24 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/SignAs.java create mode 100644 sop-java/src/main/kotlin/sop/enums/SignAs.kt diff --git a/sop-java/src/main/java/sop/enums/SignAs.java b/sop-java/src/main/java/sop/enums/SignAs.java deleted file mode 100644 index 1174098..0000000 --- a/sop-java/src/main/java/sop/enums/SignAs.java +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -public enum SignAs { - /** - * Signature is made over the binary message. - */ - Binary, - - /** - * Signature is made over the message in text mode. - */ - Text, - ; - - @Override - public String toString() { - return super.toString().toLowerCase(); - } -} diff --git a/sop-java/src/main/kotlin/sop/enums/SignAs.kt b/sop-java/src/main/kotlin/sop/enums/SignAs.kt new file mode 100644 index 0000000..832e831 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/SignAs.kt @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +enum class SignAs { + /** Signature is made over the binary message. */ + binary, + /** Signature is made over the message in text mode. */ + text +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java index e715c14..e404599 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -62,7 +62,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] signature = sop.detachedSign() .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) - .mode(SignAs.Text) + .mode(SignAs.text) .data(message) .toByteArrayAndResult() .getBytes(); From 01f98df80b4f5e6757b887fe99b3c1efc21ceb10 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 14:52:45 +0100 Subject: [PATCH 247/444] Kotlin conversion: SignatureMode --- .../main/java/sop/enums/SignatureMode.java | 25 ------------------- .../src/main/java/sop/enums/package-info.java | 9 ------- .../main/kotlin/sop/enums/SignatureMode.kt | 18 +++++++++++++ 3 files changed, 18 insertions(+), 34 deletions(-) delete mode 100644 sop-java/src/main/java/sop/enums/SignatureMode.java delete mode 100644 sop-java/src/main/java/sop/enums/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/enums/SignatureMode.kt diff --git a/sop-java/src/main/java/sop/enums/SignatureMode.java b/sop-java/src/main/java/sop/enums/SignatureMode.java deleted file mode 100644 index 71ce7d8..0000000 --- a/sop-java/src/main/java/sop/enums/SignatureMode.java +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums; - -/** - * Enum referencing relevant signature types. - * - * @see - * RFC4880 §5.2.1 - Signature Types - */ -public enum SignatureMode { - /** - * Signature of a binary document (
0x00
). - */ - binary, - - /** - * Signature of a canonical text document (
0x01
). - */ - text - - // Other Signature Types are irrelevant. -} diff --git a/sop-java/src/main/java/sop/enums/package-info.java b/sop-java/src/main/java/sop/enums/package-info.java deleted file mode 100644 index 67148d3..0000000 --- a/sop-java/src/main/java/sop/enums/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Enumerations. - */ -package sop.enums; diff --git a/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt b/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt new file mode 100644 index 0000000..7213195 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/enums/SignatureMode.kt @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.enums + +/** + * Enum referencing relevant signature types. + * + * @see RFC4880 §5.2.1 - Signature + * Types + */ +enum class SignatureMode { + /** Signature of a binary document (type `0x00`). */ + binary, + /** Signature of a canonical text document (type `0x01`). */ + text +} From b7007cc0070659c58e0e9cad520acd58b6b07342 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:00:21 +0100 Subject: [PATCH 248/444] Kotlin conversion: HexUtil --- sop-java/src/main/java/sop/util/HexUtil.java | 47 -------------------- sop-java/src/main/kotlin/sop/util/HexUtil.kt | 38 ++++++++++++++++ 2 files changed, 38 insertions(+), 47 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/HexUtil.java create mode 100644 sop-java/src/main/kotlin/sop/util/HexUtil.kt diff --git a/sop-java/src/main/java/sop/util/HexUtil.java b/sop-java/src/main/java/sop/util/HexUtil.java deleted file mode 100644 index 9b88f53..0000000 --- a/sop-java/src/main/java/sop/util/HexUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2021 Paul Schaub, @maybeWeCouldStealAVan, @Dave L. -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -public class HexUtil { - - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - - /** - * Encode a byte array to a hex string. - * - * @see - * How to convert a byte array to a hex string in Java? - * @param bytes bytes - * @return hex encoding - */ - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars); - } - - /** - * Decode a hex string into a byte array. - * - * @see - * Convert a string representation of a hex dump to a byte array using Java? - * @param s hex string - * @return decoded byte array - */ - public static byte[] hexToBytes(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - return data; - } -} diff --git a/sop-java/src/main/kotlin/sop/util/HexUtil.kt b/sop-java/src/main/kotlin/sop/util/HexUtil.kt new file mode 100644 index 0000000..f372137 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/HexUtil.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +class HexUtil { + + companion object { + /** + * Encode a byte array to a hex string. + * + * @param bytes bytes + * @return hex encoding + * @see + * [Convert Byte Arrays to Hex Strings in Kotlin](https://www.baeldung.com/kotlin/byte-arrays-to-hex-strings) + */ + @JvmStatic fun bytesToHex(bytes: ByteArray): String = bytes.toHex() + + /** + * Decode a hex string into a byte array. + * + * @param s hex string + * @return decoded byte array + * @see + * [Kotlin convert hex string to ByteArray](https://stackoverflow.com/a/66614516/11150851) + */ + @JvmStatic fun hexToBytes(s: String): ByteArray = s.decodeHex() + } +} + +fun String.decodeHex(): ByteArray { + check(length % 2 == 0) { "Hex encoding must have even number of digits." } + + return chunked(2).map { it.toInt(16).toByte() }.toByteArray() +} + +fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02X".format(eachByte) } From 05886228df497c18ce94ffd756fc2075e48b0a95 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:07:13 +0100 Subject: [PATCH 249/444] Kotlin conversion: ProxyOutputStream --- .../main/java/sop/util/ProxyOutputStream.java | 80 ------------------- .../main/kotlin/sop/util/ProxyOutputStream.kt | 75 +++++++++++++++++ 2 files changed, 75 insertions(+), 80 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/ProxyOutputStream.java create mode 100644 sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt diff --git a/sop-java/src/main/java/sop/util/ProxyOutputStream.java b/sop-java/src/main/java/sop/util/ProxyOutputStream.java deleted file mode 100644 index ed24fc2..0000000 --- a/sop-java/src/main/java/sop/util/ProxyOutputStream.java +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * {@link OutputStream} that buffers data being written into it, until its underlying output stream is being replaced. - * At that point, first all the buffered data is being written to the underlying stream, followed by any successive - * data that may get written to the {@link ProxyOutputStream}. - *

- * This class is useful if we need to provide an {@link OutputStream} at one point in time when the final - * target output stream is not yet known. - */ -public class ProxyOutputStream extends OutputStream { - - private final ByteArrayOutputStream buffer; - private OutputStream swapped; - - public ProxyOutputStream() { - this.buffer = new ByteArrayOutputStream(); - } - - public synchronized void replaceOutputStream(OutputStream underlying) throws IOException { - if (underlying == null) { - throw new NullPointerException("Underlying OutputStream cannot be null."); - } - this.swapped = underlying; - - byte[] bufferBytes = buffer.toByteArray(); - swapped.write(bufferBytes); - } - - @Override - public synchronized void write(byte[] b) throws IOException { - if (swapped == null) { - buffer.write(b); - } else { - swapped.write(b); - } - } - - @Override - public synchronized void write(byte[] b, int off, int len) throws IOException { - if (swapped == null) { - buffer.write(b, off, len); - } else { - swapped.write(b, off, len); - } - } - - @Override - public synchronized void flush() throws IOException { - buffer.flush(); - if (swapped != null) { - swapped.flush(); - } - } - - @Override - public synchronized void close() throws IOException { - buffer.close(); - if (swapped != null) { - swapped.close(); - } - } - - @Override - public synchronized void write(int i) throws IOException { - if (swapped == null) { - buffer.write(i); - } else { - swapped.write(i); - } - } -} diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt new file mode 100644 index 0000000..142f7b3 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream + +/** + * [OutputStream] that buffers data being written into it, until its underlying output stream is + * being replaced. At that point, first all the buffered data is being written to the underlying + * stream, followed by any successive data that may get written to the [ProxyOutputStream]. This + * class is useful if we need to provide an [OutputStream] at one point in time when the final + * target output stream is not yet known. + */ +class ProxyOutputStream { + private val buffer = ByteArrayOutputStream() + private var swapped: OutputStream? = null + + @Synchronized + fun replaceOutputStream(underlying: OutputStream) { + this.swapped = underlying + swapped!!.write(buffer.toByteArray()) + } + + @Synchronized + @Throws(IOException::class) + fun write(b: ByteArray) { + if (swapped == null) { + buffer.write(b) + } else { + swapped!!.write(b) + } + } + + @Synchronized + @Throws(IOException::class) + fun write(b: ByteArray, off: Int, len: Int) { + if (swapped == null) { + buffer.write(b, off, len) + } else { + swapped!!.write(b, off, len) + } + } + + @Synchronized + @Throws(IOException::class) + fun flush() { + buffer.flush() + if (swapped != null) { + swapped!!.flush() + } + } + + @Synchronized + @Throws(IOException::class) + fun close() { + buffer.close() + if (swapped != null) { + swapped!!.close() + } + } + + @Synchronized + @Throws(IOException::class) + fun write(i: Int) { + if (swapped == null) { + buffer.write(i) + } else { + swapped!!.write(i) + } + } +} From 25a33611fdab6eba7cb3625677d37ccd5c16fd7d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:10:46 +0100 Subject: [PATCH 250/444] Kotlin conversion: UTCUtil --- sop-java/src/main/java/sop/util/UTCUtil.java | 65 -------------------- sop-java/src/main/kotlin/sop/util/UTCUtil.kt | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 65 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/UTCUtil.java create mode 100644 sop-java/src/main/kotlin/sop/util/UTCUtil.kt diff --git a/sop-java/src/main/java/sop/util/UTCUtil.java b/sop-java/src/main/java/sop/util/UTCUtil.java deleted file mode 100644 index 96a6b9c..0000000 --- a/sop-java/src/main/java/sop/util/UTCUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import javax.annotation.Nonnull; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -/** - * Utility class to parse and format dates as ISO-8601 UTC timestamps. - */ -public class UTCUtil { - - public static final SimpleDateFormat UTC_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - public static final SimpleDateFormat[] UTC_PARSERS = new SimpleDateFormat[] { - UTC_FORMATTER, - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), - new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'") - }; - - static { - for (SimpleDateFormat f : UTC_PARSERS) { - f.setTimeZone(TimeZone.getTimeZone("UTC")); - } - } - /** - * Parse an ISO-8601 UTC timestamp from a string. - * - * @param dateString string - * @return date - * @throws ParseException if the date string is malformed and cannot be parsed - */ - @Nonnull - public static Date parseUTCDate(String dateString) throws ParseException { - ParseException exception = null; - for (SimpleDateFormat parser : UTC_PARSERS) { - try { - return parser.parse(dateString); - } catch (ParseException e) { - // Store first exception (that of UTC_FORMATTER) to throw if we fail to parse the date - if (exception == null) { - exception = e; - } - // Try next parser - } - } - // No parser worked, so we throw the store exception - throw exception; - } - - /** - * Format a date as ISO-8601 UTC timestamp. - * - * @param date date - * @return timestamp string - */ - public static String formatUTCDate(Date date) { - return UTC_FORMATTER.format(date); - } -} diff --git a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt new file mode 100644 index 0000000..8924f25 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class UTCUtil { + + companion object { + + @JvmStatic val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + @JvmStatic + val UTC_PARSERS = + arrayOf( + UTC_FORMATTER, + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"), + SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"), + SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")) + .onEach { fmt -> fmt.timeZone = TimeZone.getTimeZone("UTC") } + + /** + * Parse an ISO-8601 UTC timestamp from a string. + * + * @param dateString string + * @return date + * @throws ParseException if the date string is malformed and cannot be parsed + */ + @JvmStatic + @Throws(ParseException::class) + fun parseUTCDate(dateString: String): Date { + var exception: ParseException? = null + for (parser in UTC_PARSERS) { + try { + return parser.parse(dateString) + } catch (e: ParseException) { + // Store first exception (that of UTC_FORMATTER) to throw if we fail to parse + // the date + if (exception == null) { + exception = e + } + // Try next parser + } + } + throw exception!! + } + + /** + * Format a date as ISO-8601 UTC timestamp. + * + * @param date date + * @return timestamp string + */ + @JvmStatic + fun formatUTCDate(date: Date): String { + return UTC_FORMATTER.format(date) + } + } +} From e1a6ffd07a7e081e207f84d105344013f9535b17 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:18:39 +0100 Subject: [PATCH 251/444] Use @JvmField annotation --- sop-java/src/main/kotlin/sop/util/UTCUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt index 8924f25..4ae13bc 100644 --- a/sop-java/src/main/kotlin/sop/util/UTCUtil.kt +++ b/sop-java/src/main/kotlin/sop/util/UTCUtil.kt @@ -12,8 +12,8 @@ class UTCUtil { companion object { - @JvmStatic val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") - @JvmStatic + @JvmField val UTC_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + @JvmField val UTC_PARSERS = arrayOf( UTC_FORMATTER, From 94b428ef62cc9735fad20cca4560eb14af6a2b41 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:18:48 +0100 Subject: [PATCH 252/444] Kotlin conversion: UTF8Util --- sop-java/src/main/java/sop/util/UTF8Util.java | 37 ------------------- .../src/main/java/sop/util/package-info.java | 8 ---- sop-java/src/main/kotlin/sop/util/UTF8Util.kt | 37 +++++++++++++++++++ 3 files changed, 37 insertions(+), 45 deletions(-) delete mode 100644 sop-java/src/main/java/sop/util/UTF8Util.java delete mode 100644 sop-java/src/main/java/sop/util/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/util/UTF8Util.kt diff --git a/sop-java/src/main/java/sop/util/UTF8Util.java b/sop-java/src/main/java/sop/util/UTF8Util.java deleted file mode 100644 index 1b4941b..0000000 --- a/sop-java/src/main/java/sop/util/UTF8Util.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CodingErrorAction; - -public class UTF8Util { - - public static final Charset UTF8 = Charset.forName("UTF8"); - private static final CharsetDecoder UTF8Decoder = UTF8 - .newDecoder() - .onUnmappableCharacter(CodingErrorAction.REPORT) - .onMalformedInput(CodingErrorAction.REPORT); - - /** - * Detect non-valid UTF8 data. - * - * @see ante on StackOverflow - * @param data utf-8 encoded bytes - * - * @return decoded string - * @throws CharacterCodingException if the input data does not resemble UTF8 - */ - public static String decodeUTF8(byte[] data) - throws CharacterCodingException { - ByteBuffer byteBuffer = ByteBuffer.wrap(data); - CharBuffer charBuffer = UTF8Decoder.decode(byteBuffer); - return charBuffer.toString(); - } -} diff --git a/sop-java/src/main/java/sop/util/package-info.java b/sop-java/src/main/java/sop/util/package-info.java deleted file mode 100644 index 3dd9fc1..0000000 --- a/sop-java/src/main/java/sop/util/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Utility classes. - */ -package sop.util; diff --git a/sop-java/src/main/kotlin/sop/util/UTF8Util.kt b/sop-java/src/main/kotlin/sop/util/UTF8Util.kt new file mode 100644 index 0000000..770f32c --- /dev/null +++ b/sop-java/src/main/kotlin/sop/util/UTF8Util.kt @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.util + +import java.nio.ByteBuffer +import java.nio.charset.Charset +import java.nio.charset.CodingErrorAction + +class UTF8Util { + companion object { + @JvmField val UTF8: Charset = Charset.forName("UTF8") + + @JvmStatic + private val UTF8Decoder = + UTF8.newDecoder() + .onUnmappableCharacter(CodingErrorAction.REPORT) + .onMalformedInput(CodingErrorAction.REPORT) + + /** + * Detect non-valid UTF8 data. + * + * @param data utf-8 encoded bytes + * @return decoded string + * @throws CharacterCodingException if the input data does not resemble UTF8 + * @see [ante on StackOverflow](https://stackoverflow.com/a/1471193) + */ + @JvmStatic + @Throws(CharacterCodingException::class) + fun decodeUTF8(data: ByteArray): String { + val byteBuffer = ByteBuffer.wrap(data) + val charBuffer = UTF8Decoder.decode(byteBuffer) + return charBuffer.toString() + } + } +} From 7824ee92c536f990de01c63d07c2dbfd68f4f579 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:27:47 +0100 Subject: [PATCH 253/444] Kotlin conversion: SOPGPException --- .../java/sop/exception/SOPGPException.java | 473 ------------------ .../main/java/sop/exception/package-info.java | 9 - sop-java/src/main/java/sop/package-info.java | 8 - .../kotlin/sop/exception/SOPGPException.kt | 308 ++++++++++++ 4 files changed, 308 insertions(+), 490 deletions(-) delete mode 100644 sop-java/src/main/java/sop/exception/SOPGPException.java delete mode 100644 sop-java/src/main/java/sop/exception/package-info.java delete mode 100644 sop-java/src/main/java/sop/package-info.java create mode 100644 sop-java/src/main/kotlin/sop/exception/SOPGPException.kt diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java deleted file mode 100644 index 1d13065..0000000 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ /dev/null @@ -1,473 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.exception; - -public abstract class SOPGPException extends RuntimeException { - - public SOPGPException() { - super(); - } - - public SOPGPException(String message) { - super(message); - } - - public SOPGPException(Throwable e) { - super(e); - } - - public SOPGPException(String message, Throwable cause) { - super(message, cause); - } - - public abstract int getExitCode(); - - /** - * No acceptable signatures found (sop verify, inline-verify). - */ - public static class NoSignature extends SOPGPException { - - public static final int EXIT_CODE = 3; - - public NoSignature() { - this("No verifiable signature found."); - } - - public NoSignature(String message) { - super(message); - } - - public NoSignature(String errorMsg, NoSignature e) { - super(errorMsg, e); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign). - */ - public static class UnsupportedAsymmetricAlgo extends SOPGPException { - - public static final int EXIT_CODE = 13; - - public UnsupportedAsymmetricAlgo(String message, Throwable e) { - super(message, e); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Certificate not encryption capable (e,g, expired, revoked, unacceptable usage). - */ - public static class CertCannotEncrypt extends SOPGPException { - public static final int EXIT_CODE = 17; - - public CertCannotEncrypt(String message, Throwable cause) { - super(message, cause); - } - - public CertCannotEncrypt(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Missing required argument. - */ - public static class MissingArg extends SOPGPException { - - public static final int EXIT_CODE = 19; - - public MissingArg() { - - } - - public MissingArg(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Incomplete verification instructions (sop decrypt). - */ - public static class IncompleteVerification extends SOPGPException { - - public static final int EXIT_CODE = 23; - - public IncompleteVerification(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unable to decrypt (sop decrypt). - */ - public static class CannotDecrypt extends SOPGPException { - - public static final int EXIT_CODE = 29; - - public CannotDecrypt() { - - } - - public CannotDecrypt(String errorMsg, Throwable e) { - super(errorMsg, e); - } - - public CannotDecrypt(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Non-UTF-8 or otherwise unreliable password (sop encrypt). - */ - public static class PasswordNotHumanReadable extends SOPGPException { - - public static final int EXIT_CODE = 31; - - public PasswordNotHumanReadable() { - super(); - } - - public PasswordNotHumanReadable(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unsupported option. - */ - public static class UnsupportedOption extends SOPGPException { - - public static final int EXIT_CODE = 37; - - public UnsupportedOption(String message) { - super(message); - } - - public UnsupportedOption(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Invalid data type (no secret key where KEYS expected, etc.). - */ - public static class BadData extends SOPGPException { - - public static final int EXIT_CODE = 41; - - public BadData(String message) { - super(message); - } - - public BadData(Throwable throwable) { - super(throwable); - } - - public BadData(String message, Throwable throwable) { - super(message, throwable); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Non-Text input where text expected. - */ - public static class ExpectedText extends SOPGPException { - - public static final int EXIT_CODE = 53; - - public ExpectedText() { - super(); - } - - public ExpectedText(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Output file already exists. - */ - public static class OutputExists extends SOPGPException { - - public static final int EXIT_CODE = 59; - - public OutputExists(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Input file does not exist. - */ - public static class MissingInput extends SOPGPException { - - public static final int EXIT_CODE = 61; - - public MissingInput(String message, Throwable cause) { - super(message, cause); - } - - public MissingInput(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * A KEYS input is protected (locked) with a password and sop failed to unlock it. - */ - public static class KeyIsProtected extends SOPGPException { - - public static final int EXIT_CODE = 67; - - public KeyIsProtected() { - super(); - } - - public KeyIsProtected(String message) { - super(message); - } - - public KeyIsProtected(String message, Throwable cause) { - super(message, cause); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Unsupported subcommand. - */ - public static class UnsupportedSubcommand extends SOPGPException { - - public static final int EXIT_CODE = 69; - - public UnsupportedSubcommand(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * An indirect parameter is a special designator (it starts with @), but sop does not know how to handle the prefix. - */ - public static class UnsupportedSpecialPrefix extends SOPGPException { - - public static final int EXIT_CODE = 71; - - public UnsupportedSpecialPrefix(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Exception that gets thrown if a special designator (starting with @) is given, but the filesystem contains - * a file matching the designator. - *

- * E.g.

@ENV:FOO
is given, but
./@ENV:FOO
exists on the filesystem. - */ - public static class AmbiguousInput extends SOPGPException { - - public static final int EXIT_CODE = 73; - - public AmbiguousInput(String message) { - super(message); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * Key not signature-capable (e.g. expired, revoked, unacceptable usage flags). - */ - public static class KeyCannotSign extends SOPGPException { - - public static final int EXIT_CODE = 79; - - public KeyCannotSign() { - super(); - } - - public KeyCannotSign(String message) { - super(message); - } - - public KeyCannotSign(String s, Throwable throwable) { - super(s, throwable); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * User provided incompatible options (e.g. "--as=clearsigned --no-armor"). - */ - public static class IncompatibleOptions extends SOPGPException { - - public static final int EXIT_CODE = 83; - - public IncompatibleOptions() { - super(); - } - - public IncompatibleOptions(String errorMsg) { - super(errorMsg); - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } - - /** - * The user provided a subcommand with an unsupported profile ("--profile=XYZ"), - * or the user tried to list profiles of a subcommand that does not support profiles at all. - */ - public static class UnsupportedProfile extends SOPGPException { - - public static final int EXIT_CODE = 89; - - private final String subcommand; - private final String profile; - - /** - * Create an exception signalling a subcommand that does not support any profiles. - * - * @param subcommand subcommand - */ - public UnsupportedProfile(String subcommand) { - super("Subcommand '" + subcommand + "' does not support any profiles."); - this.subcommand = subcommand; - this.profile = null; - } - - /** - * Create an exception signalling a subcommand does not support a specific profile. - * - * @param subcommand subcommand - * @param profile unsupported profile - */ - public UnsupportedProfile(String subcommand, String profile) { - super("Subcommand '" + subcommand + "' does not support profile '" + profile + "'."); - this.subcommand = subcommand; - this.profile = profile; - } - - /** - * Wrap an exception into another instance with a possibly translated error message. - * - * @param errorMsg error message - * @param e exception - */ - public UnsupportedProfile(String errorMsg, UnsupportedProfile e) { - super(errorMsg, e); - this.subcommand = e.getSubcommand(); - this.profile = e.getProfile(); - } - - /** - * Return the subcommand name. - * - * @return subcommand - */ - public String getSubcommand() { - return subcommand; - } - - /** - * Return the profile name. - * May return
null
. - * - * @return profile name - */ - public String getProfile() { - return profile; - } - - @Override - public int getExitCode() { - return EXIT_CODE; - } - } -} diff --git a/sop-java/src/main/java/sop/exception/package-info.java b/sop-java/src/main/java/sop/exception/package-info.java deleted file mode 100644 index 4abc562..0000000 --- a/sop-java/src/main/java/sop/exception/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - * Exception classes. - */ -package sop.exception; diff --git a/sop-java/src/main/java/sop/package-info.java b/sop-java/src/main/java/sop/package-info.java deleted file mode 100644 index 5ad4f52..0000000 --- a/sop-java/src/main/java/sop/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Stateless OpenPGP Interface for Java. - */ -package sop; diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt new file mode 100644 index 0000000..2473258 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -0,0 +1,308 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.exception + +abstract class SOPGPException : RuntimeException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor(message: String, cause: Throwable) : super(message, cause) + + abstract fun getExitCode(): Int + + /** No acceptable signatures found (sop verify, inline-verify). */ + class NoSignature : SOPGPException { + @JvmOverloads + constructor(message: String = "No verifiable signature found.") : super(message) + + constructor(errorMsg: String, e: NoSignature) : super(errorMsg, e) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 3 + } + } + + /** Asymmetric algorithm unsupported (sop encrypt, sign, inline-sign). */ + class UnsupportedAsymmetricAlgo(message: String, e: Throwable) : SOPGPException(message, e) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 13 + } + } + + /** Certificate not encryption capable (e,g, expired, revoked, unacceptable usage). */ + class CertCannotEncrypt : SOPGPException { + constructor(message: String, cause: Throwable) : super(message, cause) + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 17 + } + } + + /** Missing required argument. */ + class MissingArg : SOPGPException { + constructor() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 19 + } + } + + /** Incomplete verification instructions (sop decrypt). */ + class IncompleteVerification(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 23 + } + } + + /** Unable to decrypt (sop decrypt). */ + class CannotDecrypt : SOPGPException { + constructor() + + constructor(errorMsg: String, e: Throwable) : super(errorMsg, e) + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 29 + } + } + + /** Non-UTF-8 or otherwise unreliable password (sop encrypt). */ + class PasswordNotHumanReadable : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 31 + } + } + + /** Unsupported option. */ + class UnsupportedOption : SOPGPException { + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 37 + } + } + + /** Invalid data type (no secret key where KEYS expected, etc.). */ + class BadData : SOPGPException { + constructor(message: String) : super(message) + + constructor(throwable: Throwable) : super(throwable) + + constructor(message: String, throwable: Throwable) : super(message, throwable) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 41 + } + } + + /** Non-Text input where text expected. */ + class ExpectedText : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 53 + } + } + + /** Output file already exists. */ + class OutputExists(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 59 + } + } + + /** Input file does not exist. */ + class MissingInput : SOPGPException { + constructor(message: String, cause: Throwable) : super(message, cause) + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 61 + } + } + + /** A KEYS input is protected (locked) with a password and sop failed to unlock it. */ + class KeyIsProtected : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + constructor(message: String, cause: Throwable) : super(message, cause) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 67 + } + } + + /** Unsupported subcommand. */ + class UnsupportedSubcommand(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 69 + } + } + + /** + * An indirect parameter is a special designator (it starts with @), but sop does not know how + * to handle the prefix. + */ + class UnsupportedSpecialPrefix(errorMsg: String) : SOPGPException(errorMsg) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 71 + } + } + + /** + * Exception that gets thrown if a special designator (starting with @) is given, but the + * filesystem contains a file matching the designator. + * + * E.g.
@ENV:FOO
is given, but
./@ENV:FOO
exists on the filesystem. + */ + class AmbiguousInput(message: String) : SOPGPException(message) { + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 73 + } + } + + /** Key not signature-capable (e.g. expired, revoked, unacceptable usage flags). */ + class KeyCannotSign : SOPGPException { + constructor() : super() + + constructor(message: String) : super(message) + + constructor(s: String, throwable: Throwable) : super(s, throwable) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 79 + } + } + + /** User provided incompatible options (e.g. "--as=clearsigned --no-armor"). */ + class IncompatibleOptions : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 83 + } + } + + /** + * The user provided a subcommand with an unsupported profile ("--profile=XYZ"), or the user + * tried to list profiles of a subcommand that does not support profiles at all. + */ + class UnsupportedProfile : SOPGPException { + /** + * Return the subcommand name. + * + * @return subcommand + */ + val subcommand: String + + /** + * Return the profile name. May return `null`. + * + * @return profile name + */ + val profile: String? + + /** + * Create an exception signalling a subcommand that does not support any profiles. + * + * @param subcommand subcommand + */ + constructor( + subcommand: String + ) : super("Subcommand '$subcommand' does not support any profiles.") { + this.subcommand = subcommand + profile = null + } + + /** + * Create an exception signalling a subcommand does not support a specific profile. + * + * @param subcommand subcommand + * @param profile unsupported profile + */ + constructor( + subcommand: String, + profile: String + ) : super("Subcommand '$subcommand' does not support profile '$profile'.") { + this.subcommand = subcommand + this.profile = profile + } + + /** + * Wrap an exception into another instance with a possibly translated error message. + * + * @param errorMsg error message + * @param e exception + */ + constructor(errorMsg: String, e: UnsupportedProfile) : super(errorMsg, e) { + subcommand = e.subcommand + profile = e.profile + } + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 89 + } + } +} From 5ee9414410c76b02b20833c10a84f500dca582aa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 15:54:54 +0100 Subject: [PATCH 254/444] Encrypt: Add --session-key-out support --- .../main/java/sop/external/ExternalSOP.java | 2 +- .../external/operation/EncryptExternal.java | 62 +++++++++++++++++-- .../sop/cli/picocli/commands/EncryptCmd.java | 36 ++++++++++- .../cli/picocli/commands/EncryptCmdTest.java | 44 ++++++------- .../src/main/kotlin/sop/EncryptionResult.kt | 15 +++++ .../src/main/kotlin/sop/operation/Encrypt.kt | 13 ++-- .../operation/EncryptDecryptTest.java | 30 +++++++-- 7 files changed, 162 insertions(+), 40 deletions(-) create mode 100644 sop-java/src/main/kotlin/sop/EncryptionResult.kt diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index 376e54c..e1479ee 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -147,7 +147,7 @@ public class ExternalSOP implements SOP { @Override public Encrypt encrypt() { - return new EncryptExternal(binaryName, properties); + return new EncryptExternal(binaryName, properties, tempDirProvider); } @Override diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index bc40208..3eabfc5 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -4,14 +4,19 @@ package sop.external.operation; -import sop.Ready; +import sop.EncryptionResult; +import sop.ReadyWithResult; +import sop.SessionKey; import sop.enums.EncryptAs; import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Encrypt; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -21,6 +26,7 @@ import java.util.Properties; */ public class EncryptExternal implements Encrypt { + private final ExternalSOP.TempDirProvider tempDirProvider; private final List commandList = new ArrayList<>(); private final List envList; private int SIGN_WITH_COUNTER = 0; @@ -28,7 +34,8 @@ public class EncryptExternal implements Encrypt { private int PASSWORD_COUNTER = 0; private int CERT_COUNTER = 0; - public EncryptExternal(String binary, Properties environment) { + public EncryptExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { + this.tempDirProvider = tempDirProvider; this.commandList.add(binary); this.commandList.add("encrypt"); this.envList = ExternalSOP.propertiesToEnv(environment); @@ -92,8 +99,53 @@ public class EncryptExternal implements Encrypt { } @Override - public Ready plaintext(InputStream plaintext) - throws SOPGPException.KeyIsProtected { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, plaintext); + public ReadyWithResult plaintext(InputStream plaintext) + throws SOPGPException.KeyIsProtected, IOException { + File tempDir = tempDirProvider.provideTempDirectory(); + + File sessionKeyOut = new File(tempDir, "session-key-out"); + sessionKeyOut.delete(); + commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); + + String[] command = commandList.toArray(new String[0]); + String[] env = envList.toArray(new String[0]); + try { + Process process = Runtime.getRuntime().exec(command, env); + OutputStream processOut = process.getOutputStream(); + InputStream processIn = process.getInputStream(); + + return new ReadyWithResult() { + @Override + public EncryptionResult writeTo(OutputStream outputStream) throws IOException { + byte[] buf = new byte[4096]; + int r; + while ((r = plaintext.read(buf)) > 0) { + processOut.write(buf, 0, r); + } + + plaintext.close(); + processOut.close(); + + while ((r = processIn.read(buf)) > 0) { + outputStream.write(buf, 0 , r); + } + + processIn.close(); + outputStream.close(); + + ExternalSOP.finish(process); + + FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); + String line = ExternalSOP.readString(sessionKeyOutIn); + SessionKey sessionKey = SessionKey.fromString(line.trim()); + sessionKeyOutIn.close(); + sessionKeyOut.delete(); + + return new EncryptionResult(sessionKey); + } + }; + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index efda26f..0d37416 100644 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java @@ -5,7 +5,9 @@ package sop.cli.picocli.commands; import picocli.CommandLine; -import sop.Ready; +import sop.EncryptionResult; +import sop.ReadyWithResult; +import sop.SessionKey; import sop.cli.picocli.SopCLI; import sop.enums.EncryptAs; import sop.exception.SOPGPException; @@ -13,6 +15,8 @@ import sop.operation.Encrypt; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -49,11 +53,18 @@ public class EncryptCmd extends AbstractSopCmd { paramLabel = "CERTS") List certs = new ArrayList<>(); + @CommandLine.Option( + names = {"--session-key-out"}, + paramLabel = "SESSIONKEY") + String sessionKeyOut; + @Override public void run() { Encrypt encrypt = throwIfUnsupportedSubcommand( SopCLI.getSop().encrypt(), "encrypt"); + throwIfOutputExists(sessionKeyOut); + if (profile != null) { try { encrypt.profile(profile); @@ -145,8 +156,27 @@ public class EncryptCmd extends AbstractSopCmd { } try { - Ready ready = encrypt.plaintext(System.in); - ready.writeTo(System.out); + ReadyWithResult ready = encrypt.plaintext(System.in); + EncryptionResult result = ready.writeTo(System.out); + + if (sessionKeyOut == null) { + return; + } + try (OutputStream outputStream = getOutput(sessionKeyOut)) { + if (!result.getSessionKey().isPresent()) { + String errorMsg = getMsg("sop.error.runtime.no_session_key_extracted"); + throw new SOPGPException.UnsupportedOption(String.format(errorMsg, "--session-key-out")); + } + SessionKey sessionKey = result.getSessionKey().get(); + if (sessionKey == null) { + return; + } + PrintWriter writer = new PrintWriter(outputStream); + // CHECKSTYLE:OFF + writer.println(sessionKey); + // CHECKSTYLE:ON + writer.flush(); + } } catch (IOException e) { throw new RuntimeException(e); } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java index 73ec9cb..09346af 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java @@ -4,6 +4,24 @@ package sop.cli.picocli.commands; +import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sop.EncryptionResult; +import sop.ReadyWithResult; +import sop.SOP; +import sop.cli.picocli.SopCLI; +import sop.cli.picocli.TestFileUtil; +import sop.enums.EncryptAs; +import sop.exception.SOPGPException; +import sop.operation.Encrypt; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -11,22 +29,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import sop.Ready; -import sop.SOP; -import sop.cli.picocli.SopCLI; -import sop.cli.picocli.TestFileUtil; -import sop.enums.EncryptAs; -import sop.exception.SOPGPException; -import sop.operation.Encrypt; - public class EncryptCmdTest { Encrypt encrypt; @@ -34,10 +36,10 @@ public class EncryptCmdTest { @BeforeEach public void mockComponents() throws IOException { encrypt = mock(Encrypt.class); - when(encrypt.plaintext((InputStream) any())).thenReturn(new Ready() { + when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult() { @Override - public void writeTo(OutputStream outputStream) { - + public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { + return new EncryptionResult(null); } }); @@ -190,9 +192,9 @@ public class EncryptCmdTest { @Test @ExpectSystemExitWithStatus(1) public void writeTo_ioExceptionCausesExit1() throws IOException { - when(encrypt.plaintext((InputStream) any())).thenReturn(new Ready() { + when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult() { @Override - public void writeTo(OutputStream outputStream) throws IOException { + public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { throw new IOException(); } }); diff --git a/sop-java/src/main/kotlin/sop/EncryptionResult.kt b/sop-java/src/main/kotlin/sop/EncryptionResult.kt new file mode 100644 index 0000000..1284f89 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/EncryptionResult.kt @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.util.Optional + +class EncryptionResult(sessionKey: SessionKey?) { + val sessionKey: Optional + + init { + this.sessionKey = Optional.ofNullable(sessionKey) + } +} diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt index 0daebee..71c04cb 100644 --- a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -6,8 +6,9 @@ package sop.operation import java.io.IOException import java.io.InputStream +import sop.EncryptionResult import sop.Profile -import sop.Ready +import sop.ReadyWithResult import sop.enums.EncryptAs import sop.exception.SOPGPException.* import sop.util.UTF8Util @@ -146,20 +147,22 @@ interface Encrypt { * Encrypt the given data yielding the ciphertext. * * @param plaintext plaintext - * @return input stream containing the ciphertext + * @return result and ciphertext * @throws IOException in case of an IO error * @throws KeyIsProtected if at least one signing key cannot be unlocked */ - @Throws(IOException::class, KeyIsProtected::class) fun plaintext(plaintext: InputStream): Ready + @Throws(IOException::class, KeyIsProtected::class) + fun plaintext(plaintext: InputStream): ReadyWithResult /** * Encrypt the given data yielding the ciphertext. * * @param plaintext plaintext - * @return input stream containing the ciphertext + * @return result and ciphertext * @throws IOException in case of an IO error * @throws KeyIsProtected if at least one signing key cannot be unlocked */ @Throws(IOException::class, KeyIsProtected::class) - fun plaintext(plaintext: ByteArray): Ready = plaintext(plaintext.inputStream()) + fun plaintext(plaintext: ByteArray): ReadyWithResult = + plaintext(plaintext.inputStream()) } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 9138f64..51c117a 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -10,13 +10,16 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; +import sop.EncryptionResult; import sop.SOP; +import sop.SessionKey; import sop.Verification; import sop.enums.EncryptAs; import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.testsuite.TestData; import sop.testsuite.assertions.VerificationListAssert; +import sop.util.Optional; import sop.util.UTCUtil; import java.io.IOException; @@ -27,6 +30,7 @@ import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -41,18 +45,26 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + ByteArrayAndResult encResult = sop.encrypt() .withPassword("sw0rdf1sh") .plaintext(message) - .getBytes(); + .toByteArrayAndResult(); - byte[] plaintext = sop.decrypt() + byte[] ciphertext = encResult.getBytes(); + Optional encSessionKey = encResult.getResult().getSessionKey(); + + ByteArrayAndResult decResult = sop.decrypt() .withPassword("sw0rdf1sh") .ciphertext(ciphertext) - .toByteArrayAndResult() - .getBytes(); + .toByteArrayAndResult(); + + byte[] plaintext = decResult.getBytes(); + Optional decSessionKey = decResult.getResult().getSessionKey(); assertArrayEquals(message, plaintext); + if (encSessionKey.isPresent() && decSessionKey.isPresent()) { + assertEquals(encSessionKey.get(), decSessionKey.get()); + } } @ParameterizedTest @@ -62,6 +74,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) + .toByteArrayAndResult() .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() @@ -83,6 +96,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) + .toByteArrayAndResult() .getBytes(); byte[] plaintext = sop.decrypt() @@ -101,6 +115,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = sop.encrypt() .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) + .toByteArrayAndResult() .getBytes(); byte[] plaintext = sop.decrypt() @@ -120,6 +135,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) + .toByteArrayAndResult() .getBytes(); byte[] armored = sop.armor() @@ -144,6 +160,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.binary) .plaintext(message) + .toByteArrayAndResult() .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() @@ -175,6 +192,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.text) .plaintext(message) + .toByteArrayAndResult() .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() @@ -215,6 +233,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .signWith(key) .withKeyPassword(keyPassword) .plaintext(message) + .toByteArrayAndResult() .getBytes(); ByteArrayAndResult bytesAndResult = sop.decrypt() @@ -305,6 +324,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() .plaintext(message) + .toByteArrayAndResult() .getBytes()); } } From 86b173bf1c4b5daa4b58abf2390597b7eee0d73b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 31 Oct 2023 16:16:32 +0100 Subject: [PATCH 255/444] Update Changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca12a2..d736ded 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 8.0.0-SNAPSHOT +- Rewrote API in Kotlin +- Update implementation to [SOP Specification revision 08](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html). + - Add `--no-armor` option to `revoke-key` and `change-key-password` subcommands + - `armor`: Deprecate `--label` option + - `encrypt`: Add `--session-key-out` option +- Slight API changes: + - `sop.encrypt().plaintext()` now returns a `ReadyWithResult` instead of `Ready`. + - `EncryptionResult` is a new result type, that provides access to the session key of an encrypted message + - Change `ArmorLabel` values into lowercase + - Change `EncryptAs` values into lowercase + - Change `SignAs` values into lowercase + ## 7.0.0 - Update implementation to [SOP Specification revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html). - Add support for new `revoke-key` subcommand From 1de179c0156c7bad489f05bd4c0a39cf06f96286 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 16:07:37 +0100 Subject: [PATCH 256/444] Kotlin conversion: VersionCmd --- .../sop/cli/picocli/commands/VersionCmd.java | 58 ------------------- .../sop/cli/picocli/commands/VersionCmd.kt | 51 ++++++++++++++++ 2 files changed, 51 insertions(+), 58 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java deleted file mode 100644 index 6ccb8f7..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.cli.picocli.Print; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.Version; - -@CommandLine.Command(name = "version", resourceBundle = "msg_version", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class VersionCmd extends AbstractSopCmd { - - @CommandLine.ArgGroup() - Exclusive exclusive; - - static class Exclusive { - @CommandLine.Option(names = "--extended") - boolean extended; - - @CommandLine.Option(names = "--backend") - boolean backend; - - @CommandLine.Option(names = "--sop-spec") - boolean sopSpec; - } - - - - @Override - public void run() { - Version version = throwIfUnsupportedSubcommand( - SopCLI.getSop().version(), "version"); - - if (exclusive == null) { - Print.outln(version.getName() + " " + version.getVersion()); - return; - } - - if (exclusive.extended) { - Print.outln(version.getExtendedVersion()); - return; - } - - if (exclusive.backend) { - Print.outln(version.getBackendVersion()); - return; - } - - if (exclusive.sopSpec) { - Print.outln(version.getSopSpecVersion()); - return; - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt new file mode 100644 index 0000000..75197fe --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine.ArgGroup +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException + +@Command( + name = "version", + resourceBundle = "msg_version", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class VersionCmd : AbstractSopCmd() { + + @ArgGroup var exclusive: Exclusive? = null + + class Exclusive { + @Option(names = ["--extended"]) var extended: Boolean = false + @Option(names = ["--backend"]) var backend: Boolean = false + @Option(names = ["--sop-spec"]) var sopSpec: Boolean = false + } + + override fun run() { + val version = throwIfUnsupportedSubcommand(SopCLI.getSop().version(), "version") + + if (exclusive == null) { + // No option provided + println("${version.getName()} ${version.getVersion()}") + return + } + + if (exclusive!!.extended) { + println(version.getExtendedVersion()) + return + } + + if (exclusive!!.backend) { + println(version.getBackendVersion()) + return + } + + if (exclusive!!.sopSpec) { + println(version.getSopSpecVersion()) + return + } + } +} From 8246359a854ec3d2fe715ccf683e908afb981c68 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 16:15:35 +0100 Subject: [PATCH 257/444] Kotlin conversion: VerifyCmd --- .../sop/cli/picocli/commands/VerifyCmd.java | 102 ------------------ .../sop/cli/picocli/commands/VerifyCmd.kt | 81 ++++++++++++++ 2 files changed, 81 insertions(+), 102 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VerifyCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java deleted file mode 100644 index d76bb37..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VerifyCmd.java +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Verification; -import sop.cli.picocli.Print; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.DetachedVerify; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "verify", - resourceBundle = "msg_detached-verify", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class VerifyCmd extends AbstractSopCmd { - - @CommandLine.Parameters(index = "0", - paramLabel = "SIGNATURE") - String signature; - - @CommandLine.Parameters(index = "1..*", - arity = "1..*", - paramLabel = "CERT") - List certificates = new ArrayList<>(); - - @CommandLine.Option(names = {"--not-before"}, - paramLabel = "DATE") - String notBefore = "-"; - - @CommandLine.Option(names = {"--not-after"}, - paramLabel = "DATE") - String notAfter = "now"; - - @Override - public void run() { - DetachedVerify detachedVerify = throwIfUnsupportedSubcommand( - SopCLI.getSop().detachedVerify(), "verify"); - - if (notAfter != null) { - try { - detachedVerify.notAfter(parseNotAfter(notAfter)); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - if (notBefore != null) { - try { - detachedVerify.notBefore(parseNotBefore(notBefore)); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - for (String certInput : certificates) { - try (InputStream certIn = getInput(certInput)) { - detachedVerify.cert(certIn); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - if (signature != null) { - try (InputStream sigIn = getInput(signature)) { - detachedVerify.signatures(sigIn); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_signature", signature); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - List verifications; - try { - verifications = detachedVerify.data(System.in); - } catch (SOPGPException.NoSignature e) { - String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); - throw new SOPGPException.NoSignature(errorMsg, e); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); - throw new SOPGPException.BadData(errorMsg, badData); - } - - for (Verification verification : verifications) { - Print.outln(verification.toString()); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VerifyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VerifyCmd.kt new file mode 100644 index 0000000..ef27266 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VerifyCmd.kt @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.* + +@Command( + name = "verify", + resourceBundle = "msg_detached-verify", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class VerifyCmd : AbstractSopCmd() { + + @Parameters(index = "0", paramLabel = "SIGNATURE") lateinit var signature: String + + @Parameters(index = "1..*", arity = "1..*", paramLabel = "CERT") + lateinit var certificates: List + + @Option(names = ["--not-before"], paramLabel = "DATE") var notBefore: String = "-" + + @Option(names = ["--not-after"], paramLabel = "DATE") var notAfter: String = "now" + + override fun run() { + val detachedVerify = + throwIfUnsupportedSubcommand(SopCLI.getSop().detachedVerify(), "verify") + try { + detachedVerify.notAfter(parseNotAfter(notAfter)) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after") + throw UnsupportedOption(errorMsg, unsupportedOption) + } + + try { + detachedVerify.notBefore(parseNotBefore(notBefore)) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before") + throw UnsupportedOption(errorMsg, unsupportedOption) + } + + for (certInput in certificates) { + try { + getInput(certInput).use { certIn -> detachedVerify.cert(certIn) } + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", certInput) + throw BadData(errorMsg, badData) + } + } + + try { + getInput(signature).use { sigIn -> detachedVerify.signatures(sigIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_signature", signature) + throw BadData(errorMsg, badData) + } + + val verifications = + try { + detachedVerify.data(System.`in`) + } catch (e: NoSignature) { + val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found") + throw NoSignature(errorMsg, e) + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_a_message") + throw BadData(errorMsg, badData) + } + + for (verification in verifications) { + println(verification.toString()) + } + } +} From 256d1c596067fa91ff0d542981a2ca8f9141deae Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 16:34:35 +0100 Subject: [PATCH 258/444] Kotlin conversion: SignCmd --- .../sop/cli/picocli/commands/SignCmd.java | 108 ------------------ .../sop/cli/picocli/commands/SignCmd.kt | 90 +++++++++++++++ 2 files changed, 90 insertions(+), 108 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/SignCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java deleted file mode 100644 index cad9d6e..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/SignCmd.java +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.MicAlg; -import sop.ReadyWithResult; -import sop.SigningResult; -import sop.cli.picocli.SopCLI; -import sop.enums.SignAs; -import sop.exception.SOPGPException; -import sop.operation.DetachedSign; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "sign", - resourceBundle = "msg_detached-sign", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class SignCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Option(names = "--as", - paramLabel = "{binary|text}") - SignAs type; - - @CommandLine.Parameters(paramLabel = "KEYS") - List secretKeyFile = new ArrayList<>(); - - @CommandLine.Option(names = "--with-key-password", - paramLabel = "PASSWORD") - List withKeyPassword = new ArrayList<>(); - - @CommandLine.Option(names = "--micalg-out", - paramLabel = "MICALG") - String micAlgOut; - - @Override - public void run() { - DetachedSign detachedSign = throwIfUnsupportedSubcommand( - SopCLI.getSop().detachedSign(), "sign"); - - throwIfOutputExists(micAlgOut); - throwIfEmptyParameters(secretKeyFile, "KEYS"); - - if (type != null) { - try { - detachedSign.mode(type); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - for (String passwordFile : withKeyPassword) { - try { - String password = stringFromInputStream(getInput(passwordFile)); - detachedSign.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (String keyInput : secretKeyFile) { - try (InputStream keyIn = getInput(keyInput)) { - detachedSign.key(keyIn); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.KeyIsProtected keyIsProtected) { - String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput); - throw new SOPGPException.KeyIsProtected(errorMsg, keyIsProtected); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - if (!armor) { - detachedSign.noArmor(); - } - - try { - ReadyWithResult ready = detachedSign.data(System.in); - SigningResult result = ready.writeTo(System.out); - - MicAlg micAlg = result.getMicAlg(); - if (micAlgOut != null) { - // Write micalg out - OutputStream outputStream = getOutput(micAlgOut); - micAlg.writeTo(outputStream); - outputStream.close(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/SignCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/SignCmd.kt new file mode 100644 index 0000000..6860477 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/SignCmd.kt @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.enums.SignAs +import sop.exception.SOPGPException +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.KeyIsProtected + +@Command( + name = "sign", + resourceBundle = "msg_detached-sign", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class SignCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor: Boolean = true + + @Option(names = ["--as"], paramLabel = "{binary|text}") var type: SignAs? = null + + @Parameters(paramLabel = "KEYS") var secretKeyFile: List = listOf() + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--micalg-out"], paramLabel = "MICALG") var micAlgOut: String? = null + + override fun run() { + val detachedSign = throwIfUnsupportedSubcommand(SopCLI.getSop().detachedSign(), "sign") + + throwIfOutputExists(micAlgOut) + throwIfEmptyParameters(secretKeyFile, "KEYS") + + try { + type?.let { detachedSign.mode(it) } + } catch (unsupported: SOPGPException.UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw SOPGPException.UnsupportedOption(errorMsg, unsupported) + } catch (ioe: IOException) { + throw RuntimeException(ioe) + } + + withKeyPassword.forEach { passIn -> + try { + val password = stringFromInputStream(getInput(passIn)) + detachedSign.withKeyPassword(password) + } catch (unsupported: SOPGPException.UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw SOPGPException.UnsupportedOption(errorMsg, unsupported) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + secretKeyFile.forEach { keyIn -> + try { + getInput(keyIn).use { input -> detachedSign.key(input) } + } catch (ioe: IOException) { + throw RuntimeException(ioe) + } catch (keyIsProtected: KeyIsProtected) { + val errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyIn) + throw KeyIsProtected(errorMsg, keyIsProtected) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", keyIn) + throw BadData(errorMsg, badData) + } + } + + if (!armor) { + detachedSign.noArmor() + } + + try { + val ready = detachedSign.data(System.`in`) + val result = ready.writeTo(System.out) + + if (micAlgOut != null) { + getOutput(micAlgOut).use { result.micAlg.writeTo(it) } + } + } catch (e: IOException) { + throw java.lang.RuntimeException(e) + } + } +} From 666d51384b442ffa8621299b7874a1fabba6c020 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:20:00 +0100 Subject: [PATCH 259/444] Kotlin conversion: AbstractSopCmd --- .../cli/picocli/commands/AbstractSopCmd.java | 282 ------------------ .../cli/picocli/commands/AbstractSopCmd.kt | 248 +++++++++++++++ .../picocli/commands/AbstractSopCmdTest.java | 2 +- 3 files changed, 249 insertions(+), 283 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java deleted file mode 100644 index 9aec5a7..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java +++ /dev/null @@ -1,282 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import sop.exception.SOPGPException; -import sop.util.UTCUtil; -import sop.util.UTF8Util; - -import javax.annotation.Nonnull; -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.text.ParseException; -import java.util.Collection; -import java.util.Date; -import java.util.Locale; -import java.util.ResourceBundle; -import java.util.regex.Pattern; - -/** - * Abstract super class of SOP subcommands. - */ -public abstract class AbstractSopCmd implements Runnable { - - /** - * Interface to modularize resolving of environment variables. - */ - public interface EnvironmentVariableResolver { - /** - * Resolve the value of the given environment variable. - * Return null if the variable is not present. - * - * @param name name of the variable - * @return variable value or null - */ - String resolveEnvironmentVariable(String name); - } - - public static final String PRFX_ENV = "@ENV:"; - public static final String PRFX_FD = "@FD:"; - public static final Date BEGINNING_OF_TIME = new Date(0); - public static final Date END_OF_TIME = new Date(8640000000000000L); - - public static final Pattern PATTERN_FD = Pattern.compile("^\\d{1,20}$"); - - protected final ResourceBundle messages; - protected EnvironmentVariableResolver envResolver = System::getenv; - - public AbstractSopCmd() { - this(Locale.getDefault()); - } - - public AbstractSopCmd(@Nonnull Locale locale) { - messages = ResourceBundle.getBundle("msg_sop", locale); - } - - void throwIfOutputExists(String output) { - if (output == null) { - return; - } - - File outputFile = new File(output); - if (outputFile.exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.output_file_already_exists", outputFile.getAbsolutePath()); - throw new SOPGPException.OutputExists(errorMsg); - } - } - - public String getMsg(String key) { - return messages.getString(key); - } - - public String getMsg(String key, String arg1) { - return String.format(messages.getString(key), arg1); - } - - public String getMsg(String key, String arg1, String arg2) { - return String.format(messages.getString(key), arg1, arg2); - } - - void throwIfMissingArg(Object arg, String argName) { - if (arg == null) { - String errorMsg = getMsg("sop.error.usage.argument_required", argName); - throw new SOPGPException.MissingArg(errorMsg); - } - } - - void throwIfEmptyParameters(Collection arg, String parmName) { - if (arg.isEmpty()) { - String errorMsg = getMsg("sop.error.usage.parameter_required", parmName); - throw new SOPGPException.MissingArg(errorMsg); - } - } - - T throwIfUnsupportedSubcommand(T subcommand, String subcommandName) { - if (subcommand == null) { - String errorMsg = getMsg("sop.error.feature_support.subcommand_not_supported", subcommandName); - throw new SOPGPException.UnsupportedSubcommand(errorMsg); - } - return subcommand; - } - - void setEnvironmentVariableResolver(EnvironmentVariableResolver envResolver) { - if (envResolver == null) { - throw new NullPointerException("Variable envResolver cannot be null."); - } - this.envResolver = envResolver; - } - - public InputStream getInput(String indirectInput) throws IOException { - if (indirectInput == null) { - throw new IllegalArgumentException("Input cannot not be null."); - } - - String trimmed = indirectInput.trim(); - if (trimmed.isEmpty()) { - throw new IllegalArgumentException("Input cannot be blank."); - } - - if (trimmed.startsWith(PRFX_ENV)) { - if (new File(trimmed).exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.ambiguous_filename", trimmed); - throw new SOPGPException.AmbiguousInput(errorMsg); - } - - String envName = trimmed.substring(PRFX_ENV.length()); - String envValue = envResolver.resolveEnvironmentVariable(envName); - if (envValue == null) { - String errorMsg = getMsg("sop.error.indirect_data_type.environment_variable_not_set", envName); - throw new IllegalArgumentException(errorMsg); - } - - if (envValue.trim().isEmpty()) { - String errorMsg = getMsg("sop.error.indirect_data_type.environment_variable_empty", envName); - throw new IllegalArgumentException(errorMsg); - } - - return new ByteArrayInputStream(envValue.getBytes("UTF8")); - - } else if (trimmed.startsWith(PRFX_FD)) { - - if (new File(trimmed).exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.ambiguous_filename", trimmed); - throw new SOPGPException.AmbiguousInput(errorMsg); - } - - File fdFile = fileDescriptorFromString(trimmed); - try { - FileInputStream fileIn = new FileInputStream(fdFile); - return fileIn; - } catch (FileNotFoundException e) { - String errorMsg = getMsg("sop.error.indirect_data_type.file_descriptor_not_found", fdFile.getAbsolutePath()); - throw new IOException(errorMsg, e); - } - } else { - File file = new File(trimmed); - if (!file.exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.input_file_does_not_exist", file.getAbsolutePath()); - throw new SOPGPException.MissingInput(errorMsg); - } - - if (!file.isFile()) { - String errorMsg = getMsg("sop.error.indirect_data_type.input_not_a_file", file.getAbsolutePath()); - throw new SOPGPException.MissingInput(errorMsg); - } - - return new FileInputStream(file); - } - } - - public OutputStream getOutput(String indirectOutput) throws IOException { - if (indirectOutput == null) { - throw new IllegalArgumentException("Output cannot be null."); - } - - String trimmed = indirectOutput.trim(); - if (trimmed.isEmpty()) { - throw new IllegalArgumentException("Output cannot be blank."); - } - - // @ENV not allowed for output - if (trimmed.startsWith(PRFX_ENV)) { - String errorMsg = getMsg("sop.error.indirect_data_type.illegal_use_of_env_designator"); - throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); - } - - // File Descriptor - if (trimmed.startsWith(PRFX_FD)) { - File fdFile = fileDescriptorFromString(trimmed); - try { - FileOutputStream fout = new FileOutputStream(fdFile); - return fout; - } catch (FileNotFoundException e) { - String errorMsg = getMsg("sop.error.indirect_data_type.file_descriptor_not_found", fdFile.getAbsolutePath()); - throw new IOException(errorMsg, e); - } - } - - File file = new File(trimmed); - if (file.exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.output_file_already_exists", file.getAbsolutePath()); - throw new SOPGPException.OutputExists(errorMsg); - } - - if (!file.createNewFile()) { - String errorMsg = getMsg("sop.error.indirect_data_type.output_file_cannot_be_created", file.getAbsolutePath()); - throw new IOException(errorMsg); - } - - return new FileOutputStream(file); - } - - public File fileDescriptorFromString(String fdString) { - File fdDir = new File("/dev/fd/"); - if (!fdDir.exists()) { - String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); - throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); - } - String fdNumber = fdString.substring(PRFX_FD.length()); - if (!PATTERN_FD.matcher(fdNumber).matches()) { - throw new IllegalArgumentException("File descriptor must be a positive number."); - } - File descriptor = new File(fdDir, fdNumber); - return descriptor; - } - - public static String stringFromInputStream(InputStream inputStream) throws IOException { - try { - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - byte[] buf = new byte[4096]; int read; - while ((read = inputStream.read(buf)) != -1) { - byteOut.write(buf, 0, read); - } - // TODO: For decrypt operations we MUST accept non-UTF8 passwords - return UTF8Util.decodeUTF8(byteOut.toByteArray()); - } finally { - inputStream.close(); - } - } - - public Date parseNotAfter(String notAfter) { - if (notAfter.equals("now")) { - return new Date(); - } - - if (notAfter.equals("-")) { - return END_OF_TIME; - } - - try { - return UTCUtil.parseUTCDate(notAfter); - } catch (ParseException e) { - String errorMsg = getMsg("sop.error.input.malformed_not_after"); - throw new IllegalArgumentException(errorMsg); - } - } - - public Date parseNotBefore(String notBefore) { - if (notBefore.equals("now")) { - return new Date(); - } - - if (notBefore.equals("-")) { - return BEGINNING_OF_TIME; - } - - try { - return UTCUtil.parseUTCDate(notBefore); - } catch (ParseException e) { - String errorMsg = getMsg("sop.error.input.malformed_not_before"); - throw new IllegalArgumentException(errorMsg); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt new file mode 100644 index 0000000..4629e57 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.* +import java.text.ParseException +import java.util.* +import sop.cli.picocli.commands.AbstractSopCmd.EnvironmentVariableResolver +import sop.exception.SOPGPException.* +import sop.util.UTCUtil.Companion.parseUTCDate +import sop.util.UTF8Util.Companion.decodeUTF8 + +/** Abstract super class of SOP subcommands. */ +abstract class AbstractSopCmd(locale: Locale = Locale.getDefault()) : Runnable { + + private val messages: ResourceBundle = ResourceBundle.getBundle("msg_sop", locale) + var environmentVariableResolver = EnvironmentVariableResolver { name: String -> + System.getenv(name) + } + + /** Interface to modularize resolving of environment variables. */ + fun interface EnvironmentVariableResolver { + + /** + * Resolve the value of the given environment variable. Return null if the variable is not + * present. + * + * @param name name of the variable + * @return variable value or null + */ + fun resolveEnvironmentVariable(name: String): String? + } + + fun throwIfOutputExists(output: String?) { + output + ?.let { File(it) } + ?.let { + if (it.exists()) { + val errorMsg: String = + getMsg( + "sop.error.indirect_data_type.output_file_already_exists", + it.absolutePath) + throw OutputExists(errorMsg) + } + } + } + + fun getMsg(key: String): String = messages.getString(key) + + fun getMsg(key: String, vararg args: String): String { + val msg = messages.getString(key) + return String.format(msg, *args) + } + + fun throwIfMissingArg(arg: Any?, argName: String) { + if (arg == null) { + val errorMsg = getMsg("sop.error.usage.argument_required", argName) + throw MissingArg(errorMsg) + } + } + + fun throwIfEmptyParameters(arg: Collection<*>, parmName: String) { + if (arg.isEmpty()) { + val errorMsg = getMsg("sop.error.usage.parameter_required", parmName) + throw MissingArg(errorMsg) + } + } + + fun throwIfUnsupportedSubcommand(subcommand: T?, subcommandName: String): T { + if (subcommand == null) { + val errorMsg = + getMsg("sop.error.feature_support.subcommand_not_supported", subcommandName) + throw UnsupportedSubcommand(errorMsg) + } + return subcommand + } + + @Throws(IOException::class) + fun getInput(indirectInput: String): InputStream { + val trimmed = indirectInput.trim() + require(trimmed.isNotBlank()) { "Input cannot be blank." } + + if (trimmed.startsWith(PRFX_ENV)) { + if (File(trimmed).exists()) { + val errorMsg = getMsg("sop.error.indirect_data_type.ambiguous_filename", trimmed) + throw AmbiguousInput(errorMsg) + } + + val envName = trimmed.substring(PRFX_ENV.length) + val envValue = environmentVariableResolver.resolveEnvironmentVariable(envName) + requireNotNull(envValue) { + getMsg("sop.error.indirect_data_type.environment_variable_not_set", envName) + } + + require(envValue.trim().isNotEmpty()) { + getMsg("sop.error.indirect_data_type.environment_variable_empty", envName) + } + + return envValue.byteInputStream() + } else if (trimmed.startsWith(PRFX_FD)) { + + if (File(trimmed).exists()) { + val errorMsg = getMsg("sop.error.indirect_data_type.ambiguous_filename", trimmed) + throw AmbiguousInput(errorMsg) + } + + val fdFile: File = fileDescriptorFromString(trimmed) + return try { + fdFile.inputStream() + } catch (e: FileNotFoundException) { + val errorMsg = + getMsg( + "sop.error.indirect_data_type.file_descriptor_not_found", + fdFile.absolutePath) + throw IOException(errorMsg, e) + } + } else { + + val file = File(trimmed) + if (!file.exists()) { + val errorMsg = + getMsg( + "sop.error.indirect_data_type.input_file_does_not_exist", file.absolutePath) + throw MissingInput(errorMsg) + } + if (!file.isFile()) { + val errorMsg = + getMsg("sop.error.indirect_data_type.input_not_a_file", file.absolutePath) + throw MissingInput(errorMsg) + } + return file.inputStream() + } + } + + @Throws(IOException::class) + fun getOutput(indirectOutput: String?): OutputStream { + requireNotNull(indirectOutput) { "Output cannot be null." } + val trimmed = indirectOutput.trim() + require(trimmed.isNotEmpty()) { "Output cannot be blank." } + + // @ENV not allowed for output + if (trimmed.startsWith(PRFX_ENV)) { + val errorMsg = getMsg("sop.error.indirect_data_type.illegal_use_of_env_designator") + throw UnsupportedSpecialPrefix(errorMsg) + } + + // File Descriptor + if (trimmed.startsWith(PRFX_FD)) { + val fdFile = fileDescriptorFromString(trimmed) + return try { + fdFile.outputStream() + } catch (e: FileNotFoundException) { + val errorMsg = + getMsg( + "sop.error.indirect_data_type.file_descriptor_not_found", + fdFile.absolutePath) + throw IOException(errorMsg, e) + } + } + val file = File(trimmed) + if (file.exists()) { + val errorMsg = + getMsg("sop.error.indirect_data_type.output_file_already_exists", file.absolutePath) + throw OutputExists(errorMsg) + } + if (!file.createNewFile()) { + val errorMsg = + getMsg( + "sop.error.indirect_data_type.output_file_cannot_be_created", file.absolutePath) + throw IOException(errorMsg) + } + return file.outputStream() + } + + fun fileDescriptorFromString(fdString: String): File { + val fdDir = File("/dev/fd/") + if (!fdDir.exists()) { + val errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported") + throw UnsupportedSpecialPrefix(errorMsg) + } + val fdNumber = fdString.substring(PRFX_FD.length) + require(PATTERN_FD.matcher(fdNumber).matches()) { + "File descriptor must be a positive number." + } + return File(fdDir, fdNumber) + } + + fun parseNotAfter(notAfter: String): Date { + return when (notAfter) { + "now" -> Date() + "-" -> END_OF_TIME + else -> + try { + parseUTCDate(notAfter) + } catch (e: ParseException) { + val errorMsg = getMsg("sop.error.input.malformed_not_after") + throw IllegalArgumentException(errorMsg) + } + } + } + + fun parseNotBefore(notBefore: String): Date { + return when (notBefore) { + "now" -> Date() + "-" -> DAWN_OF_TIME + else -> + try { + parseUTCDate(notBefore) + } catch (e: ParseException) { + val errorMsg = getMsg("sop.error.input.malformed_not_before") + throw IllegalArgumentException(errorMsg) + } + } + } + + companion object { + const val PRFX_ENV = "@ENV:" + + const val PRFX_FD = "@FD:" + + @JvmField val DAWN_OF_TIME = Date(0) + + @JvmField + @Deprecated("Replace with DAWN_OF_TIME", ReplaceWith("DAWN_OF_TIME")) + val BEGINNING_OF_TIME = DAWN_OF_TIME + + @JvmField val END_OF_TIME = Date(8640000000000000L) + + @JvmField val PATTERN_FD = "^\\d{1,20}$".toPattern() + + @Throws(IOException::class) + @JvmStatic + fun stringFromInputStream(inputStream: InputStream): String { + return inputStream.use { input -> + val byteOut = ByteArrayOutputStream() + val buf = ByteArray(4096) + var read: Int + while (input.read(buf).also { read = it } != -1) { + byteOut.write(buf, 0, read) + } + // TODO: For decrypt operations we MUST accept non-UTF8 passwords + decodeUTF8(byteOut.toByteArray()) + } + } + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java index aed420b..396bc7f 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/AbstractSopCmdTest.java @@ -36,7 +36,7 @@ public class AbstractSopCmdTest { @Test public void getInput_NullInvalid() { - assertThrows(IllegalArgumentException.class, () -> abstractCmd.getInput(null)); + assertThrows(NullPointerException.class, () -> abstractCmd.getInput(null)); } @Test From 18865feaff9192ac63d6f6a44b1ec6f3648621ce Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:26:38 +0100 Subject: [PATCH 260/444] Kotlin conversion: RevokeKeyCmd --- .../cli/picocli/commands/RevokeKeyCmd.java | 62 ------------------- .../sop/cli/picocli/commands/RevokeKeyCmd.kt | 58 +++++++++++++++++ 2 files changed, 58 insertions(+), 62 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java deleted file mode 100644 index 45f22fa..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/RevokeKeyCmd.java +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.RevokeKey; - -import java.io.IOException; - -@CommandLine.Command(name = "revoke-key", - resourceBundle = "msg_revoke-key", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class RevokeKeyCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Option(names = "--with-key-password", - paramLabel = "PASSWORD") - String withKeyPassword; - - @Override - public void run() { - RevokeKey revokeKey = throwIfUnsupportedSubcommand( - SopCLI.getSop().revokeKey(), "revoke-key"); - - if (!armor) { - revokeKey.noArmor(); - } - - if (withKeyPassword != null) { - try { - String password = stringFromInputStream(getInput(withKeyPassword)); - revokeKey.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption e) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - Ready ready; - try { - ready = revokeKey.keys(System.in); - } catch (SOPGPException.KeyIsProtected e) { - String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", "STANDARD_IN"); - throw new SOPGPException.KeyIsProtected(errorMsg, e); - } - try { - ready.writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt new file mode 100644 index 0000000..0b93ac5 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import sop.exception.SOPGPException.KeyIsProtected + +@Command( + name = "revoke-key", + resourceBundle = "msg_revoke-key", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class RevokeKeyCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: String? = null + + override fun run() { + val revokeKey = throwIfUnsupportedSubcommand(SopCLI.getSop().revokeKey(), "revoke-key") + + if (!armor) { + revokeKey.noArmor() + } + + withKeyPassword?.let { + try { + val password = stringFromInputStream(getInput(it)) + revokeKey.withKeyPassword(password) + } catch (e: SOPGPException.UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw SOPGPException.UnsupportedOption(errorMsg, e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + val ready = + try { + revokeKey.keys(System.`in`) + } catch (e: KeyIsProtected) { + val errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", "STANDARD_IN") + throw KeyIsProtected(errorMsg, e) + } + try { + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 377a7287b3a8fc2e26eba4608964844eac63b2de Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:34:30 +0100 Subject: [PATCH 261/444] Kotlin conversion: ArmorCmd --- .../sop/cli/picocli/commands/ArmorCmd.java | 50 ------------------- .../sop/cli/picocli/commands/ArmorCmd.kt | 42 ++++++++++++++++ 2 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java deleted file mode 100644 index 5691686..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ArmorCmd.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.enums.ArmorLabel; -import sop.exception.SOPGPException; -import sop.operation.Armor; - -import java.io.IOException; - -@CommandLine.Command(name = "armor", - resourceBundle = "msg_armor", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class ArmorCmd extends AbstractSopCmd { - - @CommandLine.Option(names = {"--label"}, - paramLabel = "{auto|sig|key|cert|message}") - ArmorLabel label; - - @Override - public void run() { - Armor armor = throwIfUnsupportedSubcommand( - SopCLI.getSop().armor(), - "armor"); - - if (label != null) { - try { - armor.label(label); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--label"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - try { - Ready ready = armor.data(System.in); - ready.writeTo(System.out); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); - throw new SOPGPException.BadData(errorMsg, badData); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt new file mode 100644 index 0000000..cb6bc79 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt @@ -0,0 +1,42 @@ +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.enums.ArmorLabel +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +@Command( + name = "armor", + resourceBundle = "msg_armor", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class ArmorCmd : AbstractSopCmd() { + + @Option(names = ["--label"], paramLabel = "{auto|sig|key|cert|message}") + var label: ArmorLabel? = null + + override fun run() { + val armor = throwIfUnsupportedSubcommand(SopCLI.getSop().armor(), "armor") + + label?.let { + try { + armor.label(it) + } catch (unsupported: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--label") + throw UnsupportedOption(errorMsg, unsupported) + } + } + + try { + val ready = armor.data(System.`in`) + ready.writeTo(System.out) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data") + throw BadData(errorMsg, badData) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 49120c5da883dca98ec243577b9368c9569dd858 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:41:07 +0100 Subject: [PATCH 262/444] Kotlin conversion: ChangeKeyPasswordCmd --- .../commands/ChangeKeyPasswordCmd.java | 56 ------------------- .../picocli/commands/ChangeKeyPasswordCmd.kt | 46 +++++++++++++++ 2 files changed, 46 insertions(+), 56 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java deleted file mode 100644 index 5a6aa2a..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ChangeKeyPasswordCmd.java +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.ChangeKeyPassword; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "change-key-password", - resourceBundle = "msg_change-key-password", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class ChangeKeyPasswordCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Option(names = {"--old-key-password"}, - paramLabel = "PASSWORD") - List oldKeyPasswords = new ArrayList<>(); - - @CommandLine.Option(names = {"--new-key-password"}, arity = "0..1", - paramLabel = "PASSWORD") - String newKeyPassword = null; - - @Override - public void run() { - ChangeKeyPassword changeKeyPassword = throwIfUnsupportedSubcommand( - SopCLI.getSop().changeKeyPassword(), "change-key-password"); - - if (!armor) { - changeKeyPassword.noArmor(); - } - - for (String oldKeyPassword : oldKeyPasswords) { - changeKeyPassword.oldKeyPassphrase(oldKeyPassword); - } - - if (newKeyPassword != null) { - changeKeyPassword.newKeyPassphrase(newKeyPassword); - } - - try { - changeKeyPassword.keys(System.in).writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt new file mode 100644 index 0000000..0c2eb4a --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import java.lang.RuntimeException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException + +@Command( + name = "change-key-password", + resourceBundle = "msg_change-key-password", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class ChangeKeyPasswordCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor: Boolean = true + + @Option(names = ["--old-key-password"], paramLabel = "PASSWORD") + var oldKeyPasswords: List = listOf() + + @Option(names = ["--new-key-password"], arity = "0..1", paramLabel = "PASSWORD") + var newKeyPassword: String? = null + + override fun run() { + val changeKeyPassword = + throwIfUnsupportedSubcommand(SopCLI.getSop().changeKeyPassword(), "change-key-password") + + if (!armor) { + changeKeyPassword.noArmor() + } + + oldKeyPasswords.forEach { changeKeyPassword.oldKeyPassphrase(it) } + + newKeyPassword?.let { changeKeyPassword.newKeyPassphrase(it) } + + try { + changeKeyPassword.keys(System.`in`).writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 688b8043a26cce2a80807db136ababd656cb978a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:45:56 +0100 Subject: [PATCH 263/444] Kotlin conversion: DearmorCmd --- .../sop/cli/picocli/commands/DearmorCmd.java | 47 ------------------- .../sop/cli/picocli/commands/DearmorCmd.kt | 37 +++++++++++++++ 2 files changed, 37 insertions(+), 47 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java deleted file mode 100644 index f73e351..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DearmorCmd.java +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.Dearmor; - -import java.io.IOException; - -@CommandLine.Command(name = "dearmor", - resourceBundle = "msg_dearmor", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class DearmorCmd extends AbstractSopCmd { - - @Override - public void run() { - Dearmor dearmor = throwIfUnsupportedSubcommand( - SopCLI.getSop().dearmor(), "dearmor"); - - try { - dearmor.data(System.in) - .writeTo(System.out); - } catch (SOPGPException.BadData e) { - String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); - throw new SOPGPException.BadData(errorMsg, e); - } catch (IOException e) { - String msg = e.getMessage(); - if (msg == null) { - throw new RuntimeException(e); - } - - String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); - if (msg.equals("invalid armor") || - msg.equals("invalid armor header") || - msg.equals("inconsistent line endings in headers") || - msg.startsWith("unable to decode base64 data")) { - throw new SOPGPException.BadData(errorMsg, e); - } - - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt new file mode 100644 index 0000000..9ae97c6 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt @@ -0,0 +1,37 @@ +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import sop.exception.SOPGPException.BadData + +@Command( + name = "dearmor", + resourceBundle = "msg_dearmor", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class DearmorCmd : AbstractSopCmd() { + + override fun run() { + val dearmor = throwIfUnsupportedSubcommand(SopCLI.getSop().dearmor(), "dearmor") + + try { + dearmor.data(System.`in`).writeTo(System.out) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data") + throw BadData(errorMsg, badData) + } catch (e: IOException) { + e.message?.let { + val errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data") + if (it == "invalid armor" || + it == "invalid armor header" || + it == "inconsistent line endings in headers" || + it.startsWith("unable to decode base64 data")) { + throw BadData(errorMsg, e) + } + throw RuntimeException(e) + } + ?: throw RuntimeException(e) + } + } +} From 8e65771e3638ac57c7ef3befed334fbf48c0c3e3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 17:50:07 +0100 Subject: [PATCH 264/444] Kotlin conversion: ListProfilesCmd --- .../cli/picocli/commands/ListProfilesCmd.java | 36 ------------------- .../cli/picocli/commands/ListProfilesCmd.kt | 34 ++++++++++++++++++ 2 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ListProfilesCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java deleted file mode 100644 index 53ec024..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ListProfilesCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Profile; -import sop.cli.picocli.Print; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.ListProfiles; - -@CommandLine.Command(name = "list-profiles", - resourceBundle = "msg_list-profiles", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class ListProfilesCmd extends AbstractSopCmd { - - @CommandLine.Parameters(paramLabel = "COMMAND", arity = "1", descriptionKey = "subcommand") - String subcommand; - - @Override - public void run() { - ListProfiles listProfiles = throwIfUnsupportedSubcommand( - SopCLI.getSop().listProfiles(), "list-profiles"); - - try { - for (Profile profile : listProfiles.subcommand(subcommand)) { - Print.outln(profile.toString()); - } - } catch (SOPGPException.UnsupportedProfile e) { - String errorMsg = getMsg("sop.error.feature_support.subcommand_does_not_support_profiles", subcommand); - throw new SOPGPException.UnsupportedProfile(errorMsg, e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ListProfilesCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ListProfilesCmd.kt new file mode 100644 index 0000000..b770e82 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ListProfilesCmd.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine.Command +import picocli.CommandLine.Parameters +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import sop.exception.SOPGPException.UnsupportedProfile + +@Command( + name = "list-profiles", + resourceBundle = "msg_list-profiles", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class ListProfilesCmd : AbstractSopCmd() { + + @Parameters(paramLabel = "COMMAND", arity = "1", descriptionKey = "subcommand") + lateinit var subcommand: String + + override fun run() { + val listProfiles = + throwIfUnsupportedSubcommand(SopCLI.getSop().listProfiles(), "list-profiles") + + try { + listProfiles.subcommand(subcommand).forEach { println(it) } + } catch (e: UnsupportedProfile) { + val errorMsg = + getMsg("sop.error.feature_support.subcommand_does_not_support_profiles", subcommand) + throw UnsupportedProfile(errorMsg, e) + } + } +} From 9daabb758a559b48b24582cd5f1e776c29c29309 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 18:18:55 +0100 Subject: [PATCH 265/444] Kotlin conversion: DecryptCmd --- .../sop/cli/picocli/commands/DecryptCmd.java | 255 ------------------ .../sop/cli/picocli/commands/DecryptCmd.kt | 223 +++++++++++++++ .../cli/picocli/commands/DecryptCmdTest.java | 6 +- 3 files changed, 226 insertions(+), 258 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java deleted file mode 100644 index a681b4d..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/DecryptCmd.java +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.Verification; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.Decrypt; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -@CommandLine.Command(name = "decrypt", - resourceBundle = "msg_decrypt", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class DecryptCmd extends AbstractSopCmd { - - private static final String OPT_SESSION_KEY_OUT = "--session-key-out"; - private static final String OPT_WITH_SESSION_KEY = "--with-session-key"; - private static final String OPT_WITH_PASSWORD = "--with-password"; - private static final String OPT_WITH_KEY_PASSWORD = "--with-key-password"; - private static final String OPT_VERIFICATIONS_OUT = "--verifications-out"; // see SOP-05 - private static final String OPT_VERIFY_WITH = "--verify-with"; - private static final String OPT_NOT_BEFORE = "--verify-not-before"; - private static final String OPT_NOT_AFTER = "--verify-not-after"; - - - @CommandLine.Option( - names = {OPT_SESSION_KEY_OUT}, - paramLabel = "SESSIONKEY") - String sessionKeyOut; - - @CommandLine.Option( - names = {OPT_WITH_SESSION_KEY}, - paramLabel = "SESSIONKEY") - List withSessionKey = new ArrayList<>(); - - @CommandLine.Option( - names = {OPT_WITH_PASSWORD}, - paramLabel = "PASSWORD") - List withPassword = new ArrayList<>(); - - @CommandLine.Option(names = {OPT_VERIFICATIONS_OUT, "--verify-out"}, // TODO: Remove --verify-out in 06 - paramLabel = "VERIFICATIONS") - String verifyOut; - - @CommandLine.Option(names = {OPT_VERIFY_WITH}, - paramLabel = "CERT") - List certs = new ArrayList<>(); - - @CommandLine.Option(names = {OPT_NOT_BEFORE}, - paramLabel = "DATE") - String notBefore = "-"; - - @CommandLine.Option(names = {OPT_NOT_AFTER}, - paramLabel = "DATE") - String notAfter = "now"; - - @CommandLine.Parameters(index = "0..*", - paramLabel = "KEY") - List keys = new ArrayList<>(); - - @CommandLine.Option(names = {OPT_WITH_KEY_PASSWORD}, - paramLabel = "PASSWORD") - List withKeyPassword = new ArrayList<>(); - - @Override - public void run() { - Decrypt decrypt = throwIfUnsupportedSubcommand( - SopCLI.getSop().decrypt(), "decrypt"); - - throwIfOutputExists(verifyOut); - throwIfOutputExists(sessionKeyOut); - - setNotAfter(notAfter, decrypt); - setNotBefore(notBefore, decrypt); - setWithPasswords(withPassword, decrypt); - setWithSessionKeys(withSessionKey, decrypt); - setWithKeyPassword(withKeyPassword, decrypt); - setVerifyWith(certs, decrypt); - setDecryptWith(keys, decrypt); - - if (verifyOut != null && certs.isEmpty()) { - String errorMsg = getMsg("sop.error.usage.option_requires_other_option", OPT_VERIFICATIONS_OUT, OPT_VERIFY_WITH); - throw new SOPGPException.IncompleteVerification(errorMsg); - } - - try { - ReadyWithResult ready = decrypt.ciphertext(System.in); - DecryptionResult result = ready.writeTo(System.out); - writeSessionKeyOut(result); - writeVerifyOut(result); - - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); - throw new SOPGPException.BadData(errorMsg, badData); - } catch (SOPGPException.CannotDecrypt e) { - String errorMsg = getMsg("sop.error.runtime.cannot_decrypt_message"); - throw new SOPGPException.CannotDecrypt(errorMsg, e); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } - } - - private void writeVerifyOut(DecryptionResult result) throws IOException { - if (verifyOut != null) { - if (result.getVerifications().isEmpty()) { - String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); - throw new SOPGPException.NoSignature(errorMsg); - } - - try (OutputStream fileOut = getOutput(verifyOut)) { - PrintWriter writer = new PrintWriter(fileOut); - for (Verification verification : result.getVerifications()) { - // CHECKSTYLE:OFF - writer.println(verification.toString()); - // CHECKSTYLE:ON - } - writer.flush(); - } - } - } - - private void writeSessionKeyOut(DecryptionResult result) throws IOException { - if (sessionKeyOut == null) { - return; - } - try (OutputStream outputStream = getOutput(sessionKeyOut)) { - if (!result.getSessionKey().isPresent()) { - String errorMsg = getMsg("sop.error.runtime.no_session_key_extracted"); - throw new SOPGPException.UnsupportedOption(String.format(errorMsg, OPT_SESSION_KEY_OUT)); - } - SessionKey sessionKey = result.getSessionKey().get(); - PrintWriter writer = new PrintWriter(outputStream); - // CHECKSTYLE:OFF - writer.println(sessionKey.toString()); - // CHECKSTYLE:ON - writer.flush(); - } - } - - private void setDecryptWith(List keys, Decrypt decrypt) { - for (String key : keys) { - try (InputStream keyIn = getInput(key)) { - decrypt.withKey(keyIn); - } catch (SOPGPException.KeyIsProtected keyIsProtected) { - String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", key); - throw new SOPGPException.KeyIsProtected(errorMsg, keyIsProtected); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_private_key", key); - throw new SOPGPException.BadData(errorMsg, badData); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private void setVerifyWith(List certs, Decrypt decrypt) { - for (String cert : certs) { - try (InputStream certIn = getInput(cert)) { - decrypt.verifyWithCert(certIn); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_certificate", cert); - throw new SOPGPException.BadData(errorMsg, badData); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } - } - } - - private void setWithSessionKeys(List withSessionKey, Decrypt decrypt) { - for (String sessionKeyFile : withSessionKey) { - String sessionKeyString; - try { - sessionKeyString = stringFromInputStream(getInput(sessionKeyFile)); - } catch (IOException e) { - throw new RuntimeException(e); - } - SessionKey sessionKey; - try { - sessionKey = SessionKey.fromString(sessionKeyString); - } catch (IllegalArgumentException e) { - String errorMsg = getMsg("sop.error.input.malformed_session_key"); - throw new IllegalArgumentException(errorMsg, e); - } - try { - decrypt.withSessionKey(sessionKey); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_SESSION_KEY); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - } - - private void setWithPasswords(List withPassword, Decrypt decrypt) { - for (String passwordFile : withPassword) { - try { - String password = stringFromInputStream(getInput(passwordFile)); - decrypt.withPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_PASSWORD); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private void setWithKeyPassword(List withKeyPassword, Decrypt decrypt) { - for (String passwordFile : withKeyPassword) { - try { - String password = stringFromInputStream(getInput(passwordFile)); - decrypt.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_KEY_PASSWORD); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - private void setNotAfter(String notAfter, Decrypt decrypt) { - Date notAfterDate = parseNotAfter(notAfter); - try { - decrypt.verifyNotAfter(notAfterDate); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_NOT_AFTER); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - private void setNotBefore(String notBefore, Decrypt decrypt) { - Date notBeforeDate = parseNotBefore(notBefore); - try { - decrypt.verifyNotBefore(notBeforeDate); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_NOT_BEFORE); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt new file mode 100644 index 0000000..95298c2 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt @@ -0,0 +1,223 @@ +package sop.cli.picocli.commands + +import java.io.IOException +import java.io.PrintWriter +import picocli.CommandLine.* +import sop.DecryptionResult +import sop.SessionKey +import sop.SessionKey.Companion.fromString +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.* +import sop.operation.Decrypt + +@Command( + name = "decrypt", + resourceBundle = "msg_decrypt", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class DecryptCmd : AbstractSopCmd() { + + @Option(names = [OPT_SESSION_KEY_OUT], paramLabel = "SESSIONKEY") + var sessionKeyOut: String? = null + + @Option(names = [OPT_WITH_SESSION_KEY], paramLabel = "SESSIONKEY") + var withSessionKey: List = listOf() + + @Option(names = [OPT_WITH_PASSWORD], paramLabel = "PASSWORD") + var withPassword: List = listOf() + + @Option(names = [OPT_VERIFICATIONS_OUT], paramLabel = "VERIFICATIONS") + var verifyOut: String? = null + + @Option(names = [OPT_VERIFY_WITH], paramLabel = "CERT") var certs: List = listOf() + + @Option(names = [OPT_NOT_BEFORE], paramLabel = "DATE") var notBefore = "-" + + @Option(names = [OPT_NOT_AFTER], paramLabel = "DATE") var notAfter = "now" + + @Parameters(index = "0..*", paramLabel = "KEY") var keys: List = listOf() + + @Option(names = [OPT_WITH_KEY_PASSWORD], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + override fun run() { + val decrypt = throwIfUnsupportedSubcommand(SopCLI.getSop().decrypt(), "decrypt") + + throwIfOutputExists(verifyOut) + throwIfOutputExists(sessionKeyOut) + + setNotAfter(notAfter, decrypt) + setNotBefore(notBefore, decrypt) + setWithPasswords(withPassword, decrypt) + setWithSessionKeys(withSessionKey, decrypt) + setWithKeyPassword(withKeyPassword, decrypt) + setVerifyWith(certs, decrypt) + setDecryptWith(keys, decrypt) + + if (verifyOut != null && certs.isEmpty()) { + val errorMsg = + getMsg( + "sop.error.usage.option_requires_other_option", + OPT_VERIFICATIONS_OUT, + OPT_VERIFY_WITH) + throw IncompleteVerification(errorMsg) + } + + try { + val ready = decrypt.ciphertext(System.`in`) + val result = ready.writeTo(System.out) + writeSessionKeyOut(result) + writeVerifyOut(result) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_a_message") + throw BadData(errorMsg, badData) + } catch (e: CannotDecrypt) { + val errorMsg = getMsg("sop.error.runtime.cannot_decrypt_message") + throw CannotDecrypt(errorMsg, e) + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } + } + + @Throws(IOException::class) + private fun writeVerifyOut(result: DecryptionResult) { + verifyOut?.let { + if (result.verifications.isEmpty()) { + val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found") + throw NoSignature(errorMsg) + } + + getOutput(verifyOut).use { out -> + PrintWriter(out).use { pw -> result.verifications.forEach { pw.println(it) } } + } + } + } + + @Throws(IOException::class) + private fun writeSessionKeyOut(result: DecryptionResult) { + sessionKeyOut?.let { fileName -> + getOutput(fileName).use { out -> + if (!result.sessionKey.isPresent) { + val errorMsg = getMsg("sop.error.runtime.no_session_key_extracted") + throw UnsupportedOption(String.format(errorMsg, OPT_SESSION_KEY_OUT)) + } + + PrintWriter(out).use { it.println(result.sessionKey.get()!!) } + } + } + } + + private fun setDecryptWith(keys: List, decrypt: Decrypt) { + for (key in keys) { + try { + getInput(key).use { decrypt.withKey(it) } + } catch (keyIsProtected: KeyIsProtected) { + val errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", key) + throw KeyIsProtected(errorMsg, keyIsProtected) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", key) + throw BadData(errorMsg, badData) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + } + + private fun setVerifyWith(certs: List, decrypt: Decrypt) { + for (cert in certs) { + try { + getInput(cert).use { certIn -> decrypt.verifyWithCert(certIn) } + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", cert) + throw BadData(errorMsg, badData) + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } + } + } + + private fun setWithSessionKeys(withSessionKey: List, decrypt: Decrypt) { + for (sessionKeyFile in withSessionKey) { + val sessionKeyString: String = + try { + stringFromInputStream(getInput(sessionKeyFile)) + } catch (e: IOException) { + throw RuntimeException(e) + } + val sessionKey: SessionKey = + try { + fromString(sessionKeyString) + } catch (e: IllegalArgumentException) { + val errorMsg = getMsg("sop.error.input.malformed_session_key") + throw IllegalArgumentException(errorMsg, e) + } + try { + decrypt.withSessionKey(sessionKey) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_SESSION_KEY) + throw UnsupportedOption(errorMsg, unsupportedOption) + } + } + } + + private fun setWithPasswords(withPassword: List, decrypt: Decrypt) { + for (passwordFile in withPassword) { + try { + val password = stringFromInputStream(getInput(passwordFile)) + decrypt.withPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_PASSWORD) + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + } + + private fun setWithKeyPassword(withKeyPassword: List, decrypt: Decrypt) { + for (passwordFile in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFile)) + decrypt.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_KEY_PASSWORD) + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + } + + private fun setNotAfter(notAfter: String, decrypt: Decrypt) { + val notAfterDate = parseNotAfter(notAfter) + try { + decrypt.verifyNotAfter(notAfterDate) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_NOT_AFTER) + throw UnsupportedOption(errorMsg, unsupportedOption) + } + } + + private fun setNotBefore(notBefore: String, decrypt: Decrypt) { + val notBeforeDate = parseNotBefore(notBefore) + try { + decrypt.verifyNotBefore(notBeforeDate) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_NOT_BEFORE) + throw UnsupportedOption(errorMsg, unsupportedOption) + } + } + + companion object { + const val OPT_SESSION_KEY_OUT = "--session-key-out" + const val OPT_WITH_SESSION_KEY = "--with-session-key" + const val OPT_WITH_PASSWORD = "--with-password" + const val OPT_WITH_KEY_PASSWORD = "--with-key-password" + const val OPT_VERIFICATIONS_OUT = "--verifications-out" + const val OPT_VERIFY_WITH = "--verify-with" + const val OPT_NOT_BEFORE = "--verify-not-before" + const val OPT_NOT_AFTER = "--verify-not-after" + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index e3dd198..c2c67ba 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -278,7 +278,7 @@ public class DecryptCmdTest { File certFile = File.createTempFile("existing-verify-out-cert", ".asc"); File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp"); - SopCLI.main(new String[] {"decrypt", "--verify-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); + SopCLI.main(new String[] {"decrypt", "--verifications-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); } @Test @@ -302,7 +302,7 @@ public class DecryptCmdTest { } }); - SopCLI.main(new String[] {"decrypt", "--verify-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); + SopCLI.main(new String[] {"decrypt", "--verifications-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) { String line = reader.readLine(); assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line); @@ -377,6 +377,6 @@ public class DecryptCmdTest { @Test @ExpectSystemExitWithStatus(SOPGPException.IncompleteVerification.EXIT_CODE) public void verifyOutWithoutVerifyWithCausesExit23() { - SopCLI.main(new String[] {"decrypt", "--verify-out", "out.file"}); + SopCLI.main(new String[] {"decrypt", "--verifications-out", "out.file"}); } } From 714c933cef399b3c98101c51b14d4f86e45fb407 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 18:31:52 +0100 Subject: [PATCH 266/444] Kotlin conversion: InlineDetachCmd --- .../cli/picocli/commands/InlineDetachCmd.java | 50 ------------------- .../cli/picocli/commands/InlineDetachCmd.kt | 47 +++++++++++++++++ 2 files changed, 47 insertions(+), 50 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineDetachCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java deleted file mode 100644 index 52b654f..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineDetachCmd.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Signatures; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.InlineDetach; - -import java.io.IOException; -import java.io.OutputStream; - -@CommandLine.Command(name = "inline-detach", - resourceBundle = "msg_inline-detach", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class InlineDetachCmd extends AbstractSopCmd { - - @CommandLine.Option( - names = {"--signatures-out"}, - paramLabel = "SIGNATURES") - String signaturesOut; - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @Override - public void run() { - InlineDetach inlineDetach = throwIfUnsupportedSubcommand( - SopCLI.getSop().inlineDetach(), "inline-detach"); - - throwIfOutputExists(signaturesOut); - throwIfMissingArg(signaturesOut, "--signatures-out"); - - if (!armor) { - inlineDetach.noArmor(); - } - - try (OutputStream outputStream = getOutput(signaturesOut)) { - Signatures signatures = inlineDetach - .message(System.in).writeTo(System.out); - signatures.writeTo(outputStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineDetachCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineDetachCmd.kt new file mode 100644 index 0000000..e311adf --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineDetachCmd.kt @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import java.lang.RuntimeException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException + +@Command( + name = "inline-detach", + resourceBundle = "msg_inline-detach", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class InlineDetachCmd : AbstractSopCmd() { + + @Option(names = ["--signatures-out"], paramLabel = "SIGNATURES") + var signaturesOut: String? = null + + @Option(names = ["--no-armor"], negatable = true) var armor: Boolean = true + + override fun run() { + val inlineDetach = + throwIfUnsupportedSubcommand(SopCLI.getSop().inlineDetach(), "inline-detach") + + throwIfOutputExists(signaturesOut) + throwIfMissingArg(signaturesOut, "--signatures-out") + + if (!armor) { + inlineDetach.noArmor() + } + + try { + getOutput(signaturesOut).use { sigOut -> + inlineDetach + .message(System.`in`) + .writeTo(System.out) // message out + .writeTo(sigOut) // signatures out + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 159ffbe0846960f419ba743a744f000012adcd49 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 4 Nov 2023 18:32:01 +0100 Subject: [PATCH 267/444] Add missing license headers --- .../src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt | 4 ++++ .../src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt | 4 ++++ .../src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt index cb6bc79..ccfba4d 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.cli.picocli.commands import java.io.IOException diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt index 9ae97c6..09d2a71 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DearmorCmd.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.cli.picocli.commands import java.io.IOException diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt index 95298c2..1e688b3 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.cli.picocli.commands import java.io.IOException From bfad8c4203258563492323dd2bbbf2646a72e2de Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:21:42 +0100 Subject: [PATCH 268/444] Kotlin conversion: EncryptCmd --- .../sop/cli/picocli/commands/EncryptCmd.java | 154 ------------------ .../sop/cli/picocli/commands/EncryptCmd.kt | 138 ++++++++++++++++ 2 files changed, 138 insertions(+), 154 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/EncryptCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java deleted file mode 100644 index efda26f..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.enums.EncryptAs; -import sop.exception.SOPGPException; -import sop.operation.Encrypt; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "encrypt", - resourceBundle = "msg_encrypt", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class EncryptCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Option(names = {"--as"}, - paramLabel = "{binary|text}") - EncryptAs type; - - @CommandLine.Option(names = "--with-password", - paramLabel = "PASSWORD") - List withPassword = new ArrayList<>(); - - @CommandLine.Option(names = "--sign-with", - paramLabel = "KEY") - List signWith = new ArrayList<>(); - - @CommandLine.Option(names = "--with-key-password", - paramLabel = "PASSWORD") - List withKeyPassword = new ArrayList<>(); - - @CommandLine.Option(names = "--profile", - paramLabel = "PROFILE") - String profile; - - @CommandLine.Parameters(index = "0..*", - paramLabel = "CERTS") - List certs = new ArrayList<>(); - - @Override - public void run() { - Encrypt encrypt = throwIfUnsupportedSubcommand( - SopCLI.getSop().encrypt(), "encrypt"); - - if (profile != null) { - try { - encrypt.profile(profile); - } catch (SOPGPException.UnsupportedProfile e) { - String errorMsg = getMsg("sop.error.usage.profile_not_supported", "encrypt", profile); - throw new SOPGPException.UnsupportedProfile(errorMsg, e); - } - } - - if (type != null) { - try { - encrypt.mode(type); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - if (withPassword.isEmpty() && certs.isEmpty()) { - String errorMsg = getMsg("sop.error.usage.password_or_cert_required"); - throw new SOPGPException.MissingArg(errorMsg); - } - - for (String passwordFileName : withPassword) { - try { - String password = stringFromInputStream(getInput(passwordFileName)); - encrypt.withPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (String passwordFileName : withKeyPassword) { - try { - String password = stringFromInputStream(getInput(passwordFileName)); - encrypt.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (String keyInput : signWith) { - try (InputStream keyIn = getInput(keyInput)) { - encrypt.signWith(keyIn); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.KeyIsProtected keyIsProtected) { - String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput); - throw new SOPGPException.KeyIsProtected(errorMsg, keyIsProtected); - } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - String errorMsg = getMsg("sop.error.runtime.key_uses_unsupported_asymmetric_algorithm", keyInput); - throw new SOPGPException.UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo); - } catch (SOPGPException.KeyCannotSign keyCannotSign) { - String errorMsg = getMsg("sop.error.runtime.key_cannot_sign", keyInput); - throw new SOPGPException.KeyCannotSign(errorMsg, keyCannotSign); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - for (String certInput : certs) { - try (InputStream certIn = getInput(certInput)) { - encrypt.withCert(certIn); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - String errorMsg = getMsg("sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm", certInput); - throw new SOPGPException.UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo); - } catch (SOPGPException.CertCannotEncrypt certCannotEncrypt) { - String errorMsg = getMsg("sop.error.runtime.cert_cannot_encrypt", certInput); - throw new SOPGPException.CertCannotEncrypt(errorMsg, certCannotEncrypt); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - if (!armor) { - encrypt.noArmor(); - } - - try { - Ready ready = encrypt.plaintext(System.in); - ready.writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/EncryptCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/EncryptCmd.kt new file mode 100644 index 0000000..b3b0d87 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/EncryptCmd.kt @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.enums.EncryptAs +import sop.exception.SOPGPException.* + +@Command( + name = "encrypt", + resourceBundle = "msg_encrypt", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class EncryptCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--as"], paramLabel = "{binary|text}") var type: EncryptAs? = null + + @Option(names = ["--with-password"], paramLabel = "PASSWORD") + var withPassword: List = listOf() + + @Option(names = ["--sign-with"], paramLabel = "KEY") var signWith: List = listOf() + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--profile"], paramLabel = "PROFILE") var profile: String? = null + + @Parameters(index = "0..*", paramLabel = "CERTS") var certs: List = listOf() + + override fun run() { + val encrypt = throwIfUnsupportedSubcommand(SopCLI.getSop().encrypt(), "encrypt") + + profile?.let { + try { + encrypt.profile(it) + } catch (e: UnsupportedProfile) { + val errorMsg = getMsg("sop.error.usage.profile_not_supported", "encrypt", it) + throw UnsupportedProfile(errorMsg, e) + } + } + + type?.let { + try { + encrypt.mode(it) + } catch (e: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as") + throw UnsupportedOption(errorMsg, e) + } + } + + if (withPassword.isEmpty() && certs.isEmpty()) { + val errorMsg = getMsg("sop.error.usage.password_or_cert_required") + throw MissingArg(errorMsg) + } + + for (passwordFileName in withPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + encrypt.withPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (passwordFileName in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + encrypt.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (keyInput in signWith) { + try { + getInput(keyInput).use { keyIn -> encrypt.signWith(keyIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (keyIsProtected: KeyIsProtected) { + val errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput) + throw KeyIsProtected(errorMsg, keyIsProtected) + } catch (unsupportedAsymmetricAlgo: UnsupportedAsymmetricAlgo) { + val errorMsg = + getMsg("sop.error.runtime.key_uses_unsupported_asymmetric_algorithm", keyInput) + throw UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo) + } catch (keyCannotSign: KeyCannotSign) { + val errorMsg = getMsg("sop.error.runtime.key_cannot_sign", keyInput) + throw KeyCannotSign(errorMsg, keyCannotSign) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput) + throw BadData(errorMsg, badData) + } + } + + for (certInput in certs) { + try { + getInput(certInput).use { certIn -> encrypt.withCert(certIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (unsupportedAsymmetricAlgo: UnsupportedAsymmetricAlgo) { + val errorMsg = + getMsg( + "sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm", certInput) + throw UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo) + } catch (certCannotEncrypt: CertCannotEncrypt) { + val errorMsg = getMsg("sop.error.runtime.cert_cannot_encrypt", certInput) + throw CertCannotEncrypt(errorMsg, certCannotEncrypt) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", certInput) + throw BadData(errorMsg, badData) + } + } + + if (!armor) { + encrypt.noArmor() + } + + try { + val ready = encrypt.plaintext(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 019dd63e1b6d4ee56d4d9c02328310ff8d213d16 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:26:06 +0100 Subject: [PATCH 269/444] Kotlin conversion: ExtractCertCmd --- .../cli/picocli/commands/ExtractCertCmd.java | 43 ------------------- .../cli/picocli/commands/ExtractCertCmd.kt | 40 +++++++++++++++++ 2 files changed, 40 insertions(+), 43 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ExtractCertCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java deleted file mode 100644 index 64a7a84..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/ExtractCertCmd.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import java.io.IOException; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.ExtractCert; - -@CommandLine.Command(name = "extract-cert", - resourceBundle = "msg_extract-cert", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class ExtractCertCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @Override - public void run() { - ExtractCert extractCert = throwIfUnsupportedSubcommand( - SopCLI.getSop().extractCert(), "extract-cert"); - - if (!armor) { - extractCert.noArmor(); - } - - try { - Ready ready = extractCert.key(System.in); - ready.writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.stdin_not_a_private_key"); - throw new SOPGPException.BadData(errorMsg, badData); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ExtractCertCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ExtractCertCmd.kt new file mode 100644 index 0000000..cff996f --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ExtractCertCmd.kt @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import sop.exception.SOPGPException.BadData + +@Command( + name = "extract-cert", + resourceBundle = "msg_extract-cert", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class ExtractCertCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + override fun run() { + val extractCert = + throwIfUnsupportedSubcommand(SopCLI.getSop().extractCert(), "extract-cert") + + if (!armor) { + extractCert.noArmor() + } + + try { + val ready = extractCert.key(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_a_private_key") + throw BadData(errorMsg, badData) + } + } +} From e9a5467f6b885b44248ee6b4555e3f991769d9b3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:33:34 +0100 Subject: [PATCH 270/444] Kotlin conversion: GenerateKeyCmd --- .../cli/picocli/commands/GenerateKeyCmd.java | 85 ------------------- .../cli/picocli/commands/GenerateKeyCmd.kt | 76 +++++++++++++++++ 2 files changed, 76 insertions(+), 85 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java deleted file mode 100644 index eea992e..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.GenerateKey; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "generate-key", - resourceBundle = "msg_generate-key", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class GenerateKeyCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Parameters(paramLabel = "USERID") - List userId = new ArrayList<>(); - - @CommandLine.Option(names = "--with-key-password", - paramLabel = "PASSWORD") - String withKeyPassword; - - @CommandLine.Option(names = "--profile", - paramLabel = "PROFILE") - String profile; - - @CommandLine.Option(names = "--signing-only") - boolean signingOnly = false; - - @Override - public void run() { - GenerateKey generateKey = throwIfUnsupportedSubcommand( - SopCLI.getSop().generateKey(), "generate-key"); - - if (profile != null) { - try { - generateKey.profile(profile); - } catch (SOPGPException.UnsupportedProfile e) { - String errorMsg = getMsg("sop.error.usage.profile_not_supported", "generate-key", profile); - throw new SOPGPException.UnsupportedProfile(errorMsg, e); - } - } - - if (signingOnly) { - generateKey.signingOnly(); - } - - for (String userId : userId) { - generateKey.userId(userId); - } - - if (!armor) { - generateKey.noArmor(); - } - - if (withKeyPassword != null) { - try { - String password = stringFromInputStream(getInput(withKeyPassword)); - generateKey.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption e) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, e); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - try { - Ready ready = generateKey.generate(); - ready.writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt new file mode 100644 index 0000000..fb5e321 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.UnsupportedOption +import sop.exception.SOPGPException.UnsupportedProfile + +@Command( + name = "generate-key", + resourceBundle = "msg_generate-key", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class GenerateKeyCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Parameters(paramLabel = "USERID") var userId: List = listOf() + + @Option(names = ["---with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: String? = null + + @Option(names = ["--profile"], paramLabel = "PROFILE") var profile: String? = null + + @Option(names = ["--signing-only"]) var signingOnly: Boolean = false + + override fun run() { + val generateKey = + throwIfUnsupportedSubcommand(SopCLI.getSop().generateKey(), "generate-key") + + profile?.let { + try { + generateKey.profile(it) + } catch (e: UnsupportedProfile) { + val errorMsg = + getMsg("sop.error.usage.profile_not_supported", "generate-key", profile!!) + throw UnsupportedProfile(errorMsg, e) + } + } + + if (signingOnly) { + generateKey.signingOnly() + } + + for (userId in userId) { + generateKey.userId(userId) + } + + if (!armor) { + generateKey.noArmor() + } + + withKeyPassword?.let { + try { + val password = stringFromInputStream(getInput(it)) + generateKey.withKeyPassword(password) + } catch (e: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + try { + val ready = generateKey.generate() + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 2e118357e2cee22e65ef999e94c830d70e48ad54 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:40:46 +0100 Subject: [PATCH 271/444] Kotlin conversion: InlineSignCmd --- .../cli/picocli/commands/InlineSignCmd.java | 101 ------------------ .../sop/cli/picocli/commands/InlineSignCmd.kt | 89 +++++++++++++++ 2 files changed, 89 insertions(+), 101 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineSignCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java deleted file mode 100644 index 1865bcf..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineSignCmd.java +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.Ready; -import sop.cli.picocli.SopCLI; -import sop.enums.InlineSignAs; -import sop.exception.SOPGPException; -import sop.operation.InlineSign; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "inline-sign", - resourceBundle = "msg_inline-sign", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class InlineSignCmd extends AbstractSopCmd { - - @CommandLine.Option(names = "--no-armor", - negatable = true) - boolean armor = true; - - @CommandLine.Option(names = "--as", - paramLabel = "{binary|text|clearsigned}") - InlineSignAs type; - - @CommandLine.Parameters(paramLabel = "KEYS") - List secretKeyFile = new ArrayList<>(); - - @CommandLine.Option(names = "--with-key-password", - paramLabel = "PASSWORD") - List withKeyPassword = new ArrayList<>(); - - @Override - public void run() { - InlineSign inlineSign = throwIfUnsupportedSubcommand( - SopCLI.getSop().inlineSign(), "inline-sign"); - - // Clearsigned messages are inherently armored, so --no-armor makes no sense. - if (!armor && type == InlineSignAs.clearsigned) { - String errorMsg = getMsg("sop.error.usage.incompatible_options.clearsigned_no_armor"); - throw new SOPGPException.IncompatibleOptions(errorMsg); - } - - if (type != null) { - try { - inlineSign.mode(type); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - if (secretKeyFile.isEmpty()) { - String errorMsg = getMsg("sop.error.usage.parameter_required", "KEYS"); - throw new SOPGPException.MissingArg(errorMsg); - } - - for (String passwordFile : withKeyPassword) { - try { - String password = stringFromInputStream(getInput(passwordFile)); - inlineSign.withKeyPassword(password); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - for (String keyInput : secretKeyFile) { - try (InputStream keyIn = getInput(keyInput)) { - inlineSign.key(keyIn); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (SOPGPException.KeyIsProtected e) { - String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput); - throw new SOPGPException.KeyIsProtected(errorMsg, e); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - if (!armor) { - inlineSign.noArmor(); - } - - try { - Ready ready = inlineSign.data(System.in); - ready.writeTo(System.out); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineSignCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineSignCmd.kt new file mode 100644 index 0000000..c41f6f6 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineSignCmd.kt @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.enums.InlineSignAs +import sop.exception.SOPGPException.* + +@Command( + name = "inline-sign", + resourceBundle = "msg_inline-sign", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class InlineSignCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--as"], paramLabel = "{binary|text|clearsigned}") + var type: InlineSignAs? = null + + @Parameters(paramLabel = "KEYS") var secretKeyFile: List = listOf() + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + override fun run() { + val inlineSign = throwIfUnsupportedSubcommand(SopCLI.getSop().inlineSign(), "inline-sign") + + if (!armor && type == InlineSignAs.clearsigned) { + val errorMsg = getMsg("sop.error.usage.incompatible_options.clearsigned_no_armor") + throw IncompatibleOptions(errorMsg) + } + + type?.let { + try { + inlineSign.mode(it) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as") + throw UnsupportedOption(errorMsg, unsupportedOption) + } + } + + if (secretKeyFile.isEmpty()) { + val errorMsg = getMsg("sop.error.usage.parameter_required", "KEYS") + throw MissingArg(errorMsg) + } + + for (passwordFile in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFile)) + inlineSign.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (keyInput in secretKeyFile) { + try { + getInput(keyInput).use { keyIn -> inlineSign.key(keyIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: KeyIsProtected) { + val errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput) + throw KeyIsProtected(errorMsg, e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput) + throw BadData(errorMsg, badData) + } + } + + if (!armor) { + inlineSign.noArmor() + } + + try { + val ready = inlineSign.data(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From b884f2b1a9447064d17f6944d1dcaf0fcd43c3c8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:49:04 +0100 Subject: [PATCH 272/444] Kotlin conversion: InlineVerifyCmd --- .../cli/picocli/commands/InlineVerifyCmd.java | 108 ------------------ .../cli/picocli/commands/package-info.java | 8 -- .../cli/picocli/commands/InlineVerifyCmd.kt | 93 +++++++++++++++ 3 files changed, 93 insertions(+), 116 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/commands/package-info.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineVerifyCmd.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java deleted file mode 100644 index c413c85..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/InlineVerifyCmd.java +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli.commands; - -import picocli.CommandLine; -import sop.ReadyWithResult; -import sop.Verification; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.InlineVerify; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -@CommandLine.Command(name = "inline-verify", - resourceBundle = "msg_inline-verify", - exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) -public class InlineVerifyCmd extends AbstractSopCmd { - - @CommandLine.Parameters(arity = "0..*", - paramLabel = "CERT") - List certificates = new ArrayList<>(); - - @CommandLine.Option(names = {"--not-before"}, - paramLabel = "DATE") - String notBefore = "-"; - - @CommandLine.Option(names = {"--not-after"}, - paramLabel = "DATE") - String notAfter = "now"; - - @CommandLine.Option(names = "--verifications-out", paramLabel = "VERIFICATIONS") - String verificationsOut; - - @Override - public void run() { - InlineVerify inlineVerify = throwIfUnsupportedSubcommand( - SopCLI.getSop().inlineVerify(), "inline-verify"); - - throwIfOutputExists(verificationsOut); - - if (notAfter != null) { - try { - inlineVerify.notAfter(parseNotAfter(notAfter)); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - if (notBefore != null) { - try { - inlineVerify.notBefore(parseNotBefore(notBefore)); - } catch (SOPGPException.UnsupportedOption unsupportedOption) { - String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before"); - throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); - } - } - - for (String certInput : certificates) { - try (InputStream certIn = getInput(certInput)) { - inlineVerify.cert(certIn); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - String errorMsg = getMsg("sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm", certInput); - throw new SOPGPException.UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); - throw new SOPGPException.BadData(errorMsg, badData); - } - } - - List verifications = null; - try { - ReadyWithResult> ready = inlineVerify.data(System.in); - verifications = ready.writeTo(System.out); - } catch (SOPGPException.NoSignature e) { - String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); - throw new SOPGPException.NoSignature(errorMsg, e); - } catch (IOException ioException) { - throw new RuntimeException(ioException); - } catch (SOPGPException.BadData badData) { - String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); - throw new SOPGPException.BadData(errorMsg, badData); - } - - if (verificationsOut != null) { - try (OutputStream outputStream = getOutput(verificationsOut)) { - PrintWriter pw = new PrintWriter(outputStream); - for (Verification verification : verifications) { - // CHECKSTYLE:OFF - pw.println(verification); - // CHECKSTYLE:ON - } - pw.flush(); - pw.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } -} diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/package-info.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/package-info.java deleted file mode 100644 index fc6aefd..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/commands/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Subcommands of the PGPainless SOP. - */ -package sop.cli.picocli.commands; diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineVerifyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineVerifyCmd.kt new file mode 100644 index 0000000..6a641a6 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/InlineVerifyCmd.kt @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import java.io.PrintWriter +import picocli.CommandLine.* +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.* + +@Command( + name = "inline-verify", + resourceBundle = "msg_inline-verify", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class InlineVerifyCmd : AbstractSopCmd() { + + @Parameters(arity = "0..*", paramLabel = "CERT") var certificates: List = listOf() + + @Option(names = ["--not-before"], paramLabel = "DATE") var notBefore: String = "-" + + @Option(names = ["--not-after"], paramLabel = "DATE") var notAfter: String = "now" + + @Option(names = ["--verifications-out"], paramLabel = "VERIFICATIONS") + var verificationsOut: String? = null + + override fun run() { + val inlineVerify = + throwIfUnsupportedSubcommand(SopCLI.getSop().inlineVerify(), "inline-verify") + + throwIfOutputExists(verificationsOut) + + try { + inlineVerify.notAfter(parseNotAfter(notAfter)) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after") + throw UnsupportedOption(errorMsg, unsupportedOption) + } + + try { + inlineVerify.notBefore(parseNotBefore(notBefore)) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before") + throw UnsupportedOption(errorMsg, unsupportedOption) + } + + for (certInput in certificates) { + try { + getInput(certInput).use { certIn -> inlineVerify.cert(certIn) } + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } catch (unsupportedAsymmetricAlgo: UnsupportedAsymmetricAlgo) { + val errorMsg = + getMsg( + "sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm", certInput) + throw UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", certInput) + throw BadData(errorMsg, badData) + } + } + + val verifications = + try { + val ready = inlineVerify.data(System.`in`) + ready.writeTo(System.out) + } catch (e: NoSignature) { + val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found") + throw NoSignature(errorMsg, e) + } catch (ioException: IOException) { + throw RuntimeException(ioException) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.stdin_not_a_message") + throw BadData(errorMsg, badData) + } + + verificationsOut?.let { + try { + getOutput(it).use { outputStream -> + val pw = PrintWriter(outputStream) + for (verification in verifications) { + pw.println(verification) + } + pw.flush() + pw.close() + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + } +} From b251956f492268eadb7f1876e671a34b87c38b9e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:49:45 +0100 Subject: [PATCH 273/444] Delete unused Print class --- .../src/main/java/sop/cli/picocli/Print.java | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/Print.java diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java b/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java deleted file mode 100644 index 9e81b66..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/Print.java +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -public class Print { - - public static void outln(String string) { - // CHECKSTYLE:OFF - System.out.println(string); - // CHECKSTYLE:ON - } -} From 5c2695228b6e780d6cbcb9959c580732c76a3af2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:55:50 +0100 Subject: [PATCH 274/444] Kotlin conversion: SOPExceptionExitCodeMapper --- .../picocli/SOPExceptionExitCodeMapper.java | 34 ------------------- .../cli/picocli/SOPExceptionExitCodeMapper.kt | 31 +++++++++++++++++ 2 files changed, 31 insertions(+), 34 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/SOPExceptionExitCodeMapper.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExceptionExitCodeMapper.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExceptionExitCodeMapper.java deleted file mode 100644 index 8b38af3..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExceptionExitCodeMapper.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import picocli.CommandLine; -import sop.exception.SOPGPException; - -public class SOPExceptionExitCodeMapper implements CommandLine.IExitCodeExceptionMapper { - - @Override - public int getExitCode(Throwable exception) { - if (exception instanceof SOPGPException) { - return ((SOPGPException) exception).getExitCode(); - } - if (exception instanceof CommandLine.UnmatchedArgumentException) { - CommandLine.UnmatchedArgumentException ex = (CommandLine.UnmatchedArgumentException) exception; - // Unmatched option of subcommand (eg. `generate-key -k`) - if (ex.isUnknownOption()) { - return SOPGPException.UnsupportedOption.EXIT_CODE; - } - // Unmatched subcommand - return SOPGPException.UnsupportedSubcommand.EXIT_CODE; - } - // Invalid option (eg. `--label Invalid`) - if (exception instanceof CommandLine.ParameterException) { - return SOPGPException.UnsupportedOption.EXIT_CODE; - } - - // Others, like IOException etc. - return 1; - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt new file mode 100644 index 0000000..29aa77b --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli + +import picocli.CommandLine.* +import sop.exception.SOPGPException + +class SOPExceptionExitCodeMapper : IExitCodeExceptionMapper { + + override fun getExitCode(exception: Throwable): Int = + if (exception is SOPGPException) { + // SOPGPExceptions have well-defined exit code + exception.getExitCode() + } else if (exception is UnmatchedArgumentException) { + if (exception.isUnknownOption) { + // Unmatched option of subcommand (e.g. `generate-key --unknown`) + SOPGPException.UnsupportedOption.EXIT_CODE + } else { + // Unmatched subcommand + SOPGPException.UnsupportedSubcommand.EXIT_CODE + } + } else if (exception is ParameterException) { + // Invalid option (e.g. `--as invalid`) + SOPGPException.UnsupportedOption.EXIT_CODE + } else { + // Others, like IOException etc. + 1 + } +} From 0c2cf5cb19dec00e9c9de41ffb7307fd859fc113 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 11:59:11 +0100 Subject: [PATCH 275/444] Kotlin conversion: SOPExecutionExceptionHandler --- .../picocli/SOPExecutionExceptionHandler.java | 33 ------------------ .../picocli/SOPExecutionExceptionHandler.kt | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 33 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExecutionExceptionHandler.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java deleted file mode 100644 index f6906ff..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SOPExecutionExceptionHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import picocli.CommandLine; - -public class SOPExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler { - - @Override - public int handleExecutionException(Exception ex, CommandLine commandLine, CommandLine.ParseResult parseResult) { - - int exitCode = commandLine.getExitCodeExceptionMapper() != null ? - commandLine.getExitCodeExceptionMapper().getExitCode(ex) : - commandLine.getCommandSpec().exitCodeOnExecutionException(); - - CommandLine.Help.ColorScheme colorScheme = commandLine.getColorScheme(); - // CHECKSTYLE:OFF - if (ex.getMessage() != null) { - commandLine.getErr().println(colorScheme.errorText(ex.getMessage())); - } else { - commandLine.getErr().println(ex.getClass().getName()); - } - - if (SopCLI.stacktrace) { - ex.printStackTrace(commandLine.getErr()); - } - // CHECKSTYLE:ON - - return exitCode; - } -} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExecutionExceptionHandler.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExecutionExceptionHandler.kt new file mode 100644 index 0000000..52236d3 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExecutionExceptionHandler.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli + +import picocli.CommandLine +import picocli.CommandLine.IExecutionExceptionHandler + +class SOPExecutionExceptionHandler : IExecutionExceptionHandler { + override fun handleExecutionException( + ex: Exception, + commandLine: CommandLine, + parseResult: CommandLine.ParseResult + ): Int { + val exitCode = + if (commandLine.exitCodeExceptionMapper != null) + commandLine.exitCodeExceptionMapper.getExitCode(ex) + else commandLine.commandSpec.exitCodeOnExecutionException() + + val colorScheme = commandLine.colorScheme + if (ex.message != null) { + commandLine.getErr().println(colorScheme.errorText(ex.message)) + } else { + commandLine.getErr().println(ex.javaClass.getName()) + } + + if (SopCLI.stacktrace) { + ex.printStackTrace(commandLine.getErr()) + } + + return exitCode + } +} From baa44a6b1a4218a051ea8ee1045736224beea20f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 12:35:23 +0100 Subject: [PATCH 276/444] Kotlin conversion: SopCLI --- .../src/main/java/sop/cli/picocli/SopCLI.java | 129 ------------------ .../java/sop/cli/picocli/package-info.java | 8 -- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 113 +++++++++++++++ .../test/java/sop/cli/picocli/SOPTest.java | 2 +- 4 files changed, 114 insertions(+), 138 deletions(-) delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java delete mode 100644 sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java deleted file mode 100644 index 5420dea..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import picocli.AutoComplete; -import picocli.CommandLine; -import sop.SOP; -import sop.cli.picocli.commands.ArmorCmd; -import sop.cli.picocli.commands.ChangeKeyPasswordCmd; -import sop.cli.picocli.commands.DearmorCmd; -import sop.cli.picocli.commands.DecryptCmd; -import sop.cli.picocli.commands.InlineDetachCmd; -import sop.cli.picocli.commands.EncryptCmd; -import sop.cli.picocli.commands.ExtractCertCmd; -import sop.cli.picocli.commands.GenerateKeyCmd; -import sop.cli.picocli.commands.InlineSignCmd; -import sop.cli.picocli.commands.InlineVerifyCmd; -import sop.cli.picocli.commands.ListProfilesCmd; -import sop.cli.picocli.commands.RevokeKeyCmd; -import sop.cli.picocli.commands.SignCmd; -import sop.cli.picocli.commands.VerifyCmd; -import sop.cli.picocli.commands.VersionCmd; -import sop.exception.SOPGPException; - -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; - -@CommandLine.Command( - name = "sop", - resourceBundle = "msg_sop", - exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, - subcommands = { - // Meta Subcommands - VersionCmd.class, - ListProfilesCmd.class, - // Key and Certificate Management Subcommands - GenerateKeyCmd.class, - ChangeKeyPasswordCmd.class, - RevokeKeyCmd.class, - ExtractCertCmd.class, - // Messaging Subcommands - SignCmd.class, - VerifyCmd.class, - EncryptCmd.class, - DecryptCmd.class, - InlineDetachCmd.class, - InlineSignCmd.class, - InlineVerifyCmd.class, - // Transport Subcommands - ArmorCmd.class, - DearmorCmd.class, - // Miscellaneous Subcommands - CommandLine.HelpCommand.class, - AutoComplete.GenerateCompletion.class - } -) -public class SopCLI { - // Singleton - static SOP SOP_INSTANCE; - static ResourceBundle cliMsg = ResourceBundle.getBundle("msg_sop"); - - public static String EXECUTABLE_NAME = "sop"; - - @CommandLine.Option(names = {"--stacktrace"}, - scope = CommandLine.ScopeType.INHERIT) - static boolean stacktrace; - - public static void main(String[] args) { - int exitCode = execute(args); - if (exitCode != 0) { - System.exit(exitCode); - } - } - - public static int execute(String[] args) { - - // Set locale - new CommandLine(new InitLocale()).parseArgs(args); - - // get error message bundle - cliMsg = ResourceBundle.getBundle("msg_sop"); - - // Prepare CLI - CommandLine cmd = new CommandLine(SopCLI.class); - - // explicitly set help command resource bundle - cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("msg_help")); - - // Hide generate-completion command - cmd.getSubcommands().get("generate-completion").getCommandSpec().usageMessage().hidden(true); - - cmd.setCommandName(EXECUTABLE_NAME) - .setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) - .setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper()) - .setCaseInsensitiveEnumValuesAllowed(true); - - return cmd.execute(args); - } - - public static SOP getSop() { - if (SOP_INSTANCE == null) { - String errorMsg = cliMsg.getString("sop.error.runtime.no_backend_set"); - throw new IllegalStateException(errorMsg); - } - return SOP_INSTANCE; - } - - public static void setSopInstance(SOP instance) { - SOP_INSTANCE = instance; - } -} - -/** - * Control the locale. - * - * @see Picocli Readme - */ -class InitLocale { - @CommandLine.Option(names = { "-l", "--locale" }, descriptionKey = "sop.locale") - void setLocale(String locale) { - Locale.setDefault(new Locale(locale)); - } - - @CommandLine.Unmatched - List remainder; // ignore any other parameters and options in the first parsing phase -} diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java b/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java deleted file mode 100644 index 83f426d..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Implementation of the Stateless OpenPGP Command Line Interface using Picocli. - */ -package sop.cli.picocli; diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt new file mode 100644 index 0000000..1d5d46b --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli + +import java.util.* +import kotlin.system.exitProcess +import picocli.AutoComplete.GenerateCompletion +import picocli.CommandLine +import picocli.CommandLine.* +import sop.SOP +import sop.cli.picocli.commands.* +import sop.exception.SOPGPException + +@Command( + name = "sop", + resourceBundle = "msg_sop", + exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, + subcommands = + [ + // Meta subcommands + VersionCmd::class, + ListProfilesCmd::class, + // Key and certificate management + GenerateKeyCmd::class, + ChangeKeyPasswordCmd::class, + RevokeKeyCmd::class, + ExtractCertCmd::class, + // Messaging subcommands + SignCmd::class, + VerifyCmd::class, + EncryptCmd::class, + DecryptCmd::class, + InlineDetachCmd::class, + InlineSignCmd::class, + InlineVerifyCmd::class, + // Transport + ArmorCmd::class, + DearmorCmd::class, + // misc + HelpCommand::class, + GenerateCompletion::class]) +class SopCLI { + + companion object { + @JvmStatic private var sopInstance: SOP? = null + + @JvmStatic + fun getSop(): SOP = + checkNotNull(sopInstance) { cliMsg.getString("sop.error.runtime.no_backend_set") } + + @JvmStatic + fun setSopInstance(sop: SOP?) { + sopInstance = sop + } + + @JvmField var cliMsg: ResourceBundle = ResourceBundle.getBundle("msg_sop") + + @JvmField var EXECUTABLE_NAME = "sop" + + @JvmField + @Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) + var stacktrace = false + + @JvmStatic + fun main(vararg args: String) { + val exitCode = execute(*args) + if (exitCode != 0) { + exitProcess(exitCode) + } + } + + @JvmStatic + fun execute(vararg args: String): Int { + // Set locale + CommandLine(InitLocale()).parseArgs(*args) + + // Re-set bundle with updated locale + cliMsg = ResourceBundle.getBundle("msg_sop") + + return CommandLine(SopCLI::class.java) + .apply { + // explicitly set help command resource bundle + subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) + // Hide generate-completion command + subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) + // overwrite executable name + commandName = EXECUTABLE_NAME + // setup exception handling + executionExceptionHandler = SOPExecutionExceptionHandler() + exitCodeExceptionMapper = SOPExceptionExitCodeMapper() + isCaseInsensitiveEnumValuesAllowed = true + } + .execute(*args) + } + } + + /** + * Control the locale. + * + * @see Picocli Readme + */ + @Command + class InitLocale { + @Option(names = ["-l", "--locale"], descriptionKey = "sop.locale") + fun setLocale(locale: String) = Locale.setDefault(Locale(locale)) + + @Unmatched + var remainder: MutableList = + mutableListOf() // ignore any other parameters and options in the first parsing phase + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 47b6123..68b32be 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -45,7 +45,7 @@ public class SOPTest { @Test @ExpectSystemExitWithStatus(1) public void assertThrowsIfNoSOPBackendSet() { - SopCLI.SOP_INSTANCE = null; + SopCLI.setSopInstance(null); // At this point, no SOP backend is set, so an InvalidStateException triggers exit(1) SopCLI.main(new String[] {"armor"}); } From edef89907410621cbfa89553d3c3d93167096dc6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 12:42:41 +0100 Subject: [PATCH 277/444] Fix GenerateKey --with-key-password option name --- .../src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt index fb5e321..7fa5a70 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/GenerateKeyCmd.kt @@ -20,7 +20,7 @@ class GenerateKeyCmd : AbstractSopCmd() { @Parameters(paramLabel = "USERID") var userId: List = listOf() - @Option(names = ["---with-key-password"], paramLabel = "PASSWORD") + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: String? = null @Option(names = ["--profile"], paramLabel = "PROFILE") var profile: String? = null From 41acdfe03a9ee0b8e6732402ae58145220764218 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 12:42:56 +0100 Subject: [PATCH 278/444] ProxyOutputStream: Extend OutputStream --- .../src/main/kotlin/sop/util/ProxyOutputStream.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt index 142f7b3..da6c4fa 100644 --- a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt +++ b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt @@ -15,7 +15,7 @@ import java.io.OutputStream * class is useful if we need to provide an [OutputStream] at one point in time when the final * target output stream is not yet known. */ -class ProxyOutputStream { +class ProxyOutputStream : OutputStream() { private val buffer = ByteArrayOutputStream() private var swapped: OutputStream? = null @@ -27,7 +27,7 @@ class ProxyOutputStream { @Synchronized @Throws(IOException::class) - fun write(b: ByteArray) { + override fun write(b: ByteArray) { if (swapped == null) { buffer.write(b) } else { @@ -37,7 +37,7 @@ class ProxyOutputStream { @Synchronized @Throws(IOException::class) - fun write(b: ByteArray, off: Int, len: Int) { + override fun write(b: ByteArray, off: Int, len: Int) { if (swapped == null) { buffer.write(b, off, len) } else { @@ -47,7 +47,7 @@ class ProxyOutputStream { @Synchronized @Throws(IOException::class) - fun flush() { + override fun flush() { buffer.flush() if (swapped != null) { swapped!!.flush() @@ -56,7 +56,7 @@ class ProxyOutputStream { @Synchronized @Throws(IOException::class) - fun close() { + override fun close() { buffer.close() if (swapped != null) { swapped!!.close() @@ -65,7 +65,7 @@ class ProxyOutputStream { @Synchronized @Throws(IOException::class) - fun write(i: Int) { + override fun write(i: Int) { if (swapped == null) { buffer.write(i) } else { From 0563105b1f071389c431cb6e05df1283366163c3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 13:38:02 +0100 Subject: [PATCH 279/444] Bump version to 8.0.0-SNAPSHOT --- README.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6dd9a81..fa7e5a5 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 7](https://img.shields.io/badge/Spec%20Revision-7-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/07/) +[![Spec Revision: 8](https://img.shields.io/badge/Spec%20Revision-8-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/08/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) diff --git a/version.gradle b/version.gradle index d8ddfd6..c5a9cb1 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '7.0.1' + shortVersion = '8.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From 2051c3632a3fd8a5fbd2438a8a5a2b91c2819878 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 13:52:36 +0100 Subject: [PATCH 280/444] external-sop: Mark methods with @Nonnull where applicable --- .../main/java/sop/external/ExternalSOP.java | 19 +++++++++++-- .../sop/external/operation/ArmorExternal.java | 8 ++++-- .../operation/ChangeKeyPasswordExternal.java | 11 +++++--- .../external/operation/DearmorExternal.java | 4 ++- .../external/operation/DecryptExternal.java | 27 ++++++++++++------- .../operation/DetachedSignExternal.java | 16 +++++++---- .../operation/DetachedVerifyExternal.java | 16 +++++++---- .../external/operation/EncryptExternal.java | 25 +++++++++++------ .../operation/ExtractCertExternal.java | 5 +++- .../operation/GenerateKeyExternal.java | 13 ++++++--- .../operation/InlineDetachExternal.java | 9 ++++--- .../operation/InlineSignExternal.java | 14 +++++++--- .../operation/InlineVerifyExternal.java | 15 +++++++---- .../operation/ListProfilesExternal.java | 4 ++- .../external/operation/RevokeKeyExternal.java | 8 ++++-- .../external/operation/VersionExternal.java | 6 +++++ 16 files changed, 146 insertions(+), 54 deletions(-) diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java index e1479ee..3819848 100644 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ b/external-sop/src/main/java/sop/external/ExternalSOP.java @@ -106,76 +106,91 @@ public class ExternalSOP implements SOP { } @Override + @Nonnull public Version version() { return new VersionExternal(binaryName, properties); } @Override + @Nonnull public GenerateKey generateKey() { return new GenerateKeyExternal(binaryName, properties); } @Override + @Nonnull public ExtractCert extractCert() { return new ExtractCertExternal(binaryName, properties); } @Override + @Nonnull public DetachedSign detachedSign() { return new DetachedSignExternal(binaryName, properties, tempDirProvider); } @Override + @Nonnull public InlineSign inlineSign() { return new InlineSignExternal(binaryName, properties); } @Override + @Nonnull public DetachedVerify detachedVerify() { return new DetachedVerifyExternal(binaryName, properties); } @Override + @Nonnull public InlineVerify inlineVerify() { return new InlineVerifyExternal(binaryName, properties, tempDirProvider); } @Override + @Nonnull public InlineDetach inlineDetach() { return new InlineDetachExternal(binaryName, properties, tempDirProvider); } @Override + @Nonnull public Encrypt encrypt() { return new EncryptExternal(binaryName, properties, tempDirProvider); } @Override + @Nonnull public Decrypt decrypt() { return new DecryptExternal(binaryName, properties, tempDirProvider); } @Override + @Nonnull public Armor armor() { return new ArmorExternal(binaryName, properties); } @Override + @Nonnull public ListProfiles listProfiles() { return new ListProfilesExternal(binaryName, properties); } @Override + @Nonnull public RevokeKey revokeKey() { return new RevokeKeyExternal(binaryName, properties); } @Override + @Nonnull public ChangeKeyPassword changeKeyPassword() { return new ChangeKeyPasswordExternal(binaryName, properties); } @Override + @Nonnull public Dearmor dearmor() { return new DearmorExternal(binaryName, properties); } @@ -349,7 +364,7 @@ public class ExternalSOP implements SOP { return new Ready() { @Override - public void writeTo(OutputStream outputStream) throws IOException { + public void writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = stdIn.read(buf)) >= 0) { @@ -388,7 +403,7 @@ public class ExternalSOP implements SOP { return new Ready() { @Override - public void writeTo(OutputStream outputStream) throws IOException { + public void writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = standardIn.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java index 4df7fca..e1d02e9 100644 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java @@ -10,6 +10,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Armor; +import javax.annotation.Nonnull; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -30,13 +31,16 @@ public class ArmorExternal implements Armor { } @Override - public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption { + @Deprecated + @Nonnull + public Armor label(@Nonnull ArmorLabel label) throws SOPGPException.UnsupportedOption { commandList.add("--label=" + label); return this; } @Override - public Ready data(InputStream data) throws SOPGPException.BadData { + @Nonnull + public Ready data(@Nonnull InputStream data) throws SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java b/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java index 210152f..d53d6a5 100644 --- a/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java @@ -9,6 +9,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.ChangeKeyPassword; +import javax.annotation.Nonnull; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -27,13 +28,15 @@ public class ChangeKeyPasswordExternal implements ChangeKeyPassword { } @Override + @Nonnull public ChangeKeyPassword noArmor() { this.commandList.add("--no-armor"); return this; } @Override - public ChangeKeyPassword oldKeyPassphrase(String oldPassphrase) { + @Nonnull + public ChangeKeyPassword oldKeyPassphrase(@Nonnull String oldPassphrase) { this.commandList.add("--old-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + oldPassphrase); keyPasswordCounter++; @@ -42,7 +45,8 @@ public class ChangeKeyPasswordExternal implements ChangeKeyPassword { } @Override - public ChangeKeyPassword newKeyPassphrase(String newPassphrase) { + @Nonnull + public ChangeKeyPassword newKeyPassphrase(@Nonnull String newPassphrase) { this.commandList.add("--new-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + newPassphrase); keyPasswordCounter++; @@ -51,7 +55,8 @@ public class ChangeKeyPasswordExternal implements ChangeKeyPassword { } @Override - public Ready keys(InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { + @Nonnull + public Ready keys(@Nonnull InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, inputStream); } } diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java index bedf018..cd3da6f 100644 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java @@ -9,6 +9,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Dearmor; +import javax.annotation.Nonnull; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -29,7 +30,8 @@ public class DearmorExternal implements Dearmor { } @Override - public Ready data(InputStream data) throws SOPGPException.BadData { + @Nonnull + public Ready data(@Nonnull InputStream data) throws SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java index 0b91d5b..a1c4016 100644 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java @@ -13,6 +13,7 @@ import sop.external.ExternalSOP; import sop.operation.Decrypt; import sop.util.UTCUtil; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -48,21 +49,24 @@ public class DecryptExternal implements Decrypt { } @Override - public Decrypt verifyNotBefore(Date timestamp) + @Nonnull + public Decrypt verifyNotBefore(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { this.commandList.add("--verify-not-before=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public Decrypt verifyNotAfter(Date timestamp) + @Nonnull + public Decrypt verifyNotAfter(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { this.commandList.add("--verify-not-after=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public Decrypt verifyWithCert(InputStream cert) + @Nonnull + public Decrypt verifyWithCert(@Nonnull InputStream cert) throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "VERIFY_WITH_" + verifyWithCounter++; commandList.add("--verify-with=@ENV:" + envVar); @@ -71,7 +75,8 @@ public class DecryptExternal implements Decrypt { } @Override - public Decrypt withSessionKey(SessionKey sessionKey) + @Nonnull + public Decrypt withSessionKey(@Nonnull SessionKey sessionKey) throws SOPGPException.UnsupportedOption { String envVar = "SESSION_KEY_" + withSessionKeyCounter++; commandList.add("--with-session-key=@ENV:" + envVar); @@ -80,7 +85,8 @@ public class DecryptExternal implements Decrypt { } @Override - public Decrypt withPassword(String password) + @Nonnull + public Decrypt withPassword(@Nonnull String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { String envVar = "PASSWORD_" + withPasswordCounter++; commandList.add("--with-password=@ENV:" + envVar); @@ -89,7 +95,8 @@ public class DecryptExternal implements Decrypt { } @Override - public Decrypt withKey(InputStream key) + @Nonnull + public Decrypt withKey(@Nonnull InputStream key) throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); @@ -98,7 +105,8 @@ public class DecryptExternal implements Decrypt { } @Override - public Decrypt withKeyPassword(byte[] password) + @Nonnull + public Decrypt withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; commandList.add("--with-key-password=@ENV:" + envVar); @@ -107,7 +115,8 @@ public class DecryptExternal implements Decrypt { } @Override - public ReadyWithResult ciphertext(InputStream ciphertext) + @Nonnull + public ReadyWithResult ciphertext(@Nonnull InputStream ciphertext) throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, SOPGPException.KeyIsProtected, IOException { File tempDir = tempDirProvider.provideTempDirectory(); @@ -131,7 +140,7 @@ public class DecryptExternal implements Decrypt { return new ReadyWithResult() { @Override - public DecryptionResult writeTo(OutputStream outputStream) throws IOException { + public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = ciphertext.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java index 7c579d4..2ef2714 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java @@ -12,6 +12,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.DetachedSign; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -43,13 +44,15 @@ public class DetachedSignExternal implements DetachedSign { } @Override + @Nonnull public DetachedSign noArmor() { commandList.add("--no-armor"); return this; } @Override - public DetachedSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + @Nonnull + public DetachedSign key(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); envList.add(envVar + "=" + ExternalSOP.readString(key)); @@ -57,7 +60,8 @@ public class DetachedSignExternal implements DetachedSign { } @Override - public DetachedSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + @Nonnull + public DetachedSign withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; commandList.add("--with-key-password=@ENV:" + envVar); envList.add(envVar + "=" + new String(password)); @@ -65,13 +69,15 @@ public class DetachedSignExternal implements DetachedSign { } @Override - public DetachedSign mode(SignAs mode) throws SOPGPException.UnsupportedOption { + @Nonnull + public DetachedSign mode(@Nonnull SignAs mode) throws SOPGPException.UnsupportedOption { commandList.add("--as=" + mode); return this; } @Override - public ReadyWithResult data(InputStream data) + @Nonnull + public ReadyWithResult data(@Nonnull InputStream data) throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { File tempDir = tempDirProvider.provideTempDirectory(); @@ -88,7 +94,7 @@ public class DetachedSignExternal implements DetachedSign { return new ReadyWithResult() { @Override - public SigningResult writeTo(OutputStream outputStream) throws IOException { + public SigningResult writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = data.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java index 2f19c5b..866150d 100644 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java @@ -11,6 +11,7 @@ import sop.operation.DetachedVerify; import sop.operation.VerifySignatures; import sop.util.UTCUtil; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -42,31 +43,36 @@ public class DetachedVerifyExternal implements DetachedVerify { } @Override - public DetachedVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + @Nonnull + public DetachedVerify notBefore(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public DetachedVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + @Nonnull + public DetachedVerify notAfter(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public DetachedVerify cert(InputStream cert) throws SOPGPException.BadData { + @Nonnull + public DetachedVerify cert(@Nonnull InputStream cert) throws SOPGPException.BadData { this.certs.add(cert); return this; } @Override - public VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData { + @Nonnull + public VerifySignatures signatures(@Nonnull InputStream signatures) throws SOPGPException.BadData { this.signatures = signatures; return this; } @Override - public List data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + @Nonnull + public List data(@Nonnull InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { commandList.add("@ENV:SIGNATURE"); envList.add("SIGNATURE=" + ExternalSOP.readString(signatures)); diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java index 3eabfc5..f41a36e 100644 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java @@ -12,6 +12,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.Encrypt; +import javax.annotation.Nonnull; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -42,20 +43,23 @@ public class EncryptExternal implements Encrypt { } @Override + @Nonnull public Encrypt noArmor() { this.commandList.add("--no-armor"); return this; } @Override - public Encrypt mode(EncryptAs mode) + @Nonnull + public Encrypt mode(@Nonnull EncryptAs mode) throws SOPGPException.UnsupportedOption { this.commandList.add("--as=" + mode); return this; } @Override - public Encrypt signWith(InputStream key) + @Nonnull + public Encrypt signWith(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { String envVar = "SIGN_WITH_" + SIGN_WITH_COUNTER++; @@ -65,7 +69,8 @@ public class EncryptExternal implements Encrypt { } @Override - public Encrypt withKeyPassword(byte[] password) + @Nonnull + public Encrypt withKeyPassword(@Nonnull byte[] password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { String envVar = "KEY_PASSWORD_" + KEY_PASSWORD_COUNTER++; commandList.add("--with-key-password=@ENV:" + envVar); @@ -74,7 +79,8 @@ public class EncryptExternal implements Encrypt { } @Override - public Encrypt withPassword(String password) + @Nonnull + public Encrypt withPassword(@Nonnull String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { String envVar = "PASSWORD_" + PASSWORD_COUNTER++; commandList.add("--with-password=@ENV:" + envVar); @@ -83,7 +89,8 @@ public class EncryptExternal implements Encrypt { } @Override - public Encrypt withCert(InputStream cert) + @Nonnull + public Encrypt withCert(@Nonnull InputStream cert) throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { String envVar = "CERT_" + CERT_COUNTER++; @@ -93,13 +100,15 @@ public class EncryptExternal implements Encrypt { } @Override - public Encrypt profile(String profileName) { + @Nonnull + public Encrypt profile(@Nonnull String profileName) { commandList.add("--profile=" + profileName); return this; } @Override - public ReadyWithResult plaintext(InputStream plaintext) + @Nonnull + public ReadyWithResult plaintext(@Nonnull InputStream plaintext) throws SOPGPException.KeyIsProtected, IOException { File tempDir = tempDirProvider.provideTempDirectory(); @@ -116,7 +125,7 @@ public class EncryptExternal implements Encrypt { return new ReadyWithResult() { @Override - public EncryptionResult writeTo(OutputStream outputStream) throws IOException { + public EncryptionResult writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = plaintext.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java index 5fdcdc1..538359a 100644 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java @@ -9,6 +9,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.ExtractCert; +import javax.annotation.Nonnull; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -29,13 +30,15 @@ public class ExtractCertExternal implements ExtractCert { } @Override + @Nonnull public ExtractCert noArmor() { this.commandList.add("--no-armor"); return this; } @Override - public Ready key(InputStream keyInputStream) throws SOPGPException.BadData { + @Nonnull + public Ready key(@Nonnull InputStream keyInputStream) throws SOPGPException.BadData { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keyInputStream); } } diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java index c46dfb3..a8244cb 100644 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java @@ -9,6 +9,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.GenerateKey; +import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -30,19 +31,22 @@ public class GenerateKeyExternal implements GenerateKey { } @Override + @Nonnull public GenerateKey noArmor() { this.commandList.add("--no-armor"); return this; } @Override - public GenerateKey userId(String userId) { + @Nonnull + public GenerateKey userId(@Nonnull String userId) { this.commandList.add(userId); return this; } @Override - public GenerateKey withKeyPassword(String password) + @Nonnull + public GenerateKey withKeyPassword(@Nonnull String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { this.commandList.add("--with-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + password); @@ -52,18 +56,21 @@ public class GenerateKeyExternal implements GenerateKey { } @Override - public GenerateKey profile(String profile) { + @Nonnull + public GenerateKey profile(@Nonnull String profile) { commandList.add("--profile=" + profile); return this; } @Override + @Nonnull public GenerateKey signingOnly() { commandList.add("--signing-only"); return this; } @Override + @Nonnull public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { return ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList); diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java index c03fe1b..a798006 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java @@ -10,6 +10,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.InlineDetach; +import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; @@ -37,13 +38,15 @@ public class InlineDetachExternal implements InlineDetach { } @Override + @Nonnull public InlineDetach noArmor() { commandList.add("--no-armor"); return this; } @Override - public ReadyWithResult message(InputStream messageInputStream) throws IOException, SOPGPException.BadData { + @Nonnull + public ReadyWithResult message(@Nonnull InputStream messageInputStream) throws IOException, SOPGPException.BadData { File tempDir = tempDirProvider.provideTempDirectory(); File signaturesOut = new File(tempDir, "signatures"); @@ -60,7 +63,7 @@ public class InlineDetachExternal implements InlineDetach { return new ReadyWithResult() { @Override - public Signatures writeTo(OutputStream outputStream) throws IOException { + public Signatures writeTo(@Nonnull OutputStream outputStream) throws IOException { byte[] buf = new byte[4096]; int r; while ((r = messageInputStream.read(buf)) > 0) { @@ -90,7 +93,7 @@ public class InlineDetachExternal implements InlineDetach { final byte[] sigBytes = signaturesBuffer.toByteArray(); return new Signatures() { @Override - public void writeTo(OutputStream signatureOutputStream) throws IOException { + public void writeTo(@Nonnull OutputStream signatureOutputStream) throws IOException { signatureOutputStream.write(sigBytes); } }; diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java index 68a630d..1a86002 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java @@ -10,6 +10,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.InlineSign; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -34,13 +35,15 @@ public class InlineSignExternal implements InlineSign { } @Override + @Nonnull public InlineSign noArmor() { commandList.add("--no-armor"); return this; } @Override - public InlineSign key(InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { + @Nonnull + public InlineSign key(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { String envVar = "KEY_" + keyCounter++; commandList.add("@ENV:" + envVar); envList.add(envVar + "=" + ExternalSOP.readString(key)); @@ -48,7 +51,8 @@ public class InlineSignExternal implements InlineSign { } @Override - public InlineSign withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + @Nonnull + public InlineSign withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; commandList.add("--with-key-password=@ENV:" + envVar); envList.add(envVar + "=" + new String(password)); @@ -56,13 +60,15 @@ public class InlineSignExternal implements InlineSign { } @Override - public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { + @Nonnull + public InlineSign mode(@Nonnull InlineSignAs mode) throws SOPGPException.UnsupportedOption { commandList.add("--as=" + mode); return this; } @Override - public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { + @Nonnull + public Ready data(@Nonnull InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); } } diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java index 8010367..10a2d47 100644 --- a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java @@ -11,6 +11,7 @@ import sop.external.ExternalSOP; import sop.operation.InlineVerify; import sop.util.UTCUtil; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -42,19 +43,22 @@ public class InlineVerifyExternal implements InlineVerify { } @Override - public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { + @Nonnull + public InlineVerify notBefore(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { + @Nonnull + public InlineVerify notAfter(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); return this; } @Override - public InlineVerify cert(InputStream cert) throws SOPGPException.BadData, IOException { + @Nonnull + public InlineVerify cert(@Nonnull InputStream cert) throws SOPGPException.BadData, IOException { String envVar = "CERT_" + certCounter++; commandList.add("@ENV:" + envVar); envList.add(envVar + "=" + ExternalSOP.readString(cert)); @@ -62,7 +66,8 @@ public class InlineVerifyExternal implements InlineVerify { } @Override - public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + @Nonnull + public ReadyWithResult> data(@Nonnull InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { File tempDir = tempDirProvider.provideTempDirectory(); File verificationsOut = new File(tempDir, "verifications-out"); @@ -79,7 +84,7 @@ public class InlineVerifyExternal implements InlineVerify { return new ReadyWithResult>() { @Override - public List writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { + public List writeTo(@Nonnull OutputStream outputStream) throws IOException, SOPGPException.NoSignature { byte[] buf = new byte[4096]; int r; while ((r = data.read(buf)) > 0) { diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java index 0c76b63..21d5e13 100644 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java @@ -8,6 +8,7 @@ import sop.Profile; import sop.external.ExternalSOP; import sop.operation.ListProfiles; +import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -25,7 +26,8 @@ public class ListProfilesExternal implements ListProfiles { } @Override - public List subcommand(String command) { + @Nonnull + public List subcommand(@Nonnull String command) { commandList.add(command); try { String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); diff --git a/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java b/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java index e7aad9d..72ed549 100644 --- a/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java +++ b/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java @@ -9,6 +9,7 @@ import sop.exception.SOPGPException; import sop.external.ExternalSOP; import sop.operation.RevokeKey; +import javax.annotation.Nonnull; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -28,13 +29,15 @@ public class RevokeKeyExternal implements RevokeKey { } @Override + @Nonnull public RevokeKey noArmor() { this.commandList.add("--no-armor"); return this; } @Override - public RevokeKey withKeyPassword(byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { + @Nonnull + public RevokeKey withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; commandList.add("--with-key-password=@ENV:" + envVar); envList.add(envVar + "=" + new String(password)); @@ -42,7 +45,8 @@ public class RevokeKeyExternal implements RevokeKey { } @Override - public Ready keys(InputStream keys) { + @Nonnull + public Ready keys(@Nonnull InputStream keys) { return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keys); } } diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java index 0b9c5b4..ab2bbef 100644 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ b/external-sop/src/main/java/sop/external/operation/VersionExternal.java @@ -7,6 +7,7 @@ package sop.external.operation; import sop.external.ExternalSOP; import sop.operation.Version; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -27,6 +28,7 @@ public class VersionExternal implements Version { } @Override + @Nonnull public String getName() { String[] command = new String[] {binary, "version"}; String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); @@ -45,6 +47,7 @@ public class VersionExternal implements Version { } @Override + @Nonnull public String getVersion() { String[] command = new String[] {binary, "version"}; String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); @@ -63,6 +66,7 @@ public class VersionExternal implements Version { } @Override + @Nonnull public String getBackendVersion() { String[] command = new String[] {binary, "version", "--backend"}; String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); @@ -82,6 +86,7 @@ public class VersionExternal implements Version { } @Override + @Nonnull public String getExtendedVersion() { String[] command = new String[] {binary, "version", "--extended"}; String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); @@ -137,6 +142,7 @@ public class VersionExternal implements Version { } @Override + @Nonnull public String getSopSpecVersion() { String[] command = new String[] {binary, "version", "--sop-spec"}; String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); From 3dde1748805f4c665fc29fb6ab816a8f490eb464 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 14:26:24 +0100 Subject: [PATCH 281/444] Fix woodpecker pipeline --- .woodpecker/.build.yml | 6 +++--- .woodpecker/.reuse.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index d72d19e..ff59c4e 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -1,6 +1,6 @@ -pipeline: +steps: run: - image: gradle:7.5-jdk8-jammy + image: gradle:7.6-jdk11-jammy commands: # Install Sequoia-SOP - apt update && apt install --yes sqop @@ -14,4 +14,4 @@ pipeline: - gradle check javadocAll # Code has coverage - gradle jacocoRootReport coveralls - secrets: [COVERALLS_REPO_TOKEN] + secrets: [coveralls_repo_token] diff --git a/.woodpecker/.reuse.yml b/.woodpecker/.reuse.yml index 58f17e6..d78c61e 100644 --- a/.woodpecker/.reuse.yml +++ b/.woodpecker/.reuse.yml @@ -1,6 +1,6 @@ # Code is licensed properly # See https://reuse.software/ -pipeline: +steps: reuse: image: fsfe/reuse:latest commands: From 03cabdf3fb0d0ac25c849480a47d5e255d219644 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 15:22:40 +0100 Subject: [PATCH 282/444] Add tests for --no-armor for change-key-password and revoke-key --- .../operation/ChangeKeyPasswordTest.java | 26 +++++++++++++++++++ .../testsuite/operation/RevokeKeyTest.java | 9 +++++++ 2 files changed, 35 insertions(+) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java index b575047..8948dda 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; import sop.exception.SOPGPException; +import sop.testsuite.JUtils; +import sop.testsuite.TestData; import sop.util.UTF8Util; import java.io.IOException; @@ -17,6 +19,7 @@ import java.nio.charset.StandardCharsets; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") @@ -95,4 +98,27 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { sop.changeKeyPassword().newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testNoArmor(SOP sop) throws IOException { + byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); + byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); + byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); + + byte[] armored = sop.changeKeyPassword() + .oldKeyPassphrase(oldPassword) + .newKeyPassphrase(newPassword) + .keys(protectedKey) + .getBytes(); + JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + + byte[] unarmored = sop.changeKeyPassword() + .noArmor() + .oldKeyPassphrase(oldPassword) + .newKeyPassphrase(newPassword) + .keys(protectedKey) + .getBytes(); + assertFalse(JUtils.arrayStartsWith(unarmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); + } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java index 6595133..cb51332 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java @@ -43,6 +43,15 @@ public class RevokeKeyTest extends AbstractSOPTest { assertFalse(Arrays.equals(secretKey, revocation)); } + @ParameterizedTest + @MethodSource("provideInstances") + public void revokeUnprotectedKeyNoArmor(SOP sop) throws IOException { + byte[] secretKey = sop.generateKey().userId("Alice ").generate().getBytes(); + byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); + + assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); + } + @ParameterizedTest @MethodSource("provideInstances") public void revokeUnprotectedKeyUnarmored(SOP sop) throws IOException { From 802bc0aa73a4357912646b8e6cd9e2b0134cd021 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 15:24:09 +0100 Subject: [PATCH 283/444] ArmorCMD: Drop --label option --- .../kotlin/sop/cli/picocli/commands/ArmorCmd.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt index ccfba4d..50716f1 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ArmorCmd.kt @@ -6,9 +6,7 @@ package sop.cli.picocli.commands import java.io.IOException import picocli.CommandLine.Command -import picocli.CommandLine.Option import sop.cli.picocli.SopCLI -import sop.enums.ArmorLabel import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.UnsupportedOption @@ -18,21 +16,9 @@ import sop.exception.SOPGPException.UnsupportedOption exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) class ArmorCmd : AbstractSopCmd() { - @Option(names = ["--label"], paramLabel = "{auto|sig|key|cert|message}") - var label: ArmorLabel? = null - override fun run() { val armor = throwIfUnsupportedSubcommand(SopCLI.getSop().armor(), "armor") - label?.let { - try { - armor.label(it) - } catch (unsupported: UnsupportedOption) { - val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--label") - throw UnsupportedOption(errorMsg, unsupported) - } - } - try { val ready = armor.data(System.`in`) ready.writeTo(System.out) From d24ff9cbde0520437f308ed61369294f81484a7c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 15:26:06 +0100 Subject: [PATCH 284/444] Remove label related CLI tests --- .../cli/picocli/commands/ArmorCmdTest.java | 53 +++++-------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java index 6bdbe7f..da211e0 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java @@ -4,17 +4,6 @@ package sop.cli.picocli.commands; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import com.ginsberg.junit.exit.FailOnSystemExit; import org.junit.jupiter.api.BeforeEach; @@ -22,10 +11,20 @@ import org.junit.jupiter.api.Test; import sop.Ready; import sop.SOP; import sop.cli.picocli.SopCLI; -import sop.enums.ArmorLabel; import sop.exception.SOPGPException; import sop.operation.Armor; +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + public class ArmorCmdTest { private Armor armor; @@ -41,40 +40,12 @@ public class ArmorCmdTest { SopCLI.setSopInstance(sop); } - @Test - public void assertLabelIsNotCalledByDefault() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"armor"}); - verify(armor, never()).label(any()); - } - - @Test - public void assertLabelIsCalledWhenFlaggedWithArgument() throws SOPGPException.UnsupportedOption { - for (ArmorLabel label : ArmorLabel.values()) { - SopCLI.main(new String[] {"armor", "--label", label.name()}); - verify(armor, times(1)).label(label); - } - } - @Test public void assertDataIsAlwaysCalled() throws SOPGPException.BadData, IOException { SopCLI.main(new String[] {"armor"}); verify(armor, times(1)).data((InputStream) any()); } - @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) - public void assertThrowsForInvalidLabel() { - SopCLI.main(new String[] {"armor", "--label", "Invalid"}); - } - - @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) - public void ifLabelsUnsupportedExit37() throws SOPGPException.UnsupportedOption { - when(armor.label(any())).thenThrow(new SOPGPException.UnsupportedOption("Custom Armor labels are not supported.")); - - SopCLI.main(new String[] {"armor", "--label", "Sig"}); - } - @Test @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void ifBadDataExit41() throws SOPGPException.BadData, IOException { @@ -94,7 +65,7 @@ public class ArmorCmdTest { private static Ready nopReady() { return new Ready() { @Override - public void writeTo(OutputStream outputStream) { + public void writeTo(@Nonnull OutputStream outputStream) { } }; } From 1c0666b4e13a0199c5889fd422905a4af9059134 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 16:27:19 +0100 Subject: [PATCH 285/444] Kotlin conversion: ExternalSOP --- .../main/java/sop/external/ExternalSOP.java | 469 ------------------ .../main/kotlin/sop/external/ExternalSOP.kt | 318 ++++++++++++ 2 files changed, 318 insertions(+), 469 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/ExternalSOP.java create mode 100644 external-sop/src/main/kotlin/sop/external/ExternalSOP.kt diff --git a/external-sop/src/main/java/sop/external/ExternalSOP.java b/external-sop/src/main/java/sop/external/ExternalSOP.java deleted file mode 100644 index 3819848..0000000 --- a/external-sop/src/main/java/sop/external/ExternalSOP.java +++ /dev/null @@ -1,469 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external; - -import sop.Ready; -import sop.SOP; -import sop.exception.SOPGPException; -import sop.external.operation.ArmorExternal; -import sop.external.operation.ChangeKeyPasswordExternal; -import sop.external.operation.DearmorExternal; -import sop.external.operation.DecryptExternal; -import sop.external.operation.DetachedSignExternal; -import sop.external.operation.DetachedVerifyExternal; -import sop.external.operation.EncryptExternal; -import sop.external.operation.ExtractCertExternal; -import sop.external.operation.GenerateKeyExternal; -import sop.external.operation.InlineDetachExternal; -import sop.external.operation.InlineSignExternal; -import sop.external.operation.InlineVerifyExternal; -import sop.external.operation.ListProfilesExternal; -import sop.external.operation.RevokeKeyExternal; -import sop.external.operation.VersionExternal; -import sop.operation.Armor; -import sop.operation.ChangeKeyPassword; -import sop.operation.Dearmor; -import sop.operation.Decrypt; -import sop.operation.DetachedSign; -import sop.operation.DetachedVerify; -import sop.operation.Encrypt; -import sop.operation.ExtractCert; -import sop.operation.GenerateKey; -import sop.operation.InlineDetach; -import sop.operation.InlineSign; -import sop.operation.InlineVerify; -import sop.operation.ListProfiles; -import sop.operation.RevokeKey; -import sop.operation.Version; - -import javax.annotation.Nonnull; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.attribute.FileAttribute; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link SOP} API using an external SOP binary. - */ -public class ExternalSOP implements SOP { - - private final String binaryName; - private final Properties properties; - private final TempDirProvider tempDirProvider; - - /** - * Instantiate an {@link ExternalSOP} object for the given binary and pass it empty environment variables, - * as well as a default {@link TempDirProvider}. - * - * @param binaryName name / path of the SOP binary - */ - public ExternalSOP(@Nonnull String binaryName) { - this(binaryName, new Properties()); - } - - /** - * Instantiate an {@link ExternalSOP} object for the given binary, and pass it the given properties as - * environment variables, as well as a default {@link TempDirProvider}. - * - * @param binaryName name / path of the SOP binary - * @param properties environment variables - */ - public ExternalSOP(@Nonnull String binaryName, @Nonnull Properties properties) { - this(binaryName, properties, defaultTempDirProvider()); - } - - /** - * Instantiate an {@link ExternalSOP} object for the given binary and the given {@link TempDirProvider} - * using empty environment variables. - * - * @param binaryName name / path of the SOP binary - * @param tempDirProvider custom tempDirProvider - */ - public ExternalSOP(@Nonnull String binaryName, @Nonnull TempDirProvider tempDirProvider) { - this(binaryName, new Properties(), tempDirProvider); - } - - /** - * Instantiate an {@link ExternalSOP} object for the given binary using the given properties and - * custom {@link TempDirProvider}. - * - * @param binaryName name / path of the SOP binary - * @param properties environment variables - * @param tempDirProvider tempDirProvider - */ - public ExternalSOP(@Nonnull String binaryName, @Nonnull Properties properties, @Nonnull TempDirProvider tempDirProvider) { - this.binaryName = binaryName; - this.properties = properties; - this.tempDirProvider = tempDirProvider; - } - - @Override - @Nonnull - public Version version() { - return new VersionExternal(binaryName, properties); - } - - @Override - @Nonnull - public GenerateKey generateKey() { - return new GenerateKeyExternal(binaryName, properties); - } - - @Override - @Nonnull - public ExtractCert extractCert() { - return new ExtractCertExternal(binaryName, properties); - } - - @Override - @Nonnull - public DetachedSign detachedSign() { - return new DetachedSignExternal(binaryName, properties, tempDirProvider); - } - - @Override - @Nonnull - public InlineSign inlineSign() { - return new InlineSignExternal(binaryName, properties); - } - - @Override - @Nonnull - public DetachedVerify detachedVerify() { - return new DetachedVerifyExternal(binaryName, properties); - } - - @Override - @Nonnull - public InlineVerify inlineVerify() { - return new InlineVerifyExternal(binaryName, properties, tempDirProvider); - } - - @Override - @Nonnull - public InlineDetach inlineDetach() { - return new InlineDetachExternal(binaryName, properties, tempDirProvider); - } - - @Override - @Nonnull - public Encrypt encrypt() { - return new EncryptExternal(binaryName, properties, tempDirProvider); - } - - @Override - @Nonnull - public Decrypt decrypt() { - return new DecryptExternal(binaryName, properties, tempDirProvider); - } - - @Override - @Nonnull - public Armor armor() { - return new ArmorExternal(binaryName, properties); - } - - @Override - @Nonnull - public ListProfiles listProfiles() { - return new ListProfilesExternal(binaryName, properties); - } - - @Override - @Nonnull - public RevokeKey revokeKey() { - return new RevokeKeyExternal(binaryName, properties); - } - - @Override - @Nonnull - public ChangeKeyPassword changeKeyPassword() { - return new ChangeKeyPasswordExternal(binaryName, properties); - } - - @Override - @Nonnull - public Dearmor dearmor() { - return new DearmorExternal(binaryName, properties); - } - - public static void finish(@Nonnull Process process) throws IOException { - try { - mapExitCodeOrException(process); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - /** - * Wait for the {@link Process} to finish and read out its exit code. - * If the exit code is {@value "0"}, this method just returns. - * Otherwise, the exit code gets mapped to a {@link SOPGPException} which then gets thrown. - * If the exit code does not match any of the known exit codes defined in the SOP specification, - * this method throws a {@link RuntimeException} instead. - * - * @param process process - * @throws InterruptedException if the thread is interrupted before the process could exit - * @throws IOException in case of an IO error - */ - private static void mapExitCodeOrException(@Nonnull Process process) throws InterruptedException, IOException { - // wait for process termination - int exitCode = process.waitFor(); - - if (exitCode == 0) { - // we're good, bye - return; - } - - // Read error message - InputStream errIn = process.getErrorStream(); - String errorMessage = readString(errIn); - - switch (exitCode) { - case SOPGPException.NoSignature.EXIT_CODE: - throw new SOPGPException.NoSignature("External SOP backend reported error NoSignature (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE: - throw new UnsupportedOperationException("External SOP backend reported error UnsupportedAsymmetricAlgo (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.CertCannotEncrypt.EXIT_CODE: - throw new SOPGPException.CertCannotEncrypt("External SOP backend reported error CertCannotEncrypt (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.MissingArg.EXIT_CODE: - throw new SOPGPException.MissingArg("External SOP backend reported error MissingArg (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.IncompleteVerification.EXIT_CODE: - throw new SOPGPException.IncompleteVerification("External SOP backend reported error IncompleteVerification (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.CannotDecrypt.EXIT_CODE: - throw new SOPGPException.CannotDecrypt("External SOP backend reported error CannotDecrypt (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.PasswordNotHumanReadable.EXIT_CODE: - throw new SOPGPException.PasswordNotHumanReadable("External SOP backend reported error PasswordNotHumanReadable (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.UnsupportedOption.EXIT_CODE: - throw new SOPGPException.UnsupportedOption("External SOP backend reported error UnsupportedOption (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.BadData.EXIT_CODE: - throw new SOPGPException.BadData("External SOP backend reported error BadData (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.ExpectedText.EXIT_CODE: - throw new SOPGPException.ExpectedText("External SOP backend reported error ExpectedText (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.OutputExists.EXIT_CODE: - throw new SOPGPException.OutputExists("External SOP backend reported error OutputExists (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.MissingInput.EXIT_CODE: - throw new SOPGPException.MissingInput("External SOP backend reported error MissingInput (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.KeyIsProtected.EXIT_CODE: - throw new SOPGPException.KeyIsProtected("External SOP backend reported error KeyIsProtected (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.UnsupportedSubcommand.EXIT_CODE: - throw new SOPGPException.UnsupportedSubcommand("External SOP backend reported error UnsupportedSubcommand (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.UnsupportedSpecialPrefix.EXIT_CODE: - throw new SOPGPException.UnsupportedSpecialPrefix("External SOP backend reported error UnsupportedSpecialPrefix (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.AmbiguousInput.EXIT_CODE: - throw new SOPGPException.AmbiguousInput("External SOP backend reported error AmbiguousInput (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.KeyCannotSign.EXIT_CODE: - throw new SOPGPException.KeyCannotSign("External SOP backend reported error KeyCannotSign (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.IncompatibleOptions.EXIT_CODE: - throw new SOPGPException.IncompatibleOptions("External SOP backend reported error IncompatibleOptions (" + - exitCode + "):\n" + errorMessage); - - case SOPGPException.UnsupportedProfile.EXIT_CODE: - throw new SOPGPException.UnsupportedProfile("External SOP backend reported error UnsupportedProfile (" + - exitCode + "):\n" + errorMessage); - - default: - // Did you forget to add a case for a new exception type? - throw new RuntimeException("External SOP backend reported unknown exit code (" + - exitCode + "):\n" + errorMessage); - } - } - - /** - * Return all key-value pairs from the given {@link Properties} object as a list with items of the form - *
key=value
. - * - * @param properties properties - * @return list of key=value strings - */ - public static List propertiesToEnv(@Nonnull Properties properties) { - List env = new ArrayList<>(); - for (Object key : properties.keySet()) { - env.add(key + "=" + properties.get(key)); - } - return env; - } - - /** - * Read the contents of the {@link InputStream} and return them as a {@link String}. - * - * @param inputStream input stream - * @return string - * @throws IOException in case of an IO error - */ - public static String readString(@Nonnull InputStream inputStream) throws IOException { - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - byte[] buf = new byte[4096]; - int r; - while ((r = inputStream.read(buf)) > 0) { - bOut.write(buf, 0, r); - } - return bOut.toString(); - } - - /** - * Execute the given command on the given {@link Runtime} with the given list of environment variables. - * This command does not transform any input data, and instead is purely a producer. - * - * @param runtime runtime - * @param commandList command - * @param envList environment variables - * @return ready to read the result from - */ - public static Ready executeProducingOperation(@Nonnull Runtime runtime, - @Nonnull List commandList, - @Nonnull List envList) { - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = runtime.exec(command, env); - InputStream stdIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = stdIn.read(buf)) >= 0) { - outputStream.write(buf, 0, r); - } - - outputStream.flush(); - outputStream.close(); - - ExternalSOP.finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Execute the given command on the given runtime using the given environment variables. - * The given input stream provides input for the process. - * This command is a transformation, meaning it is given input data and transforms it into output data. - * - * @param runtime runtime - * @param commandList command - * @param envList environment variables - * @param standardIn stream of input data for the process - * @return ready to read the result from - */ - public static Ready executeTransformingOperation(@Nonnull Runtime runtime, @Nonnull List commandList, @Nonnull List envList, @Nonnull InputStream standardIn) { - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new Ready() { - @Override - public void writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = standardIn.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - standardIn.close(); - - try { - processOut.flush(); - processOut.close(); - } catch (IOException e) { - // Perhaps the stream is already closed, in which case we ignore the exception. - if (!"Stream closed".equals(e.getMessage())) { - throw e; - } - } - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - processIn.close(); - - outputStream.flush(); - outputStream.close(); - - finish(process); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * This interface can be used to provide a directory in which external SOP binaries can temporarily store - * additional results of OpenPGP operations such that the binding classes can parse them out from there. - * Unfortunately, on Java you cannot open {@link java.io.FileDescriptor FileDescriptors} arbitrarily, so we - * have to rely on temporary files to pass results. - * An example: - *
sop decrypt
can emit signature verifications via
--verify-out=/path/to/tempfile
. - * {@link DecryptExternal} will then parse the temp file to make the result available to consumers. - * Temporary files are deleted after being read, yet creating temp files for sensitive information on disk - * might pose a security risk. Use with care! - */ - public interface TempDirProvider { - File provideTempDirectory() throws IOException; - } - - /** - * Default implementation of the {@link TempDirProvider} which stores temporary files in the systems temp dir - * ({@link Files#createTempDirectory(String, FileAttribute[])}). - * - * @return default implementation - */ - public static TempDirProvider defaultTempDirProvider() { - return new TempDirProvider() { - @Override - public File provideTempDirectory() throws IOException { - return Files.createTempDirectory("ext-sop").toFile(); - } - }; - } -} diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt new file mode 100644 index 0000000..3a0ef52 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -0,0 +1,318 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external + +import java.io.* +import java.nio.file.Files +import java.util.* +import javax.annotation.Nonnull +import sop.Ready +import sop.SOP +import sop.exception.SOPGPException.* +import sop.external.ExternalSOP.TempDirProvider +import sop.external.operation.* +import sop.operation.* + +/** + * Implementation of the {@link SOP} API using an external SOP binary. + * + * Instantiate an [ExternalSOP] object for the given binary and the given [TempDirProvider] using + * empty environment variables. + * + * @param binaryName name / path of the SOP binary + * @param tempDirProvider custom tempDirProvider + */ +class ExternalSOP( + private val binaryName: String, + private val properties: Properties = Properties(), + private val tempDirProvider: TempDirProvider = defaultTempDirProvider() +) : SOP { + + constructor( + binaryName: String, + properties: Properties + ) : this(binaryName, properties, defaultTempDirProvider()) + + override fun version(): Version = VersionExternal(binaryName, properties) + + override fun generateKey(): GenerateKey = GenerateKeyExternal(binaryName, properties) + + override fun extractCert(): ExtractCert = ExtractCertExternal(binaryName, properties) + + override fun detachedSign(): DetachedSign = + DetachedSignExternal(binaryName, properties, tempDirProvider) + + override fun inlineSign(): InlineSign = InlineSignExternal(binaryName, properties) + + override fun detachedVerify(): DetachedVerify = DetachedVerifyExternal(binaryName, properties) + + override fun inlineVerify(): InlineVerify = + InlineVerifyExternal(binaryName, properties, tempDirProvider) + + override fun inlineDetach(): InlineDetach = + InlineDetachExternal(binaryName, properties, tempDirProvider) + + override fun encrypt(): Encrypt = EncryptExternal(binaryName, properties, tempDirProvider) + + override fun decrypt(): Decrypt = DecryptExternal(binaryName, properties, tempDirProvider) + + override fun armor(): Armor = ArmorExternal(binaryName, properties) + + override fun dearmor(): Dearmor = DearmorExternal(binaryName, properties) + + override fun listProfiles(): ListProfiles = ListProfilesExternal(binaryName, properties) + + override fun revokeKey(): RevokeKey = RevokeKeyExternal(binaryName, properties) + + override fun changeKeyPassword(): ChangeKeyPassword = + ChangeKeyPasswordExternal(binaryName, properties) + + /** + * This interface can be used to provide a directory in which external SOP binaries can + * temporarily store additional results of OpenPGP operations such that the binding classes can + * parse them out from there. Unfortunately, on Java you cannot open + * [FileDescriptors][java.io.FileDescriptor] arbitrarily, so we have to rely on temporary files + * to pass results. An example: `sop decrypt` can emit signature verifications via + * `--verify-out=/path/to/tempfile`. [DecryptExternal] will then parse the temp file to make the + * result available to consumers. Temporary files are deleted after being read, yet creating + * temp files for sensitive information on disk might pose a security risk. Use with care! + */ + fun interface TempDirProvider { + + @Throws(IOException::class) fun provideTempDirectory(): File + } + + companion object { + + @JvmStatic + @Throws(IOException::class) + fun finish(process: Process) { + try { + mapExitCodeOrException(process) + } catch (e: InterruptedException) { + throw RuntimeException(e) + } + } + + @JvmStatic + @Throws(InterruptedException::class, IOException::class) + private fun mapExitCodeOrException(process: Process) { + // wait for process termination + val exitCode = process.waitFor() + + if (exitCode == 0) { + // we're good, bye + return + } + + // Read error message + val errIn = process.errorStream + val errorMessage = readString(errIn) + + when (exitCode) { + NoSignature.EXIT_CODE -> + throw NoSignature( + "External SOP backend reported error NoSignature ($exitCode):\n$errorMessage") + UnsupportedAsymmetricAlgo.EXIT_CODE -> + throw UnsupportedOperationException( + "External SOP backend reported error UnsupportedAsymmetricAlgo ($exitCode):\n$errorMessage") + CertCannotEncrypt.EXIT_CODE -> + throw CertCannotEncrypt( + "External SOP backend reported error CertCannotEncrypt ($exitCode):\n$errorMessage") + MissingArg.EXIT_CODE -> + throw MissingArg( + "External SOP backend reported error MissingArg ($exitCode):\n$errorMessage") + IncompleteVerification.EXIT_CODE -> + throw IncompleteVerification( + "External SOP backend reported error IncompleteVerification ($exitCode):\n$errorMessage") + CannotDecrypt.EXIT_CODE -> + throw CannotDecrypt( + "External SOP backend reported error CannotDecrypt ($exitCode):\n$errorMessage") + PasswordNotHumanReadable.EXIT_CODE -> + throw PasswordNotHumanReadable( + "External SOP backend reported error PasswordNotHumanReadable ($exitCode):\n$errorMessage") + UnsupportedOption.EXIT_CODE -> + throw UnsupportedOption( + "External SOP backend reported error UnsupportedOption ($exitCode):\n$errorMessage") + BadData.EXIT_CODE -> + throw BadData( + "External SOP backend reported error BadData ($exitCode):\n$errorMessage") + ExpectedText.EXIT_CODE -> + throw ExpectedText( + "External SOP backend reported error ExpectedText ($exitCode):\n$errorMessage") + OutputExists.EXIT_CODE -> + throw OutputExists( + "External SOP backend reported error OutputExists ($exitCode):\n$errorMessage") + MissingInput.EXIT_CODE -> + throw MissingInput( + "External SOP backend reported error MissingInput ($exitCode):\n$errorMessage") + KeyIsProtected.EXIT_CODE -> + throw KeyIsProtected( + "External SOP backend reported error KeyIsProtected ($exitCode):\n$errorMessage") + UnsupportedSubcommand.EXIT_CODE -> + throw UnsupportedSubcommand( + "External SOP backend reported error UnsupportedSubcommand ($exitCode):\n$errorMessage") + UnsupportedSpecialPrefix.EXIT_CODE -> + throw UnsupportedSpecialPrefix( + "External SOP backend reported error UnsupportedSpecialPrefix ($exitCode):\n$errorMessage") + AmbiguousInput.EXIT_CODE -> + throw AmbiguousInput( + "External SOP backend reported error AmbiguousInput ($exitCode):\n$errorMessage") + KeyCannotSign.EXIT_CODE -> + throw KeyCannotSign( + "External SOP backend reported error KeyCannotSign ($exitCode):\n$errorMessage") + IncompatibleOptions.EXIT_CODE -> + throw IncompatibleOptions( + "External SOP backend reported error IncompatibleOptions ($exitCode):\n$errorMessage") + UnsupportedProfile.EXIT_CODE -> + throw UnsupportedProfile( + "External SOP backend reported error UnsupportedProfile ($exitCode):\n$errorMessage") + + // Did you forget to add a case for a new exception type? + else -> + throw RuntimeException( + "External SOP backend reported unknown exit code ($exitCode):\n$errorMessage") + } + } + + /** + * Return all key-value pairs from the given [Properties] object as a list with items of the + * form `key=value`. + * + * @param properties properties + * @return list of key=value strings + */ + @JvmStatic + fun propertiesToEnv(properties: Properties): List = + properties.map { "${it.key}=${it.value}" } + + /** + * Read the contents of the [InputStream] and return them as a [String]. + * + * @param inputStream input stream + * @return string + * @throws IOException in case of an IO error + */ + @JvmStatic + @Throws(IOException::class) + fun readString(inputStream: InputStream): String { + val bOut = ByteArrayOutputStream() + val buf = ByteArray(4096) + var r: Int + while (inputStream.read(buf).also { r = it } > 0) { + bOut.write(buf, 0, r) + } + return bOut.toString() + } + + /** + * Execute the given command on the given [Runtime] with the given list of environment + * variables. This command does not transform any input data, and instead is purely a + * producer. + * + * @param runtime runtime + * @param commandList command + * @param envList environment variables + * @return ready to read the result from + */ + @JvmStatic + fun executeProducingOperation( + runtime: Runtime, + commandList: List, + envList: List + ): Ready { + try { + val process = runtime.exec(commandList.toTypedArray(), envList.toTypedArray()) + val stdIn = process.inputStream + + return object : Ready() { + @Throws(IOException::class) + override fun writeTo(@Nonnull outputStream: OutputStream) { + val buf = ByteArray(4096) + var r: Int + while (stdIn.read(buf).also { r = it } >= 0) { + outputStream.write(buf, 0, r) + } + outputStream.flush() + outputStream.close() + finish(process) + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + /** + * Execute the given command on the given runtime using the given environment variables. The + * given input stream provides input for the process. This command is a transformation, + * meaning it is given input data and transforms it into output data. + * + * @param runtime runtime + * @param commandList command + * @param envList environment variables + * @param standardIn stream of input data for the process + * @return ready to read the result from + */ + @JvmStatic + fun executeTransformingOperation( + runtime: Runtime, + commandList: List, + envList: List, + standardIn: InputStream + ): Ready { + try { + val process = runtime.exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : Ready() { + override fun writeTo(outputStream: OutputStream) { + val buf = ByteArray(4096) + var r: Int + while (standardIn.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + standardIn.close() + + try { + processOut.flush() + processOut.close() + } catch (e: IOException) { + // Perhaps the stream is already closed, in which case we ignore the + // exception. + if ("Stream closed" != e.message) { + throw e + } + } + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + processIn.close() + + outputStream.flush() + outputStream.close() + + finish(process) + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + /** + * Default implementation of the [TempDirProvider] which stores temporary files in the + * systems temp dir ([Files.createTempDirectory]). + * + * @return default implementation + */ + @JvmStatic + fun defaultTempDirProvider(): TempDirProvider { + return TempDirProvider { Files.createTempDirectory("ext-sop").toFile() } + } + } +} From 67719526188a5ccbf1b4087fd4924c899b24703f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 16:33:44 +0100 Subject: [PATCH 286/444] Kotlin conversion: ArmorExternal --- .../sop/external/operation/ArmorExternal.java | 46 ------------------- .../sop/external/operation/ArmorExternal.kt | 26 +++++++++++ 2 files changed, 26 insertions(+), 46 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/ArmorExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java b/external-sop/src/main/java/sop/external/operation/ArmorExternal.java deleted file mode 100644 index e1d02e9..0000000 --- a/external-sop/src/main/java/sop/external/operation/ArmorExternal.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.enums.ArmorLabel; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.Armor; - -import javax.annotation.Nonnull; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link Armor} operation using an external SOP binary. - */ -public class ArmorExternal implements Armor { - - private final List commandList = new ArrayList<>(); - private final List envList; - - public ArmorExternal(String binary, Properties environment) { - commandList.add(binary); - commandList.add("armor"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Deprecated - @Nonnull - public Armor label(@Nonnull ArmorLabel label) throws SOPGPException.UnsupportedOption { - commandList.add("--label=" + label); - return this; - } - - @Override - @Nonnull - public Ready data(@Nonnull InputStream data) throws SOPGPException.BadData { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt new file mode 100644 index 0000000..f80c57b --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.enums.ArmorLabel +import sop.exception.SOPGPException +import sop.external.ExternalSOP +import sop.operation.Armor + +/** Implementation of the [Armor] operation using an external SOP binary. */ +class ArmorExternal(binary: String, environment: Properties) : Armor { + + private val commandList: MutableList = mutableListOf(binary, "armor") + private val envList: List = ExternalSOP.propertiesToEnv(environment) + + override fun label(label: ArmorLabel): Armor = apply { commandList.add("--label=$label") } + + @Throws(SOPGPException.BadData::class) + override fun data(data: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data) +} From d149aac56ca40bf349989537e1cb19806ddf169f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 16:45:21 +0100 Subject: [PATCH 287/444] Kotlin conversion: ChangeKeyPasswordExternal --- .../operation/ChangeKeyPasswordExternal.java | 62 ------------------- .../operation/ChangeKeyPasswordExternal.kt | 37 +++++++++++ 2 files changed, 37 insertions(+), 62 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ChangeKeyPasswordExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java b/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java deleted file mode 100644 index d53d6a5..0000000 --- a/external-sop/src/main/java/sop/external/operation/ChangeKeyPasswordExternal.java +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.ChangeKeyPassword; - -import javax.annotation.Nonnull; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -public class ChangeKeyPasswordExternal implements ChangeKeyPassword { - private final List commandList = new ArrayList<>(); - private final List envList; - - private int keyPasswordCounter = 0; - - public ChangeKeyPasswordExternal(String binary, Properties environment) { - this.commandList.add(binary); - this.commandList.add("decrypt"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public ChangeKeyPassword noArmor() { - this.commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public ChangeKeyPassword oldKeyPassphrase(@Nonnull String oldPassphrase) { - this.commandList.add("--old-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); - this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + oldPassphrase); - keyPasswordCounter++; - - return this; - } - - @Override - @Nonnull - public ChangeKeyPassword newKeyPassphrase(@Nonnull String newPassphrase) { - this.commandList.add("--new-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); - this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + newPassphrase); - keyPasswordCounter++; - - return this; - } - - @Override - @Nonnull - public Ready keys(@Nonnull InputStream inputStream) throws SOPGPException.KeyIsProtected, SOPGPException.BadData { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, inputStream); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ChangeKeyPasswordExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ChangeKeyPasswordExternal.kt new file mode 100644 index 0000000..a45e59f --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ChangeKeyPasswordExternal.kt @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.ChangeKeyPassword + +/** Implementation of the [ChangeKeyPassword] operation using an external SOP binary. */ +class ChangeKeyPasswordExternal(binary: String, environment: Properties) : ChangeKeyPassword { + + private val commandList: MutableList = mutableListOf(binary, "change-key-password") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var keyPasswordCounter = 0 + + override fun noArmor(): ChangeKeyPassword = apply { commandList.add("--no-armor") } + + override fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword = apply { + commandList.add("--old-key-password=@ENV:KEY_PASSWORD_$keyPasswordCounter") + envList.add("KEY_PASSWORD_$keyPasswordCounter=$oldPassphrase") + keyPasswordCounter += 1 + } + + override fun newKeyPassphrase(newPassphrase: String): ChangeKeyPassword = apply { + commandList.add("--new-key-password=@ENV:KEY_PASSWORD_$keyPasswordCounter") + envList.add("KEY_PASSWORD_$keyPasswordCounter=$newPassphrase") + keyPasswordCounter += 1 + } + + override fun keys(keys: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keys) +} From da2b299f4d6bea493585e7640faa0d1616fd4edc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 16:45:32 +0100 Subject: [PATCH 288/444] Kotlin conversion: DearmorExternal --- .../external/operation/DearmorExternal.java | 37 ------------------- .../sop/external/operation/DearmorExternal.kt | 20 ++++++++++ 2 files changed, 20 insertions(+), 37 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DearmorExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DearmorExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java b/external-sop/src/main/java/sop/external/operation/DearmorExternal.java deleted file mode 100644 index cd3da6f..0000000 --- a/external-sop/src/main/java/sop/external/operation/DearmorExternal.java +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.Dearmor; - -import javax.annotation.Nonnull; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link Dearmor} operation using an external SOP binary. - */ -public class DearmorExternal implements Dearmor { - - private final List commandList = new ArrayList<>(); - private final List envList; - - public DearmorExternal(String binary, Properties environment) { - commandList.add(binary); - commandList.add("dearmor"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public Ready data(@Nonnull InputStream data) throws SOPGPException.BadData { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DearmorExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DearmorExternal.kt new file mode 100644 index 0000000..928d9b4 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DearmorExternal.kt @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.Dearmor + +/** Implementation of the [Dearmor] operation using an external SOP binary. */ +class DearmorExternal(binary: String, environment: Properties) : Dearmor { + private val commandList = listOf(binary, "dearmor") + private val envList = ExternalSOP.propertiesToEnv(environment) + + override fun data(data: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data) +} From 03da9bbfb705c67906c9904204b7375d7a382a7c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:04:00 +0100 Subject: [PATCH 289/444] Kotlin conversion: DecryptExternal --- .../external/operation/DecryptExternal.java | 185 ------------------ .../sop/external/operation/DecryptExternal.kt | 133 +++++++++++++ 2 files changed, 133 insertions(+), 185 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DecryptExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java b/external-sop/src/main/java/sop/external/operation/DecryptExternal.java deleted file mode 100644 index a1c4016..0000000 --- a/external-sop/src/main/java/sop/external/operation/DecryptExternal.java +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.Verification; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.Decrypt; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link Decrypt} operation using an external SOP binary. - */ -public class DecryptExternal implements Decrypt { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - private int verifyWithCounter = 0; - private int withSessionKeyCounter = 0; - private int withPasswordCounter = 0; - private int keyCounter = 0; - private int withKeyPasswordCounter = 0; - - public DecryptExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - this.commandList.add(binary); - this.commandList.add("decrypt"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public Decrypt verifyNotBefore(@Nonnull Date timestamp) - throws SOPGPException.UnsupportedOption { - this.commandList.add("--verify-not-before=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public Decrypt verifyNotAfter(@Nonnull Date timestamp) - throws SOPGPException.UnsupportedOption { - this.commandList.add("--verify-not-after=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public Decrypt verifyWithCert(@Nonnull InputStream cert) - throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "VERIFY_WITH_" + verifyWithCounter++; - commandList.add("--verify-with=@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(cert)); - return this; - } - - @Override - @Nonnull - public Decrypt withSessionKey(@Nonnull SessionKey sessionKey) - throws SOPGPException.UnsupportedOption { - String envVar = "SESSION_KEY_" + withSessionKeyCounter++; - commandList.add("--with-session-key=@ENV:" + envVar); - envList.add(envVar + "=" + sessionKey); - return this; - } - - @Override - @Nonnull - public Decrypt withPassword(@Nonnull String password) - throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - String envVar = "PASSWORD_" + withPasswordCounter++; - commandList.add("--with-password=@ENV:" + envVar); - envList.add(envVar + "=" + password); - return this; - } - - @Override - @Nonnull - public Decrypt withKey(@Nonnull InputStream key) - throws SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "KEY_" + keyCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public Decrypt withKeyPassword(@Nonnull byte[] password) - throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public ReadyWithResult ciphertext(@Nonnull InputStream ciphertext) - throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt, - SOPGPException.KeyIsProtected, IOException { - File tempDir = tempDirProvider.provideTempDirectory(); - - File sessionKeyOut = new File(tempDir, "session-key-out"); - sessionKeyOut.delete(); - commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); - - File verifyOut = new File(tempDir, "verifications-out"); - verifyOut.delete(); - if (verifyWithCounter != 0) { - commandList.add("--verify-out=" + verifyOut.getAbsolutePath()); - } - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult() { - @Override - public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = ciphertext.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - ciphertext.close(); - processOut.close(); - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); - String line = ExternalSOP.readString(sessionKeyOutIn); - SessionKey sessionKey = SessionKey.fromString(line.trim()); - sessionKeyOutIn.close(); - sessionKeyOut.delete(); - - List verifications = new ArrayList<>(); - if (verifyWithCounter != 0) { - FileInputStream verifyOutIn = new FileInputStream(verifyOut); - BufferedReader reader = new BufferedReader(new InputStreamReader(verifyOutIn)); - while ((line = reader.readLine()) != null) { - verifications.add(Verification.fromString(line.trim())); - } - reader.close(); - } - - return new DecryptionResult(sessionKey, verifications); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt new file mode 100644 index 0000000..b68d3a6 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.DecryptionResult +import sop.ReadyWithResult +import sop.SessionKey +import sop.Verification +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.external.ExternalSOP.Companion.readString +import sop.operation.Decrypt +import sop.util.UTCUtil + +/** Implementation of the [Decrypt] operation using an external SOP binary. */ +class DecryptExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : Decrypt { + + private val commandList = mutableListOf(binary, "decrypt") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + private var requireVerification = false + + override fun verifyNotBefore(timestamp: Date): Decrypt = apply { + commandList.add("--verify-not-before=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun verifyNotAfter(timestamp: Date): Decrypt = apply { + commandList.add("--verify-not-after=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun verifyWithCert(cert: InputStream): Decrypt = apply { + commandList.add("--verify-with=@ENV:VERIFY_WITH_$argCounter") + envList.add("VERIFY_WITH_$argCounter=${readString(cert)}") + argCounter += 1 + requireVerification = true + } + + override fun withSessionKey(sessionKey: SessionKey): Decrypt = apply { + commandList.add("--with-session-key=@ENV:SESSION_KEY_$argCounter") + envList.add("SESSION_KEY_$argCounter=$sessionKey") + argCounter += 1 + } + + override fun withPassword(password: String): Decrypt = apply { + commandList.add("--with-password=@ENV:PASSWORD_$argCounter") + envList.add("PASSWORD_$argCounter=$password") + argCounter += 1 + } + + override fun withKey(key: InputStream): Decrypt = apply { + commandList.add("@ENV:KEY_$argCounter") + envList.add("KEY_$argCounter=${readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): Decrypt = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCounter") + envList.add("KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } + + override fun ciphertext(ciphertext: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + + val sessionKeyOut = File(tempDir, "session-key-out") + sessionKeyOut.delete() + commandList.add("--session-key-out=${sessionKeyOut.absolutePath}") + + val verifyOut = File(tempDir, "verifications-out") + verifyOut.delete() + if (requireVerification) { + commandList.add("--verify-out=${verifyOut.absolutePath}") + } + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult() { + override fun writeTo(outputStream: OutputStream): DecryptionResult { + val buf = ByteArray(4096) + var r: Int + while (ciphertext.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + ciphertext.close() + processOut.close() + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val sessionKeyOutIn = FileInputStream(sessionKeyOut) + var line = readString(sessionKeyOutIn) + val sessionKey = SessionKey.fromString(line.trim { it <= ' ' }) + sessionKeyOutIn.close() + sessionKeyOut.delete() + + val verifications: MutableList = ArrayList() + if (requireVerification) { + val verifyOutIn = FileInputStream(verifyOut) + val reader = BufferedReader(InputStreamReader(verifyOutIn)) + while (reader.readLine().also { line = it } != null) { + verifications.add(Verification.fromString(line.trim())) + } + reader.close() + } + + return DecryptionResult(sessionKey, verifications) + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 9cd9f151c9b6138b3e2fe5999efea347922ee9ae Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:13:28 +0100 Subject: [PATCH 290/444] Kotlin conversion: DetachedSignExternal --- .../operation/DetachedSignExternal.java | 142 ------------------ .../operation/DetachedSignExternal.kt | 104 +++++++++++++ 2 files changed, 104 insertions(+), 142 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java deleted file mode 100644 index 2ef2714..0000000 --- a/external-sop/src/main/java/sop/external/operation/DetachedSignExternal.java +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.MicAlg; -import sop.ReadyWithResult; -import sop.SigningResult; -import sop.enums.SignAs; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.DetachedSign; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link DetachedSign} operation using an external SOP binary. - */ -public class DetachedSignExternal implements DetachedSign { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - private int withKeyPasswordCounter = 0; - private int keyCounter = 0; - - public DetachedSignExternal(String binary, Properties properties, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - commandList.add(binary); - commandList.add("sign"); - envList = ExternalSOP.propertiesToEnv(properties); - } - - @Override - @Nonnull - public DetachedSign noArmor() { - commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public DetachedSign key(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "KEY_" + keyCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public DetachedSign withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public DetachedSign mode(@Nonnull SignAs mode) throws SOPGPException.UnsupportedOption { - commandList.add("--as=" + mode); - return this; - } - - @Override - @Nonnull - public ReadyWithResult data(@Nonnull InputStream data) - throws IOException, SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - - File tempDir = tempDirProvider.provideTempDirectory(); - File micAlgOut = new File(tempDir, "micAlgOut"); - micAlgOut.delete(); - commandList.add("--micalg-out=" + micAlgOut.getAbsolutePath()); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult() { - @Override - public SigningResult writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = data.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - data.close(); - try { - processOut.close(); - } catch (IOException e) { - // Ignore Stream closed - if (!"Stream closed".equals(e.getMessage())) { - throw e; - } - } - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - SigningResult.Builder builder = SigningResult.builder(); - if (micAlgOut.exists()) { - BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(micAlgOut))); - String line = reader.readLine(); - if (line != null && !line.trim().isEmpty()) { - MicAlg micAlg = new MicAlg(line.trim()); - builder.setMicAlg(micAlg); - } - reader.close(); - micAlgOut.delete(); - } - - return builder.build(); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt new file mode 100644 index 0000000..66d1db8 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DetachedSignExternal.kt @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.MicAlg +import sop.ReadyWithResult +import sop.SigningResult +import sop.SigningResult.Companion.builder +import sop.enums.SignAs +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.operation.DetachedSign + +/** Implementation of the [DetachedSign] operation using an external SOP binary. */ +class DetachedSignExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : DetachedSign { + + private val commandList = mutableListOf(binary, "sign") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun mode(mode: SignAs): DetachedSign = apply { commandList.add("--as=$mode") } + + override fun data(data: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + val micAlgOut = File(tempDir, "micAlgOut") + micAlgOut.delete() + commandList.add("--micalg-out=${micAlgOut.absolutePath}") + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult() { + override fun writeTo(outputStream: OutputStream): SigningResult { + val buf = ByteArray(4096) + var r: Int + while (data.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + data.close() + try { + processOut.close() + } catch (e: IOException) { + // Ignore Stream closed + if ("Stream closed" != e.message) { + throw e + } + } + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val builder = builder() + if (micAlgOut.exists()) { + val reader = BufferedReader(InputStreamReader(FileInputStream(micAlgOut))) + val line = reader.readLine() + if (line != null && line.isNotBlank()) { + val micAlg = MicAlg(line.trim()) + builder.setMicAlg(micAlg) + } + reader.close() + micAlgOut.delete() + } + + return builder.build() + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + override fun noArmor(): DetachedSign = apply { commandList.add("--no-armor") } + + override fun key(key: InputStream): DetachedSign = apply { + commandList.add("@ENV:KEY_$argCounter") + envList.add("KEY_$argCounter=${ExternalSOP.readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): DetachedSign = apply { + commandList.add("--with-key-password=@ENV:WITH_KEY_PASSWORD_$argCounter") + envList.add("WITH_KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } +} From 4a405f6d394d95a5b343b5e42fc00334d0fceef3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:22:07 +0100 Subject: [PATCH 291/444] Kotlin conversion: DetachedVerifyExternal --- .../operation/DetachedVerifyExternal.java | 117 ------------------ .../operation/DetachedVerifyExternal.kt | 90 ++++++++++++++ 2 files changed, 90 insertions(+), 117 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/DetachedVerifyExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java deleted file mode 100644 index 866150d..0000000 --- a/external-sop/src/main/java/sop/external/operation/DetachedVerifyExternal.java +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Verification; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.DetachedVerify; -import sop.operation.VerifySignatures; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; - -/** - * Implementation of the {@link DetachedVerify} operation using an external SOP binary. - */ -public class DetachedVerifyExternal implements DetachedVerify { - - private final List commandList = new ArrayList<>(); - private final List envList; - - private final Set certs = new HashSet<>(); - private InputStream signatures; - private int certCounter = 0; - - public DetachedVerifyExternal(String binary, Properties environment) { - commandList.add(binary); - commandList.add("verify"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public DetachedVerify notBefore(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { - commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public DetachedVerify notAfter(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { - commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public DetachedVerify cert(@Nonnull InputStream cert) throws SOPGPException.BadData { - this.certs.add(cert); - return this; - } - - @Override - @Nonnull - public VerifySignatures signatures(@Nonnull InputStream signatures) throws SOPGPException.BadData { - this.signatures = signatures; - return this; - } - - @Override - @Nonnull - public List data(@Nonnull InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { - commandList.add("@ENV:SIGNATURE"); - envList.add("SIGNATURE=" + ExternalSOP.readString(signatures)); - - for (InputStream cert : certs) { - String envVar = "CERT_" + certCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(cert)); - } - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - byte[] buf = new byte[4096]; - int r; - while ((r = data.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - data.close(); - processOut.close(); - - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(processIn)); - List verifications = new ArrayList<>(); - - String line = null; - while ((line = bufferedReader.readLine()) != null) { - verifications.add(Verification.fromString(line)); - } - - ExternalSOP.finish(process); - - return verifications; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/DetachedVerifyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DetachedVerifyExternal.kt new file mode 100644 index 0000000..3340a33 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/DetachedVerifyExternal.kt @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.util.* +import sop.Verification +import sop.Verification.Companion.fromString +import sop.exception.SOPGPException +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.operation.DetachedVerify +import sop.operation.VerifySignatures +import sop.util.UTCUtil + +/** Implementation of the [DetachedVerify] operation using an external SOP binary. */ +class DetachedVerifyExternal(binary: String, environment: Properties) : DetachedVerify { + + private val commandList = mutableListOf(binary, "verify") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var signatures: InputStream? = null + private val certs: MutableSet = mutableSetOf() + private var argCounter = 0 + + override fun signatures(signatures: InputStream): VerifySignatures = apply { + this.signatures = signatures + } + + override fun notBefore(timestamp: Date): DetachedVerify = apply { + commandList.add("--not-before=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun notAfter(timestamp: Date): DetachedVerify = apply { + commandList.add("--not-after=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun cert(cert: InputStream): DetachedVerify = apply { this.certs.add(cert) } + + override fun data(data: InputStream): List { + // Signature + if (signatures == null) { + throw SOPGPException.MissingArg("Missing argument: signatures cannot be null.") + } + commandList.add("@ENV:SIGNATURE") + envList.add("SIGNATURE=${ExternalSOP.readString(signatures!!)}") + + // Certs + for (cert in certs) { + commandList.add("@ENV:CERT_$argCounter") + envList.add("CERT_$argCounter=${ExternalSOP.readString(cert)}") + argCounter += 1 + } + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + val buf = ByteArray(4096) + var r: Int + while (data.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + data.close() + processOut.close() + + val bufferedReader = BufferedReader(InputStreamReader(processIn)) + val verifications: MutableList = ArrayList() + + var line: String? + while (bufferedReader.readLine().also { line = it } != null) { + verifications.add(fromString(line!!)) + } + + finish(process) + + return verifications + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From c53c69f3acb503f01bcf75d2f0601ba51c4c243a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:32:43 +0100 Subject: [PATCH 292/444] Kotlin conversion: EncryptExternal --- .../external/operation/EncryptExternal.java | 160 ------------------ .../sop/external/operation/EncryptExternal.kt | 111 ++++++++++++ 2 files changed, 111 insertions(+), 160 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/EncryptExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java b/external-sop/src/main/java/sop/external/operation/EncryptExternal.java deleted file mode 100644 index f41a36e..0000000 --- a/external-sop/src/main/java/sop/external/operation/EncryptExternal.java +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.EncryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.enums.EncryptAs; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.Encrypt; - -import javax.annotation.Nonnull; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link Encrypt} operation using an external SOP binary. - */ -public class EncryptExternal implements Encrypt { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - private int SIGN_WITH_COUNTER = 0; - private int KEY_PASSWORD_COUNTER = 0; - private int PASSWORD_COUNTER = 0; - private int CERT_COUNTER = 0; - - public EncryptExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - this.commandList.add(binary); - this.commandList.add("encrypt"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public Encrypt noArmor() { - this.commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public Encrypt mode(@Nonnull EncryptAs mode) - throws SOPGPException.UnsupportedOption { - this.commandList.add("--as=" + mode); - return this; - } - - @Override - @Nonnull - public Encrypt signWith(@Nonnull InputStream key) - throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, - IOException { - String envVar = "SIGN_WITH_" + SIGN_WITH_COUNTER++; - commandList.add("--sign-with=@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public Encrypt withKeyPassword(@Nonnull byte[] password) - throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - String envVar = "KEY_PASSWORD_" + KEY_PASSWORD_COUNTER++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public Encrypt withPassword(@Nonnull String password) - throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - String envVar = "PASSWORD_" + PASSWORD_COUNTER++; - commandList.add("--with-password=@ENV:" + envVar); - envList.add(envVar + "=" + password); - return this; - } - - @Override - @Nonnull - public Encrypt withCert(@Nonnull InputStream cert) - throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, - IOException { - String envVar = "CERT_" + CERT_COUNTER++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(cert)); - return this; - } - - @Override - @Nonnull - public Encrypt profile(@Nonnull String profileName) { - commandList.add("--profile=" + profileName); - return this; - } - - @Override - @Nonnull - public ReadyWithResult plaintext(@Nonnull InputStream plaintext) - throws SOPGPException.KeyIsProtected, IOException { - File tempDir = tempDirProvider.provideTempDirectory(); - - File sessionKeyOut = new File(tempDir, "session-key-out"); - sessionKeyOut.delete(); - commandList.add("--session-key-out=" + sessionKeyOut.getAbsolutePath()); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult() { - @Override - public EncryptionResult writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = plaintext.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - plaintext.close(); - processOut.close(); - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - FileInputStream sessionKeyOutIn = new FileInputStream(sessionKeyOut); - String line = ExternalSOP.readString(sessionKeyOutIn); - SessionKey sessionKey = SessionKey.fromString(line.trim()); - sessionKeyOutIn.close(); - sessionKeyOut.delete(); - - return new EncryptionResult(sessionKey); - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt new file mode 100644 index 0000000..6f1cc6c --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.util.* +import sop.EncryptionResult +import sop.ReadyWithResult +import sop.SessionKey.Companion.fromString +import sop.enums.EncryptAs +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.external.ExternalSOP.Companion.readString +import sop.operation.Encrypt + +/** Implementation of the [Encrypt] operation using an external SOP binary. */ +class EncryptExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : Encrypt { + + private val commandList = mutableListOf(binary, "encrypt") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun noArmor(): Encrypt = apply { commandList.add("--no-armor") } + + override fun mode(mode: EncryptAs): Encrypt = apply { commandList.add("--as=$mode") } + + override fun signWith(key: InputStream): Encrypt = apply { + commandList.add("--sign-with@ENV:SIGN_WITH_$argCounter") + envList.add("SIGN_WITH_$argCounter=${ExternalSOP.readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): Encrypt = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCounter") + envList.add("KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } + + override fun withPassword(password: String): Encrypt = apply { + commandList.add("--with-password=@ENV:PASSWORD_$argCounter") + envList.add("PASSWORD_$argCounter=$password") + argCounter += 1 + } + + override fun withCert(cert: InputStream): Encrypt = apply { + commandList.add("@ENV:CERT_$argCounter") + envList.add("CERT_$argCounter=${readString(cert)}") + argCounter += 1 + } + + override fun profile(profileName: String): Encrypt = apply { + commandList.add("--profile=$profileName") + } + + override fun plaintext(plaintext: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + + val sessionKeyOut = File(tempDir, "session-key-out") + sessionKeyOut.delete() + commandList.add("--session-key-out=${sessionKeyOut.absolutePath}") + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult() { + override fun writeTo(outputStream: OutputStream): EncryptionResult { + val buf = ByteArray(4096) + var r: Int + while (plaintext.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + plaintext.close() + processOut.close() + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val sessionKeyOutIn = FileInputStream(sessionKeyOut) + val line = readString(sessionKeyOutIn) + val sessionKey = fromString(line.trim()) + sessionKeyOutIn.close() + sessionKeyOut.delete() + + return EncryptionResult(sessionKey) + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 01abae4d080d47660d4c881ef2fe6bf2bdc26bd5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:36:00 +0100 Subject: [PATCH 293/444] Kotlin conversion: ExtractCertExternal --- .../operation/ExtractCertExternal.java | 44 ------------------- .../external/operation/ExtractCertExternal.kt | 24 ++++++++++ 2 files changed, 24 insertions(+), 44 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ExtractCertExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java b/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java deleted file mode 100644 index 538359a..0000000 --- a/external-sop/src/main/java/sop/external/operation/ExtractCertExternal.java +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.ExtractCert; - -import javax.annotation.Nonnull; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link ExtractCert} operation using an external SOP binary. - */ -public class ExtractCertExternal implements ExtractCert { - - private final List commandList = new ArrayList<>(); - private final List envList; - - public ExtractCertExternal(String binary, Properties properties) { - this.commandList.add(binary); - this.commandList.add("extract-cert"); - this.envList = ExternalSOP.propertiesToEnv(properties); - } - - @Override - @Nonnull - public ExtractCert noArmor() { - this.commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public Ready key(@Nonnull InputStream keyInputStream) throws SOPGPException.BadData { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keyInputStream); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ExtractCertExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ExtractCertExternal.kt new file mode 100644 index 0000000..9b86733 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ExtractCertExternal.kt @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.ExtractCert + +/** Implementation of the [ExtractCert] operation using an external SOP binary. */ +class ExtractCertExternal(binary: String, environment: Properties) : ExtractCert { + + private val commandList = mutableListOf(binary, "extract-cert") + private val envList = ExternalSOP.propertiesToEnv(environment) + + override fun noArmor(): ExtractCert = apply { commandList.add("--no-armor") } + + override fun key(keyInputStream: InputStream): Ready = + ExternalSOP.executeTransformingOperation( + Runtime.getRuntime(), commandList, envList, keyInputStream) +} From 9b79a49bb54a7a5d5ef588394f9f63dc20e463e1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:41:33 +0100 Subject: [PATCH 294/444] Kotlin conversion: GenerateKeyExternal --- .../operation/GenerateKeyExternal.java | 78 ------------------- .../external/operation/GenerateKeyExternal.kt | 38 +++++++++ 2 files changed, 38 insertions(+), 78 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/GenerateKeyExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java b/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java deleted file mode 100644 index a8244cb..0000000 --- a/external-sop/src/main/java/sop/external/operation/GenerateKeyExternal.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.GenerateKey; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link GenerateKey} operation using an external SOP binary. - */ -public class GenerateKeyExternal implements GenerateKey { - - private final List commandList = new ArrayList<>(); - private final List envList; - - private int keyPasswordCounter = 0; - - public GenerateKeyExternal(String binary, Properties environment) { - this.commandList.add(binary); - this.commandList.add("generate-key"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public GenerateKey noArmor() { - this.commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public GenerateKey userId(@Nonnull String userId) { - this.commandList.add(userId); - return this; - } - - @Override - @Nonnull - public GenerateKey withKeyPassword(@Nonnull String password) - throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { - this.commandList.add("--with-key-password=@ENV:KEY_PASSWORD_" + keyPasswordCounter); - this.envList.add("KEY_PASSWORD_" + keyPasswordCounter + "=" + password); - keyPasswordCounter++; - - return this; - } - - @Override - @Nonnull - public GenerateKey profile(@Nonnull String profile) { - commandList.add("--profile=" + profile); - return this; - } - - @Override - @Nonnull - public GenerateKey signingOnly() { - commandList.add("--signing-only"); - return this; - } - - @Override - @Nonnull - public Ready generate() - throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { - return ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/GenerateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/GenerateKeyExternal.kt new file mode 100644 index 0000000..37116a4 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/GenerateKeyExternal.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.util.Properties +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.GenerateKey + +/** Implementation of the [GenerateKey] operation using an external SOP binary. */ +class GenerateKeyExternal(binary: String, environment: Properties) : GenerateKey { + + private val commandList = mutableListOf(binary, "generate-key") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun noArmor(): GenerateKey = apply { commandList.add("--no-armor") } + + override fun userId(userId: String): GenerateKey = apply { commandList.add(userId) } + + override fun withKeyPassword(password: String): GenerateKey = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCounter") + envList.add("KEY_PASSWORD_$argCounter=$password") + argCounter += 1 + } + + override fun profile(profile: String): GenerateKey = apply { + commandList.add("--profile=$profile") + } + + override fun signingOnly(): GenerateKey = apply { commandList.add("--signing-only") } + + override fun generate(): Ready = + ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList) +} From f1814530041462e65cb137dda085893d03a6a889 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:47:37 +0100 Subject: [PATCH 295/444] Kotlin conversion: InlineDetachExternal --- .../operation/InlineDetachExternal.java | 106 ------------------ .../operation/InlineDetachExternal.kt | 82 ++++++++++++++ 2 files changed, 82 insertions(+), 106 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/InlineDetachExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java b/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java deleted file mode 100644 index a798006..0000000 --- a/external-sop/src/main/java/sop/external/operation/InlineDetachExternal.java +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.ReadyWithResult; -import sop.Signatures; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.InlineDetach; - -import javax.annotation.Nonnull; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link InlineDetach} operation using an external SOP binary. - */ -public class InlineDetachExternal implements InlineDetach { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - public InlineDetachExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - commandList.add(binary); - commandList.add("inline-detach"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public InlineDetach noArmor() { - commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public ReadyWithResult message(@Nonnull InputStream messageInputStream) throws IOException, SOPGPException.BadData { - File tempDir = tempDirProvider.provideTempDirectory(); - - File signaturesOut = new File(tempDir, "signatures"); - signaturesOut.delete(); - commandList.add("--signatures-out=" + signaturesOut.getAbsolutePath()); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult() { - @Override - public Signatures writeTo(@Nonnull OutputStream outputStream) throws IOException { - byte[] buf = new byte[4096]; - int r; - while ((r = messageInputStream.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - messageInputStream.close(); - processOut.close(); - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - FileInputStream signaturesOutIn = new FileInputStream(signaturesOut); - ByteArrayOutputStream signaturesBuffer = new ByteArrayOutputStream(); - while ((r = signaturesOutIn.read(buf)) > 0) { - signaturesBuffer.write(buf, 0, r); - } - signaturesOutIn.close(); - signaturesOut.delete(); - - final byte[] sigBytes = signaturesBuffer.toByteArray(); - return new Signatures() { - @Override - public void writeTo(@Nonnull OutputStream signatureOutputStream) throws IOException { - signatureOutputStream.write(sigBytes); - } - }; - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/InlineDetachExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/InlineDetachExternal.kt new file mode 100644 index 0000000..f44e6bb --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/InlineDetachExternal.kt @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.ReadyWithResult +import sop.Signatures +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.operation.InlineDetach + +/** Implementation of the [InlineDetach] operation using an external SOP binary. */ +class InlineDetachExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : InlineDetach { + + private val commandList = mutableListOf(binary, "inline-detach") + private val envList = ExternalSOP.propertiesToEnv(environment) + + override fun noArmor(): InlineDetach = apply { commandList.add("--no-armor") } + + override fun message(messageInputStream: InputStream): ReadyWithResult { + val tempDir = tempDirProvider.provideTempDirectory() + + val signaturesOut = File(tempDir, "signatures") + signaturesOut.delete() + commandList.add("--signatures-out=${signaturesOut.absolutePath}") + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult() { + override fun writeTo(outputStream: OutputStream): Signatures { + val buf = ByteArray(4096) + var r: Int + while (messageInputStream.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + messageInputStream.close() + processOut.close() + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val signaturesOutIn = FileInputStream(signaturesOut) + val signaturesBuffer = ByteArrayOutputStream() + while (signaturesOutIn.read(buf).also { r = it } > 0) { + signaturesBuffer.write(buf, 0, r) + } + signaturesOutIn.close() + signaturesOut.delete() + + val sigBytes = signaturesBuffer.toByteArray() + + return object : Signatures() { + @Throws(IOException::class) + override fun writeTo(outputStream: OutputStream) { + outputStream.write(sigBytes) + } + } + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 7be71494cfdc063d7e370c7305bd017061532e86 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:52:56 +0100 Subject: [PATCH 296/444] Kotlin conversion: InlineSignExternal --- .../operation/InlineSignExternal.java | 74 ------------------- .../external/operation/InlineSignExternal.kt | 40 ++++++++++ 2 files changed, 40 insertions(+), 74 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/InlineSignExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/InlineSignExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java b/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java deleted file mode 100644 index 1a86002..0000000 --- a/external-sop/src/main/java/sop/external/operation/InlineSignExternal.java +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.enums.InlineSignAs; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.InlineSign; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link InlineSign} operation using an external SOP binary. - */ -public class InlineSignExternal implements InlineSign { - - private final List commandList = new ArrayList<>(); - private final List envList; - - private int keyCounter = 0; - private int withKeyPasswordCounter = 0; - - public InlineSignExternal(String binary, Properties environment) { - commandList.add(binary); - commandList.add("inline-sign"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public InlineSign noArmor() { - commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public InlineSign key(@Nonnull InputStream key) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, SOPGPException.UnsupportedAsymmetricAlgo, IOException { - String envVar = "KEY_" + keyCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(key)); - return this; - } - - @Override - @Nonnull - public InlineSign withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "WITH_KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public InlineSign mode(@Nonnull InlineSignAs mode) throws SOPGPException.UnsupportedOption { - commandList.add("--as=" + mode); - return this; - } - - @Override - @Nonnull - public Ready data(@Nonnull InputStream data) throws SOPGPException.KeyIsProtected, SOPGPException.ExpectedText { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/InlineSignExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/InlineSignExternal.kt new file mode 100644 index 0000000..a304e85 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/InlineSignExternal.kt @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.enums.InlineSignAs +import sop.external.ExternalSOP +import sop.operation.InlineSign + +/** Implementation of the [InlineSign] operation using an external SOP binary. */ +class InlineSignExternal(binary: String, environment: Properties) : InlineSign { + + private val commandList = mutableListOf(binary, "inline-sign") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun mode(mode: InlineSignAs): InlineSign = apply { commandList.add("--as=$mode") } + + override fun data(data: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data) + + override fun noArmor(): InlineSign = apply { commandList.add("--no-armor") } + + override fun key(key: InputStream): InlineSign = apply { + commandList.add("@ENV:KEY_$argCounter") + envList.add("KEY_$argCounter=${ExternalSOP.readString(key)}") + argCounter += 1 + } + + override fun withKeyPassword(password: ByteArray): InlineSign = apply { + commandList.add("--with-key-password=@ENV:WITH_KEY_PASSWORD_$argCounter") + envList.add("WITH_KEY_PASSWORD_$argCounter=${String(password)}") + argCounter += 1 + } +} From 8dc51b67a3e58a768000f38967eafeef4c05a913 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 17:59:04 +0100 Subject: [PATCH 297/444] Kotlin conversion: InlineVerifyExternal --- .../operation/InlineVerifyExternal.java | 122 ------------------ .../operation/InlineVerifyExternal.kt | 91 +++++++++++++ 2 files changed, 91 insertions(+), 122 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java b/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java deleted file mode 100644 index 10a2d47..0000000 --- a/external-sop/src/main/java/sop/external/operation/InlineVerifyExternal.java +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.ReadyWithResult; -import sop.Verification; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.InlineVerify; -import sop.util.UTCUtil; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Properties; - -/** - * Implementation of the {@link InlineVerify} operation using an external SOP binary. - */ -public class InlineVerifyExternal implements InlineVerify { - - private final ExternalSOP.TempDirProvider tempDirProvider; - private final List commandList = new ArrayList<>(); - private final List envList; - - private int certCounter = 0; - - public InlineVerifyExternal(String binary, Properties environment, ExternalSOP.TempDirProvider tempDirProvider) { - this.tempDirProvider = tempDirProvider; - commandList.add(binary); - commandList.add("inline-verify"); - envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public InlineVerify notBefore(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { - commandList.add("--not-before=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public InlineVerify notAfter(@Nonnull Date timestamp) throws SOPGPException.UnsupportedOption { - commandList.add("--not-after=" + UTCUtil.formatUTCDate(timestamp)); - return this; - } - - @Override - @Nonnull - public InlineVerify cert(@Nonnull InputStream cert) throws SOPGPException.BadData, IOException { - String envVar = "CERT_" + certCounter++; - commandList.add("@ENV:" + envVar); - envList.add(envVar + "=" + ExternalSOP.readString(cert)); - return this; - } - - @Override - @Nonnull - public ReadyWithResult> data(@Nonnull InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { - File tempDir = tempDirProvider.provideTempDirectory(); - - File verificationsOut = new File(tempDir, "verifications-out"); - verificationsOut.delete(); - commandList.add("--verifications-out=" + verificationsOut.getAbsolutePath()); - - String[] command = commandList.toArray(new String[0]); - String[] env = envList.toArray(new String[0]); - - try { - Process process = Runtime.getRuntime().exec(command, env); - OutputStream processOut = process.getOutputStream(); - InputStream processIn = process.getInputStream(); - - return new ReadyWithResult>() { - @Override - public List writeTo(@Nonnull OutputStream outputStream) throws IOException, SOPGPException.NoSignature { - byte[] buf = new byte[4096]; - int r; - while ((r = data.read(buf)) > 0) { - processOut.write(buf, 0, r); - } - - data.close(); - processOut.close(); - - - while ((r = processIn.read(buf)) > 0) { - outputStream.write(buf, 0 , r); - } - - processIn.close(); - outputStream.close(); - - ExternalSOP.finish(process); - - FileInputStream verificationsOutIn = new FileInputStream(verificationsOut); - BufferedReader reader = new BufferedReader(new InputStreamReader(verificationsOutIn)); - List verificationList = new ArrayList<>(); - String line; - while ((line = reader.readLine()) != null) { - verificationList.add(Verification.fromString(line.trim())); - } - - return verificationList; - } - }; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt new file mode 100644 index 0000000..ed769e3 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.* +import java.util.* +import sop.ReadyWithResult +import sop.Verification +import sop.Verification.Companion.fromString +import sop.external.ExternalSOP +import sop.external.ExternalSOP.Companion.finish +import sop.operation.InlineVerify +import sop.util.UTCUtil + +/** Implementation of the [InlineVerify] operation using an external SOP binary. */ +class InlineVerifyExternal( + binary: String, + environment: Properties, + private val tempDirProvider: ExternalSOP.TempDirProvider +) : InlineVerify { + + private val commandList = mutableListOf(binary, "inline-verify") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCounter = 0 + + override fun data(data: InputStream): ReadyWithResult> { + val tempDir = tempDirProvider.provideTempDirectory() + + val verificationsOut = File(tempDir, "verifications-out") + verificationsOut.delete() + commandList.add("--verifications-out=${verificationsOut.absolutePath}") + + try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val processOut = process.outputStream + val processIn = process.inputStream + + return object : ReadyWithResult>() { + override fun writeTo(outputStream: OutputStream): List { + val buf = ByteArray(4096) + var r: Int + while (data.read(buf).also { r = it } > 0) { + processOut.write(buf, 0, r) + } + + data.close() + processOut.close() + + while (processIn.read(buf).also { r = it } > 0) { + outputStream.write(buf, 0, r) + } + + processIn.close() + outputStream.close() + + finish(process) + + val verificationsOutIn = FileInputStream(verificationsOut) + val reader = BufferedReader(InputStreamReader(verificationsOutIn)) + val verificationList: MutableList = mutableListOf() + var line: String + while (reader.readLine().also { line = it } != null) { + verificationList.add(fromString(line.trim())) + } + + return verificationList + } + } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + override fun notBefore(timestamp: Date): InlineVerify = apply { + commandList.add("--not-before=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun notAfter(timestamp: Date): InlineVerify = apply { + commandList.add("--not-after=${UTCUtil.formatUTCDate(timestamp)}") + } + + override fun cert(cert: InputStream): InlineVerify = apply { + commandList.add("@ENV:CERT_$argCounter") + envList.add("CERT_$argCounter=${ExternalSOP.readString(cert)}") + argCounter += 1 + } +} From f2204dfd4d091d868e9435e672187ced4c58b56e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:06:35 +0100 Subject: [PATCH 298/444] Kotlin conversion: ListProfilesExternal --- .../operation/ListProfilesExternal.java | 50 ------------------- .../operation/ListProfilesExternal.kt | 36 +++++++++++++ 2 files changed, 36 insertions(+), 50 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ListProfilesExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java b/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java deleted file mode 100644 index 21d5e13..0000000 --- a/external-sop/src/main/java/sop/external/operation/ListProfilesExternal.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Profile; -import sop.external.ExternalSOP; -import sop.operation.ListProfiles; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -public class ListProfilesExternal implements ListProfiles { - - private final List commandList = new ArrayList<>(); - private final List envList; - - public ListProfilesExternal(String binary, Properties properties) { - this.commandList.add(binary); - this.commandList.add("list-profiles"); - this.envList = ExternalSOP.propertiesToEnv(properties); - } - - @Override - @Nonnull - public List subcommand(@Nonnull String command) { - commandList.add(command); - try { - String output = new String(ExternalSOP.executeProducingOperation(Runtime.getRuntime(), commandList, envList).getBytes()); - return toProfiles(output); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static List toProfiles(String output) { - List profiles = new ArrayList<>(); - for (String line : output.split("\n")) { - if (line.trim().isEmpty()) { - continue; - } - profiles.add(Profile.parse(line)); - } - return profiles; - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ListProfilesExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ListProfilesExternal.kt new file mode 100644 index 0000000..5e8ff89 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ListProfilesExternal.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.IOException +import java.util.Properties +import sop.Profile +import sop.external.ExternalSOP +import sop.operation.ListProfiles + +/** Implementation of the [ListProfiles] operation using an external SOP binary. */ +class ListProfilesExternal(binary: String, environment: Properties) : ListProfiles { + + private val commandList = mutableListOf(binary, "list-profiles") + private val envList = ExternalSOP.propertiesToEnv(environment) + + override fun subcommand(command: String): List { + return try { + String( + ExternalSOP.executeProducingOperation( + Runtime.getRuntime(), commandList.plus(command), envList) + .bytes) + .let { toProfiles(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + companion object { + @JvmStatic + private fun toProfiles(output: String): List = + output.split("\n").filter { it.isNotBlank() }.map { Profile.parse(it) } + } +} From 832a455c4ccb885d68ebb97e6b855d0e80faa24d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:11:24 +0100 Subject: [PATCH 299/444] Kotlin conversion: RevokeKeyExternal --- .../external/operation/RevokeKeyExternal.java | 52 ------------------- .../external/operation/RevokeKeyExternal.kt | 31 +++++++++++ 2 files changed, 31 insertions(+), 52 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/RevokeKeyExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java b/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java deleted file mode 100644 index 72ed549..0000000 --- a/external-sop/src/main/java/sop/external/operation/RevokeKeyExternal.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.Ready; -import sop.exception.SOPGPException; -import sop.external.ExternalSOP; -import sop.operation.RevokeKey; - -import javax.annotation.Nonnull; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; - -public class RevokeKeyExternal implements RevokeKey { - - private final List commandList = new ArrayList<>(); - private final List envList; - - private int withKeyPasswordCounter = 0; - - public RevokeKeyExternal(String binary, Properties environment) { - this.commandList.add(binary); - this.commandList.add("revoke-key"); - this.envList = ExternalSOP.propertiesToEnv(environment); - } - - @Override - @Nonnull - public RevokeKey noArmor() { - this.commandList.add("--no-armor"); - return this; - } - - @Override - @Nonnull - public RevokeKey withKeyPassword(@Nonnull byte[] password) throws SOPGPException.UnsupportedOption, SOPGPException.PasswordNotHumanReadable { - String envVar = "KEY_PASSWORD_" + withKeyPasswordCounter++; - commandList.add("--with-key-password=@ENV:" + envVar); - envList.add(envVar + "=" + new String(password)); - return this; - } - - @Override - @Nonnull - public Ready keys(@Nonnull InputStream keys) { - return ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keys); - } -} diff --git a/external-sop/src/main/kotlin/sop/external/operation/RevokeKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/RevokeKeyExternal.kt new file mode 100644 index 0000000..43795e6 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/RevokeKeyExternal.kt @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.Properties +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.RevokeKey + +/** Implementation of the [RevokeKey] operation using an external SOP binary. */ +class RevokeKeyExternal(binary: String, environment: Properties) : RevokeKey { + + private val commandList = mutableListOf(binary, "revoke-key") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + override fun noArmor(): RevokeKey = apply { commandList.add("--no-armor") } + + override fun withKeyPassword(password: ByteArray): RevokeKey = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") + envList.add("KEY_PASSWORD_$argCount=${String(password)}") + argCount += 1 + } + + override fun keys(keys: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, keys) +} From 3eaae149b721f046ae22b4835782f8d49a0d8efe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:34:30 +0100 Subject: [PATCH 300/444] Kotlin conversion: VersionExternal --- .../external/operation/VersionExternal.java | 163 ------------------ .../sop/external/operation/package-info.java | 8 - .../main/java/sop/external/package-info.java | 8 - .../sop/external/operation/VersionExternal.kt | 98 +++++++++++ 4 files changed, 98 insertions(+), 179 deletions(-) delete mode 100644 external-sop/src/main/java/sop/external/operation/VersionExternal.java delete mode 100644 external-sop/src/main/java/sop/external/operation/package-info.java delete mode 100644 external-sop/src/main/java/sop/external/package-info.java create mode 100644 external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt diff --git a/external-sop/src/main/java/sop/external/operation/VersionExternal.java b/external-sop/src/main/java/sop/external/operation/VersionExternal.java deleted file mode 100644 index ab2bbef..0000000 --- a/external-sop/src/main/java/sop/external/operation/VersionExternal.java +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.external.operation; - -import sop.external.ExternalSOP; -import sop.operation.Version; - -import javax.annotation.Nonnull; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Properties; - -/** - * Implementation of the {@link Version} operation using an external SOP binary. - */ -public class VersionExternal implements Version { - - private final Runtime runtime = Runtime.getRuntime(); - private final String binary; - private final Properties environment; - - public VersionExternal(String binaryName, Properties environment) { - this.binary = binaryName; - this.environment = environment; - } - - @Override - @Nonnull - public String getName() { - String[] command = new String[] {binary, "version"}; - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line = stdInput.readLine().trim(); - ExternalSOP.finish(process); - if (line.contains(" ")) { - return line.substring(0, line.lastIndexOf(" ")); - } - return line; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - @Nonnull - public String getVersion() { - String[] command = new String[] {binary, "version"}; - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line = stdInput.readLine().trim(); - ExternalSOP.finish(process); - if (line.contains(" ")) { - return line.substring(line.lastIndexOf(" ") + 1); - } - return line; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - @Nonnull - public String getBackendVersion() { - String[] command = new String[] {binary, "version", "--backend"}; - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = stdInput.readLine()) != null) { - sb.append(line).append('\n'); - } - ExternalSOP.finish(process); - return sb.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - @Nonnull - public String getExtendedVersion() { - String[] command = new String[] {binary, "version", "--extended"}; - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = stdInput.readLine()) != null) { - sb.append(line).append('\n'); - } - ExternalSOP.finish(process); - return sb.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public int getSopSpecRevisionNumber() { - String revision = getSopSpecVersion(); - String firstLine; - if (revision.contains("\n")) { - firstLine = revision.substring(0, revision.indexOf("\n")); - } else { - firstLine = revision; - } - - if (!firstLine.contains("-")) { - return -1; - } - - return Integer.parseInt(firstLine.substring(firstLine.lastIndexOf("-") + 1)); - } - - @Override - public boolean isSopSpecImplementationIncomplete() { - String revision = getSopSpecVersion(); - return revision.startsWith("~"); - } - - @Override - public String getSopSpecImplementationRemarks() { - String revision = getSopSpecVersion(); - if (revision.contains("\n")) { - String tail = revision.substring(revision.indexOf("\n") + 1).trim(); - - if (!tail.isEmpty()) { - return tail; - } - } - return null; - } - - @Override - @Nonnull - public String getSopSpecVersion() { - String[] command = new String[] {binary, "version", "--sop-spec"}; - String[] env = ExternalSOP.propertiesToEnv(environment).toArray(new String[0]); - try { - Process process = runtime.exec(command, env); - BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - StringBuilder sb = new StringBuilder(); - String line; - while ((line = stdInput.readLine()) != null) { - sb.append(line).append('\n'); - } - ExternalSOP.finish(process); - return sb.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/external-sop/src/main/java/sop/external/operation/package-info.java b/external-sop/src/main/java/sop/external/operation/package-info.java deleted file mode 100644 index 9c7dd29..0000000 --- a/external-sop/src/main/java/sop/external/operation/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Bindings for SOP subcommands to a SOP binary. - */ -package sop.external.operation; diff --git a/external-sop/src/main/java/sop/external/package-info.java b/external-sop/src/main/java/sop/external/package-info.java deleted file mode 100644 index 208985e..0000000 --- a/external-sop/src/main/java/sop/external/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Implementation of sop-java which delegates execution to a binary implementing the SOP command line interface. - */ -package sop.external; diff --git a/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt new file mode 100644 index 0000000..7e13fc1 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.IOException +import java.util.Properties +import sop.external.ExternalSOP +import sop.operation.Version + +/** Implementation of the [Version] operation using an external SOP binary. */ +class VersionExternal(binary: String, environment: Properties) : Version { + + private val commandList = listOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment) + + override fun getName(): String { + val info = executeForLine(commandList) + return if (info.contains(" ")) { + info.substring(0, info.lastIndexOf(" ")) + } else { + info + } + } + + override fun getVersion(): String { + val info = executeForLine(commandList) + return if (info.contains(" ")) { + info.substring(info.lastIndexOf(" ") + 1) + } else { + info + } + } + + override fun getBackendVersion(): String { + return executeForLines(commandList.plus("--backend")) + } + + override fun getExtendedVersion(): String { + return executeForLines(commandList.plus("--extended")) + } + + override fun getSopSpecRevisionNumber(): Int { + val revision = getSopSpecVersion() + val firstLine = + if (revision.contains("\n")) { + revision.substring(0, revision.indexOf("\n")) + } else { + revision + } + + if (!firstLine.contains("-")) { + return -1 + } + return Integer.parseInt(firstLine.substring(firstLine.lastIndexOf("-") + 1)) + } + + override fun isSopSpecImplementationIncomplete(): Boolean { + return getSopSpecVersion().startsWith("~") + } + + override fun getSopSpecImplementationRemarks(): String? { + val revision = getSopSpecVersion() + if (revision.contains("\n")) { + revision.substring(revision.indexOf("\n")).trim().takeIf { it.isNotBlank() } + } + return null + } + + override fun getSopSpecVersion(): String { + return executeForLines(commandList.plus("--sop-spec")) + } + + private fun executeForLine(commandList: List): String { + return try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val result = process.inputStream.bufferedReader().readLine() + ExternalSOP.finish(process) + result.trim() + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + private fun executeForLines(commandList: List): String { + return try { + val process = + Runtime.getRuntime().exec(commandList.toTypedArray(), envList.toTypedArray()) + val result = process.inputStream.bufferedReader().readLines().joinToString("\n") + ExternalSOP.finish(process) + result.trim() + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} From 6c952efca23ae932e9d8e6e691aa555f50953eb4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:50:34 +0100 Subject: [PATCH 301/444] Fix NPE in line iterator --- .../kotlin/sop/external/operation/InlineVerifyExternal.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt index ed769e3..bf0c66b 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/InlineVerifyExternal.kt @@ -62,9 +62,9 @@ class InlineVerifyExternal( val verificationsOutIn = FileInputStream(verificationsOut) val reader = BufferedReader(InputStreamReader(verificationsOutIn)) val verificationList: MutableList = mutableListOf() - var line: String + var line: String? while (reader.readLine().also { line = it } != null) { - verificationList.add(fromString(line.trim())) + verificationList.add(fromString(line!!.trim())) } return verificationList From 60758dfa2f71817530a4371a0c244456095d3b27 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:58:14 +0100 Subject: [PATCH 302/444] Update changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d736ded..f168283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ SPDX-License-Identifier: Apache-2.0 # Changelog ## 8.0.0-SNAPSHOT -- Rewrote API in Kotlin +- Rewrote `sop-java` in Kotlin +- Rewrote `sop-java-picocli` in Kotlin +- Rewrote `external-sop` in Kotlin - Update implementation to [SOP Specification revision 08](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-08.html). - Add `--no-armor` option to `revoke-key` and `change-key-password` subcommands - - `armor`: Deprecate `--label` option + - `armor`: Deprecate `--label` option in `sop-java` and remove in `sop-java-picocli` - `encrypt`: Add `--session-key-out` option - Slight API changes: - `sop.encrypt().plaintext()` now returns a `ReadyWithResult` instead of `Ready`. From 7eeb159f1248347bf2cdff36322cf03f7ec93894 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 18:59:50 +0100 Subject: [PATCH 303/444] SOP-Java 8.0.0 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f168283..4b5d562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 8.0.0-SNAPSHOT +## 8.0.0 - Rewrote `sop-java` in Kotlin - Rewrote `sop-java-picocli` in Kotlin - Rewrote `external-sop` in Kotlin diff --git a/version.gradle b/version.gradle index c5a9cb1..a7c71aa 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '8.0.0' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From ae83ddcff6adc598b3f17a7d2bcbf34279fc8619 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Nov 2023 19:01:50 +0100 Subject: [PATCH 304/444] SOP-Java 8.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index a7c71aa..b1d7770 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '8.0.0' - isSnapshot = false + shortVersion = '8.0.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 51d9c298379faebad4c63655ecfe9071b163a753 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 22 Nov 2023 17:11:24 +0100 Subject: [PATCH 305/444] decrypt --verify-with: Do not expect exit 3 when verifications is empty --- .../sop/cli/picocli/commands/DecryptCmdTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index c2c67ba..62070c2 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -21,6 +21,7 @@ import sop.operation.Decrypt; import sop.util.HexUtil; import sop.util.UTCUtil; +import javax.annotation.Nonnull; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; @@ -247,15 +248,17 @@ public class DecryptCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE) - public void assertNoSignatureExceptionCausesExit3() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { + public void assertNoVerificationsIsOkay() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { + File tempFile = File.createTempFile("verify-with-", ".tmp"); + File verifyOut = new File(tempFile.getParent(), "verifications.out"); + verifyOut.deleteOnExit(); when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult() { @Override - public DecryptionResult writeTo(OutputStream outputStream) throws SOPGPException.NoSignature { - throw new SOPGPException.NoSignature(); + public DecryptionResult writeTo(@Nonnull OutputStream outputStream) throws SOPGPException.NoSignature { + return new DecryptionResult(null, Collections.emptyList()); } }); - SopCLI.main(new String[] {"decrypt"}); + SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath(), "--verifications-out", verifyOut.getAbsolutePath()}); } @Test From e5e64003f3840726cbf92021e0019757a4ecfaff Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 22 Nov 2023 17:11:48 +0100 Subject: [PATCH 306/444] decrypt: Do not throw NoSignature exception when verifications is empty --- .../kotlin/sop/cli/picocli/commands/DecryptCmd.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt index 1e688b3..3f15f07 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt @@ -85,13 +85,10 @@ class DecryptCmd : AbstractSopCmd() { @Throws(IOException::class) private fun writeVerifyOut(result: DecryptionResult) { verifyOut?.let { - if (result.verifications.isEmpty()) { - val errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found") - throw NoSignature(errorMsg) - } - - getOutput(verifyOut).use { out -> - PrintWriter(out).use { pw -> result.verifications.forEach { pw.println(it) } } + getOutput(it).use { out -> + PrintWriter(out).use { pw -> + result.verifications.forEach { verification -> pw.println(verification) } + } } } } From 592aecd64605deabd7cb7b9d27167b35e79f6a31 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 22 Nov 2023 18:19:13 +0100 Subject: [PATCH 307/444] SOP-Java 8.0.1 --- CHANGELOG.md | 3 +++ version.gradle | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5d562..f188b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 8.0.1 +- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty. + ## 8.0.0 - Rewrote `sop-java` in Kotlin - Rewrote `sop-java-picocli` in Kotlin diff --git a/version.gradle b/version.gradle index b1d7770..74bcad6 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '8.0.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 7092baee4f664941c27304312b653899d5cd27da Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 22 Nov 2023 18:21:41 +0100 Subject: [PATCH 308/444] SOP-Java 8.0.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 74bcad6..434fdc5 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '8.0.1' - isSnapshot = false + shortVersion = '8.0.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From e2a568e73e490d2c96f0f84e99b348b40e456fcf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 26 Feb 2024 11:03:16 +0100 Subject: [PATCH 309/444] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..4f92d0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** + + +**Version** + +- `sop-java`: +- `pgpainless-core`: +- `bouncycastle`: + +**To Reproduce** + +``` +Example Code Block +``` + +**Expected behavior** + + +**Additional context** + From d5d7d67d6f400430d89693ca4066d60742e434ab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 17:58:04 +0100 Subject: [PATCH 310/444] Fix reuse compliance --- .reuse/dep5 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.reuse/dep5 b/.reuse/dep5 index 8feb5a2..f43556c 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -22,3 +22,8 @@ License: Apache-2.0 Files: external-sop/src/main/resources/sop/testsuite/external/* Copyright: 2023 the original author or authors License: Apache-2.0 + +# Github Issue Templates +Files: .github/ISSUE_TEMPLATE/* +Copyright: 2024 the original author or authors +License: Apache-2.0 From 03f8950b16c72aa5b5ab771a6b21e8e0e94f19eb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 18:05:35 +0100 Subject: [PATCH 311/444] Rename woodpecker files --- .woodpecker/{.build.yml => build.yml} | 0 .woodpecker/{.reuse.yml => reuse.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .woodpecker/{.build.yml => build.yml} (100%) rename .woodpecker/{.reuse.yml => reuse.yml} (100%) diff --git a/.woodpecker/.build.yml b/.woodpecker/build.yml similarity index 100% rename from .woodpecker/.build.yml rename to .woodpecker/build.yml diff --git a/.woodpecker/.reuse.yml b/.woodpecker/reuse.yml similarity index 100% rename from .woodpecker/.reuse.yml rename to .woodpecker/reuse.yml From 173bc55eb9a6110894d845e5eba8ef9ce5784c3e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 15:41:44 +0100 Subject: [PATCH 312/444] Fix javadoc reference --- external-sop/src/main/kotlin/sop/external/ExternalSOP.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index 3a0ef52..27c93ae 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -16,7 +16,7 @@ import sop.external.operation.* import sop.operation.* /** - * Implementation of the {@link SOP} API using an external SOP binary. + * Implementation of the [SOP] API using an external SOP binary. * * Instantiate an [ExternalSOP] object for the given binary and the given [TempDirProvider] using * empty environment variables. From a0e7356757780e96cd42a337d46011787f71eb7b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 15:42:54 +0100 Subject: [PATCH 313/444] Replace assumeTrue(false) with explicit TestAbortedException --- .../java/sop/testsuite/operation/VersionTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 73ba571..0b19d20 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.TestAbortedException; import sop.SOP; import java.util.stream.Stream; @@ -15,7 +16,6 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class VersionTest extends AbstractSOPTest { @@ -59,7 +59,7 @@ public class VersionTest extends AbstractSOPTest { try { sop.version().getSopSpecVersion(); } catch (RuntimeException e) { - assumeTrue(false); // SOP backend does not support this operation yet + throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet."); } String sopSpec = sop.version().getSopSpecVersion(); From 7b04275625de94b084da6b75102fb7b7141b9795 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 15:43:39 +0100 Subject: [PATCH 314/444] Add test ckecking that BadData is thrown if KEYS is passed for CERTS --- .../sop/testsuite/operation/EncryptDecryptTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java index 51c117a..df824ca 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -327,4 +327,15 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes()); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void passingSecretKeysForPublicKeysFails(SOP sop) { + assertThrows(SOPGPException.BadData.class, () -> + sop.encrypt() + .withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) + .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) + .toByteArrayAndResult() + .getBytes()); + } } From 34a05e96a103f392fff06dc9d901ebe611d32db9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 15:45:14 +0100 Subject: [PATCH 315/444] Move signature verification operations to sopv interface subset --- .../main/kotlin/sop/external/ExternalSOPV.kt | 53 ++++++++++ .../sop/external/operation/VersionExternal.kt | 4 + .../main/kotlin/sop/cli/picocli/SopVCLI.kt | 98 +++++++++++++++++++ .../sop/cli/picocli/commands/VersionCmd.kt | 6 ++ .../src/main/resources/msg_sop.properties | 2 + .../src/main/resources/msg_sop_de.properties | 2 + sop-java/src/main/kotlin/sop/SOP.kt | 26 +---- sop-java/src/main/kotlin/sop/SOPV.kt | 34 +++++++ .../src/main/kotlin/sop/operation/Version.kt | 10 ++ .../sop/testsuite/operation/VersionTest.java | 14 +++ 10 files changed, 224 insertions(+), 25 deletions(-) create mode 100644 external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt create mode 100644 sop-java/src/main/kotlin/sop/SOPV.kt diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt new file mode 100644 index 0000000..f22f947 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external + +import java.nio.file.Files +import java.util.* +import sop.SOPV +import sop.external.ExternalSOP.TempDirProvider +import sop.external.operation.DetachedVerifyExternal +import sop.external.operation.InlineVerifyExternal +import sop.external.operation.VersionExternal +import sop.operation.DetachedVerify +import sop.operation.InlineVerify +import sop.operation.Version + +/** + * Implementation of the [SOPV] API subset using an external sopv/sop binary. + * + * Instantiate an [ExternalSOPV] object for the given binary and the given [TempDirProvider] using + * empty environment variables. + * + * @param binaryName name / path of the sopv binary + * @param tempDirProvider custom tempDirProvider + */ +class ExternalSOPV( + private val binaryName: String, + private val properties: Properties = Properties(), + private val tempDirProvider: TempDirProvider = defaultTempDirProvider() +) : SOPV { + + override fun version(): Version = VersionExternal(binaryName, properties) + + override fun detachedVerify(): DetachedVerify = DetachedVerifyExternal(binaryName, properties) + + override fun inlineVerify(): InlineVerify = + InlineVerifyExternal(binaryName, properties, tempDirProvider) + + companion object { + + /** + * Default implementation of the [TempDirProvider] which stores temporary files in the + * systems temp dir ([Files.createTempDirectory]). + * + * @return default implementation + */ + @JvmStatic + fun defaultTempDirProvider(): TempDirProvider { + return TempDirProvider { Files.createTempDirectory("ext-sopv").toFile() } + } + } +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt index 7e13fc1..728f3b6 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/VersionExternal.kt @@ -68,6 +68,10 @@ class VersionExternal(binary: String, environment: Properties) : Version { return null } + override fun getSopVVersion(): String { + return executeForLines(commandList.plus("--sopv")) + } + override fun getSopSpecVersion(): String { return executeForLines(commandList.plus("--sop-spec")) } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt new file mode 100644 index 0000000..9a8b4b4 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli + +import java.util.* +import kotlin.system.exitProcess +import picocli.AutoComplete +import picocli.CommandLine +import sop.SOPV +import sop.cli.picocli.commands.* +import sop.exception.SOPGPException + +@CommandLine.Command( + name = "sopv", + resourceBundle = "msg_sop", + exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, + subcommands = + [ + // Meta subcommands + VersionCmd::class, + // signature verification subcommands + VerifyCmd::class, + InlineVerifyCmd::class, + // misc + CommandLine.HelpCommand::class, + AutoComplete.GenerateCompletion::class]) +class SopVCLI { + + companion object { + @JvmStatic private var sopvInstance: SOPV? = null + + @JvmStatic + fun getSopV(): SOPV = + checkNotNull(sopvInstance) { cliMsg.getString("sop.error.runtime.no_backend_set") } + + @JvmStatic + fun setSopVInstance(sopv: SOPV?) { + sopvInstance = sopv + } + + @JvmField var cliMsg: ResourceBundle = ResourceBundle.getBundle("msg_sop") + + @JvmField var EXECUTABLE_NAME = "sopv" + + @JvmField + @CommandLine.Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) + var stacktrace = false + + @JvmStatic + fun main(vararg args: String) { + val exitCode = execute(*args) + if (exitCode != 0) { + exitProcess(exitCode) + } + } + + @JvmStatic + fun execute(vararg args: String): Int { + // Set locale + CommandLine(InitLocale()).parseArgs(*args) + + // Re-set bundle with updated locale + cliMsg = ResourceBundle.getBundle("msg_sop") + + return CommandLine(SopVCLI::class.java) + .apply { + // explicitly set help command resource bundle + subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) + // Hide generate-completion command + subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) + // overwrite executable name + commandName = EXECUTABLE_NAME + // setup exception handling + executionExceptionHandler = SOPExecutionExceptionHandler() + exitCodeExceptionMapper = SOPExceptionExitCodeMapper() + isCaseInsensitiveEnumValuesAllowed = true + } + .execute(*args) + } + } + + /** + * Control the locale. + * + * @see Picocli Readme + */ + @CommandLine.Command + class InitLocale { + @CommandLine.Option(names = ["-l", "--locale"], descriptionKey = "sop.locale") + fun setLocale(locale: String) = Locale.setDefault(Locale(locale)) + + @CommandLine.Unmatched + var remainder: MutableList = + mutableListOf() // ignore any other parameters and options in the first parsing phase + } +} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt index 75197fe..8b1936a 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/VersionCmd.kt @@ -22,6 +22,7 @@ class VersionCmd : AbstractSopCmd() { @Option(names = ["--extended"]) var extended: Boolean = false @Option(names = ["--backend"]) var backend: Boolean = false @Option(names = ["--sop-spec"]) var sopSpec: Boolean = false + @Option(names = ["--sopv"]) var sopv: Boolean = false } override fun run() { @@ -47,5 +48,10 @@ class VersionCmd : AbstractSopCmd() { println(version.getSopSpecVersion()) return } + + if (exclusive!!.sopv) { + println(version.getSopVVersion()) + return + } } } diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 52c5368..7979eb3 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -2,7 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop +sopv.name=sopv usage.header=Stateless OpenPGP Protocol +sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset locale=Locale for description texts # Generic diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 5900f39..40a316d 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -2,7 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 sop.name=sop +sopv.name=sopv usage.header=Stateless OpenPGP Protocol +sopv.usage.header=Stateless OpenPGP Protocol - Signature Verification Interface Subset locale=Gebietsschema für Beschreibungstexte # Generic diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index e01763a..7fdd414 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -9,16 +9,13 @@ import sop.operation.ChangeKeyPassword import sop.operation.Dearmor import sop.operation.Decrypt import sop.operation.DetachedSign -import sop.operation.DetachedVerify import sop.operation.Encrypt import sop.operation.ExtractCert import sop.operation.GenerateKey import sop.operation.InlineDetach import sop.operation.InlineSign -import sop.operation.InlineVerify import sop.operation.ListProfiles import sop.operation.RevokeKey -import sop.operation.Version /** * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related @@ -26,10 +23,7 @@ import sop.operation.Version * intended for reuse. If you for example need to generate multiple keys, make a dedicated call to * [generateKey] once per key generation. */ -interface SOP { - - /** Get information about the implementations name and version. */ - fun version(): Version +interface SOP : SOPV { /** Generate a secret key. */ fun generateKey(): GenerateKey @@ -53,24 +47,6 @@ interface SOP { */ fun inlineSign(): InlineSign - /** - * Verify detached signatures. If you need to verify an inline-signed message, use - * [inlineVerify] instead. - */ - fun verify(): DetachedVerify = detachedVerify() - - /** - * Verify detached signatures. If you need to verify an inline-signed message, use - * [inlineVerify] instead. - */ - fun detachedVerify(): DetachedVerify - - /** - * Verify signatures of an inline-signed message. If you need to verify detached signatures over - * a message, use [detachedVerify] instead. - */ - fun inlineVerify(): InlineVerify - /** Detach signatures from an inline signed message. */ fun inlineDetach(): InlineDetach diff --git a/sop-java/src/main/kotlin/sop/SOPV.kt b/sop-java/src/main/kotlin/sop/SOPV.kt new file mode 100644 index 0000000..d331559 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/SOPV.kt @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import sop.operation.DetachedVerify +import sop.operation.InlineVerify +import sop.operation.Version + +/** Subset of [SOP] implementing only OpenPGP signature verification. */ +interface SOPV { + + /** Get information about the implementations name and version. */ + fun version(): Version + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun verify(): DetachedVerify = detachedVerify() + + /** + * Verify detached signatures. If you need to verify an inline-signed message, use + * [inlineVerify] instead. + */ + fun detachedVerify(): DetachedVerify + + /** + * Verify signatures of an inline-signed message. If you need to verify detached signatures over + * a message, use [detachedVerify] instead. + */ + fun inlineVerify(): InlineVerify +} diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt index 9b3bd8a..5f26491 100644 --- a/sop-java/src/main/kotlin/sop/operation/Version.kt +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -4,6 +4,9 @@ package sop.operation +import kotlin.jvm.Throws +import sop.exception.SOPGPException + interface Version { /** @@ -97,4 +100,11 @@ interface Version { * @return remarks or null */ fun getSopSpecImplementationRemarks(): String? + + /** + * Return the single-line SEMVER version of the sopv interface subset it provides complete + * coverage of. If the implementation does not provide complete coverage for any sopv interface, + * this method throws an [SOPGPException.UnsupportedOption] instead. + */ + @Throws(SOPGPException.UnsupportedOption::class) fun getSopVVersion(): String } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java index 0b19d20..f836935 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentest4j.TestAbortedException; import sop.SOP; +import sop.exception.SOPGPException; import java.util.stream.Stream; @@ -72,4 +73,17 @@ public class VersionTest extends AbstractSOPTest { int sopRevision = sop.version().getSopSpecRevisionNumber(); assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision)); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void sopVVersionTest(SOP sop) { + try { + sop.version().getSopVVersion(); + } catch (SOPGPException.UnsupportedOption e) { + throw new TestAbortedException( + "Implementation does (gracefully) not provide coverage for any sopv interface version."); + } catch (RuntimeException e) { + throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version."); + } + } } From ae2389cabf9dbab2972aa1aa40cc42042ee5cf92 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 17 Mar 2024 15:45:29 +0100 Subject: [PATCH 316/444] Bump version to 10.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 434fdc5..80c8092 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '8.0.2' + shortVersion = '10.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From 1df57475493a18df3303f0162ec5c98259d53387 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:37:46 +0200 Subject: [PATCH 317/444] EncryptExternal: Fix parameter passing for --sign-with option --- .../src/main/kotlin/sop/external/operation/EncryptExternal.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt index 6f1cc6c..12d9cff 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt @@ -36,7 +36,7 @@ class EncryptExternal( override fun mode(mode: EncryptAs): Encrypt = apply { commandList.add("--as=$mode") } override fun signWith(key: InputStream): Encrypt = apply { - commandList.add("--sign-with@ENV:SIGN_WITH_$argCounter") + commandList.add("--sign-with=@ENV:SIGN_WITH_$argCounter") envList.add("SIGN_WITH_$argCounter=${ExternalSOP.readString(key)}") argCounter += 1 } From 4388f00dc08e936a402d79db52e3bdad47f0c71b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:00:30 +0200 Subject: [PATCH 318/444] Fix NPE in DecryptExternal when reading lines --- .../main/kotlin/sop/external/operation/DecryptExternal.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt index b68d3a6..e0a900d 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt @@ -108,8 +108,8 @@ class DecryptExternal( finish(process) val sessionKeyOutIn = FileInputStream(sessionKeyOut) - var line = readString(sessionKeyOutIn) - val sessionKey = SessionKey.fromString(line.trim { it <= ' ' }) + var line: String? = readString(sessionKeyOutIn) + val sessionKey = line?.let { l -> SessionKey.fromString(l.trim { it <= ' ' }) } sessionKeyOutIn.close() sessionKeyOut.delete() @@ -118,7 +118,7 @@ class DecryptExternal( val verifyOutIn = FileInputStream(verifyOut) val reader = BufferedReader(InputStreamReader(verifyOutIn)) while (reader.readLine().also { line = it } != null) { - verifications.add(Verification.fromString(line.trim())) + line?.let { verifications.add(Verification.fromString(it.trim())) } } reader.close() } From 65945e0094fa6a4a1e22cb0d6c71f0e745193c1e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 12:41:40 +0200 Subject: [PATCH 319/444] Fix external-sop decrypt --verifications-out --- .../src/main/kotlin/sop/external/operation/DecryptExternal.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt index e0a900d..1e6d6a2 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/DecryptExternal.kt @@ -78,7 +78,7 @@ class DecryptExternal( val verifyOut = File(tempDir, "verifications-out") verifyOut.delete() if (requireVerification) { - commandList.add("--verify-out=${verifyOut.absolutePath}") + commandList.add("--verifications-out=${verifyOut.absolutePath}") } try { From 2d4bc24c64b1a7e68a2e6596e5e07a63b940ccd5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 13:29:40 +0200 Subject: [PATCH 320/444] Abort tests on UnsupportedOption --- .../testsuite/operation/AbstractSOPTest.java | 5 +++++ .../sop/testsuite/AbortOnUnsupportedOption.kt | 12 +++++++++++ .../AbortOnUnsupportedOptionExtension.kt | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt create mode 100644 sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java index 8595898..6c163f7 100644 --- a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java +++ b/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java @@ -5,8 +5,11 @@ package sop.testsuite.operation; import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.Arguments; import sop.SOP; +import sop.testsuite.AbortOnUnsupportedOption; +import sop.testsuite.AbortOnUnsupportedOptionExtension; import sop.testsuite.SOPInstanceFactory; import java.lang.reflect.InvocationTargetException; @@ -15,6 +18,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +@ExtendWith(AbortOnUnsupportedOptionExtension.class) +@AbortOnUnsupportedOption public abstract class AbstractSOPTest { private static final List backends = new ArrayList<>(); diff --git a/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt b/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt new file mode 100644 index 0000000..cf99671 --- /dev/null +++ b/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite + +import java.lang.annotation.Inherited + +@Target(AnnotationTarget.TYPE) +@Retention(AnnotationRetention.RUNTIME) +@Inherited +annotation class AbortOnUnsupportedOption diff --git a/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt b/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt new file mode 100644 index 0000000..809c78f --- /dev/null +++ b/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite + +import org.junit.jupiter.api.Assumptions +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler +import sop.exception.SOPGPException + +class AbortOnUnsupportedOptionExtension : TestExecutionExceptionHandler { + override fun handleTestExecutionException(context: ExtensionContext, throwable: Throwable) { + val testClass = context.requiredTestClass + val annotation = testClass.getAnnotation(AbortOnUnsupportedOption::class.java) + if (annotation != null && SOPGPException.UnsupportedOption::class.isInstance(throwable)) { + Assumptions.assumeTrue(false, "Test aborted due to: " + throwable.message) + } + throw throwable + } +} From d25a424adc1328c9e10ffd72ac55c8f95188fdf5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:16:11 +0200 Subject: [PATCH 321/444] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f188b0b..2c266f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 10.0.1-SNAPSHOT +- Remove `label()` option from `Armor` operation +- Fix exit code for 'Missing required option/parameter' error +- Fix `revoke-key`: Allow for multiple invocations of `--with-key-password` option + +## 10.0.0 +- Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html). + - Throw `BadData` when passing KEYS where CERTS are expected + - Introduce `sopv` interface subset with revision `1.0` + - Add `sop version --sopv` + ## 8.0.1 - `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty. From a5232703959a9c6c1c1059d7052940044a518dad Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 21 Mar 2024 13:43:12 +0100 Subject: [PATCH 322/444] Update spec revision and badge link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa7e5a5..0efd41f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 8](https://img.shields.io/badge/Spec%20Revision-8-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/08/) +[![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/10/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) From 8d7e89098fe462d2a8b5246f45ac5d05d2a21c70 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 13:40:25 +0200 Subject: [PATCH 323/444] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c266f1..2cb5b9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ SPDX-License-Identifier: Apache-2.0 - Remove `label()` option from `Armor` operation - Fix exit code for 'Missing required option/parameter' error - Fix `revoke-key`: Allow for multiple invocations of `--with-key-password` option +- Fix `EncryptExternal` use of `--sign-with` parameter +- Fix `NullPointerException` in `DecryptExternal` when reading lines +- Fix `DecryptExternal` use of `verifications-out` +- Test suite: Ignore tests if `UnsupportedOption` is thrown ## 10.0.0 - Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html). From f7530e3263b69c73ca1c45b4e2ca3a3d182127b9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 13:43:45 +0200 Subject: [PATCH 324/444] Bump logback to 1.4.14 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 80c8092..7572c34 100644 --- a/version.gradle +++ b/version.gradle @@ -12,7 +12,7 @@ allprojects { jsrVersion = '3.0.2' junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - logbackVersion = '1.2.11' + logbackVersion = '1.4.14' mockitoVersion = '4.5.1' picocliVersion = '4.6.3' slf4jVersion = '1.7.36' From 261ac212b8f91ef58557f8e09278edd2dfc6ac81 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 13:44:17 +0200 Subject: [PATCH 325/444] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb5b9d..37c38fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ SPDX-License-Identifier: Apache-2.0 - Fix `NullPointerException` in `DecryptExternal` when reading lines - Fix `DecryptExternal` use of `verifications-out` - Test suite: Ignore tests if `UnsupportedOption` is thrown +- Bump `logback-core` to `1.4.14` ## 10.0.0 - Update implementation to [SOP Specification revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html). From 354ef8841aafe4aa0a66c76e70d5db5208223e7d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 13:46:48 +0200 Subject: [PATCH 326/444] SOP-Java 10.0.1 --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 7572c34..fbac859 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.0.0' - isSnapshot = true + shortVersion = '10.0.1' + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 7014dbcfb7bec164cf8e216eaeca09ca6ca56bf5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 14:36:41 +0200 Subject: [PATCH 327/444] SOP-Java 10.0.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index fbac859..a018d80 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.0.1' - isSnapshot = false + shortVersion = '10.0.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 63d80452247a4f596eb49904ee57028092a7cdd7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 14:41:34 +0200 Subject: [PATCH 328/444] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c38fc..ff1f4cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 10.0.1-SNAPSHOT +## 10.0.1 - Remove `label()` option from `Armor` operation - Fix exit code for 'Missing required option/parameter' error - Fix `revoke-key`: Allow for multiple invocations of `--with-key-password` option From a90f9be0e44144e5545d0422455ef4cd85373f68 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 15:50:31 +0200 Subject: [PATCH 329/444] Downgrade logback-core to 1.2.13 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index a018d80..65a8a27 100644 --- a/version.gradle +++ b/version.gradle @@ -12,7 +12,7 @@ allprojects { jsrVersion = '3.0.2' junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - logbackVersion = '1.4.14' + logbackVersion = '1.2.13' mockitoVersion = '4.5.1' picocliVersion = '4.6.3' slf4jVersion = '1.7.36' From a09f10fe8547d906030bc11aab0f1fd0d48fed2b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 15:50:40 +0200 Subject: [PATCH 330/444] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1f4cc..493a3b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 10.0.2-SNAPSHOT +- Downgrade `logback-core` to `1.2.13` + ## 10.0.1 - Remove `label()` option from `Armor` operation - Fix exit code for 'Missing required option/parameter' error From 1958614fac828a52519f34a78429ac6e92c3d935 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 16:20:33 +0200 Subject: [PATCH 331/444] SOP-Java 10.0.2 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 493a3b9..6c0c0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 10.0.2-SNAPSHOT +## 10.0.2 - Downgrade `logback-core` to `1.2.13` ## 10.0.1 diff --git a/version.gradle b/version.gradle index 65a8a27..256871d 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '10.0.2' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From b3f446fe8d49aad790b7f0ed264d3491ff0a0d22 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 16:22:06 +0200 Subject: [PATCH 332/444] SOP-Java 10.0.3-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 256871d..db61bcc 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.0.2' - isSnapshot = false + shortVersion = '10.0.3' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 42a16a4f6d46199db279f6daf9a9b70859841b7b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Oct 2024 13:15:06 +0100 Subject: [PATCH 333/444] Fix password parameter passing in change-key-password --- .../sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt index 0c2eb4a..be37309 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ChangeKeyPasswordCmd.kt @@ -33,9 +33,15 @@ class ChangeKeyPasswordCmd : AbstractSopCmd() { changeKeyPassword.noArmor() } - oldKeyPasswords.forEach { changeKeyPassword.oldKeyPassphrase(it) } + oldKeyPasswords.forEach { + val password = stringFromInputStream(getInput(it)) + changeKeyPassword.oldKeyPassphrase(password) + } - newKeyPassword?.let { changeKeyPassword.newKeyPassphrase(it) } + newKeyPassword?.let { + val password = stringFromInputStream(getInput(it)) + changeKeyPassword.newKeyPassphrase(password) + } try { changeKeyPassword.keys(System.`in`).writeTo(System.out) From 375dd6578903eb23cac395fe86b861a86afbce60 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:13:12 +0200 Subject: [PATCH 334/444] revoke-key command: Allow for multiple '--with-key-password' options --- .../main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt index 0b93ac5..b9b1015 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/RevokeKeyCmd.kt @@ -19,8 +19,8 @@ class RevokeKeyCmd : AbstractSopCmd() { @Option(names = ["--no-armor"], negatable = true) var armor = true - @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") - var withKeyPassword: String? = null + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD", arity = "0..*") + var withKeyPassword: List = listOf() override fun run() { val revokeKey = throwIfUnsupportedSubcommand(SopCLI.getSop().revokeKey(), "revoke-key") @@ -29,9 +29,9 @@ class RevokeKeyCmd : AbstractSopCmd() { revokeKey.noArmor() } - withKeyPassword?.let { + for (passwordIn in withKeyPassword) { try { - val password = stringFromInputStream(getInput(it)) + val password = stringFromInputStream(getInput(passwordIn)) revokeKey.withKeyPassword(password) } catch (e: SOPGPException.UnsupportedOption) { val errorMsg = From f35fd6c1ae39cd9cf2b79bd0d5a541c191203e11 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Oct 2024 13:53:57 +0100 Subject: [PATCH 335/444] Update changelog --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c0c0c1..13a87c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 10.0.3-SNAPSHOT +- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report) +- Backport: `revoke-key`: Allow for multiple password options + ## 10.0.2 - Downgrade `logback-core` to `1.2.13` @@ -25,6 +29,10 @@ SPDX-License-Identifier: Apache-2.0 - Introduce `sopv` interface subset with revision `1.0` - Add `sop version --sopv` +## 8.0.2 +- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report) +- Backport: `revoke-key`: Allow for multiple password options + ## 8.0.1 - `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty. @@ -43,6 +51,13 @@ SPDX-License-Identifier: Apache-2.0 - Change `EncryptAs` values into lowercase - Change `SignAs` values into lowercase +## 7.0.2 +- CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report) +- Backport: revoke-key command: Allow for multiple '--with-key-password' options + +## 7.0.1 +- `decrypt`: Do not throw `NoSignature` exception (exit code 3) if `--verify-with` is provided, but `VERIFICATIONS` is empty. + ## 7.0.0 - Update implementation to [SOP Specification revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html). - Add support for new `revoke-key` subcommand From c136d40fa7de9fa7e1979b1ffdd36f1fe44c9474 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Oct 2024 13:54:31 +0100 Subject: [PATCH 336/444] SOP-Java 10.0.3 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index db61bcc..396af1a 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '10.0.3' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From e6c9d6f43d3a37f0e9b89462d5f13a004b2c85f2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Oct 2024 14:06:37 +0100 Subject: [PATCH 337/444] SOP-Java 10.0.4-SNAPSHOT --- CHANGELOG.md | 2 +- version.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a87c3..feee975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 10.0.3-SNAPSHOT +## 10.0.3 - CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report) - Backport: `revoke-key`: Allow for multiple password options diff --git a/version.gradle b/version.gradle index 396af1a..e8bcbef 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.0.3' - isSnapshot = false + shortVersion = '10.0.4' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From ac00b68694a62fa50c67717910dc59fb0d7e4e0a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 21 Mar 2024 13:43:25 +0100 Subject: [PATCH 338/444] Add description of external-sop module --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0efd41f..baeb874 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ The repository contains the following modules: * [sop-java](/sop-java) defines a set of Java interfaces describing the Stateless OpenPGP Protocol. * [sop-java-picocli](/sop-java-picocli) contains a wrapper application that transforms the `sop-java` API into a command line application compatible with the SOP-CLI specification. +* [external-sop](/external-sop) contains an API implementation that can be used to forward API calls to a SOP executable, +allowing to delegate the implementation logic to an arbitrary SOP CLI implementation. ## Known Implementations (Please expand!) From e7778cb0d299ae5c479b538d29f8e639c71be2e7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Mar 2024 21:50:01 +0100 Subject: [PATCH 339/444] Remove deprecated junit5-system-exit Replaced with custom test DSL that avoids System.exit --- sop-java-picocli/build.gradle | 5 +- .../test/java/sop/cli/picocli/SOPTest.java | 11 +- .../cli/picocli/commands/ArmorCmdTest.java | 12 +- .../cli/picocli/commands/DearmorCmdTest.java | 8 +- .../cli/picocli/commands/DecryptCmdTest.java | 137 +++++----- .../cli/picocli/commands/EncryptCmdTest.java | 121 +++++---- .../picocli/commands/ExtractCertCmdTest.java | 22 +- .../picocli/commands/GenerateKeyCmdTest.java | 30 ++- .../picocli/commands/InlineDetachCmdTest.java | 12 +- .../sop/cli/picocli/commands/SignCmdTest.java | 61 +++-- .../cli/picocli/commands/VerifyCmdTest.java | 70 ++++-- .../cli/picocli/commands/VersionCmdTest.java | 41 ++- .../assertions/SopExecutionAssertions.java | 235 ++++++++++++++++++ 13 files changed, 550 insertions(+), 215 deletions(-) create mode 100644 sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 438ef50..0596ad3 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -12,15 +12,12 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - // Testing Exit Codes in JUnit - // https://todd.ginsberg.com/post/testing-system-exit/ - testImplementation "com.ginsberg:junit5-system-exit:$junitSysExitVersion" - // Mocking Components testImplementation "org.mockito:mockito-core:$mockitoVersion" // SOP implementation(project(":sop-java")) + testImplementation(testFixtures(project(":sop-java"))) // CLI implementation "info.picocli:picocli:$picocliVersion" diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 68b32be..fe49472 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -6,12 +6,13 @@ package sop.cli.picocli; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedSubcommand; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; @@ -34,20 +35,18 @@ import sop.operation.Version; public class SOPTest { @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedSubcommand.EXIT_CODE) public void assertExitOnInvalidSubcommand() { SOP sop = mock(SOP.class); SopCLI.setSopInstance(sop); - SopCLI.main(new String[] {"invalid"}); + assertUnsupportedSubcommand(() -> SopCLI.execute("invalid")); } @Test - @ExpectSystemExitWithStatus(1) public void assertThrowsIfNoSOPBackendSet() { SopCLI.setSopInstance(null); - // At this point, no SOP backend is set, so an InvalidStateException triggers exit(1) - SopCLI.main(new String[] {"armor"}); + // At this point, no SOP backend is set, so an InvalidStateException triggers error code 1 + assertGenericError(() -> SopCLI.execute("armor")); } @Test diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java index da211e0..3dd4c7c 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ArmorCmdTest.java @@ -4,8 +4,6 @@ package sop.cli.picocli.commands; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; -import com.ginsberg.junit.exit.FailOnSystemExit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.Ready; @@ -24,6 +22,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; public class ArmorCmdTest { @@ -42,24 +42,22 @@ public class ArmorCmdTest { @Test public void assertDataIsAlwaysCalled() throws SOPGPException.BadData, IOException { - SopCLI.main(new String[] {"armor"}); + assertSuccess(() -> SopCLI.execute("armor")); verify(armor, times(1)).data((InputStream) any()); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void ifBadDataExit41() throws SOPGPException.BadData, IOException { when(armor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"armor"}); + assertBadData(() -> SopCLI.execute("armor")); } @Test - @FailOnSystemExit public void ifNoErrorsNoExit() { when(sop.armor()).thenReturn(armor); - SopCLI.main(new String[] {"armor"}); + assertSuccess(() -> SopCLI.execute("armor")); } private static Ready nopReady() { diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DearmorCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DearmorCmdTest.java index 875eaed..b0a9fd8 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DearmorCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DearmorCmdTest.java @@ -9,12 +9,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.Ready; @@ -48,14 +49,13 @@ public class DearmorCmdTest { @Test public void assertDataIsCalled() throws IOException, SOPGPException.BadData { - SopCLI.main(new String[] {"dearmor"}); + assertSuccess(() -> SopCLI.execute("dearmor")); verify(dearmor, times(1)).data((InputStream) any()); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void assertBadDataCausesExit41() throws IOException, SOPGPException.BadData { when(dearmor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException("invalid armor"))); - SopCLI.main(new String[] {"dearmor"}); + assertBadData(() -> SopCLI.execute("dearmor")); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java index 62070c2..b7cb8bc 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/DecryptCmdTest.java @@ -4,7 +4,6 @@ package sop.cli.picocli.commands; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; @@ -42,6 +41,18 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertCannotDecrypt; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertIncompleteVerification; +import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput; +import static sop.testsuite.assertions.SopExecutionAssertions.assertOutputExists; +import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption; public class DecryptCmdTest { @@ -74,47 +85,47 @@ public class DecryptCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException { when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.MissingArg("Missing arguments.")); - SopCLI.main(new String[] {"decrypt"}); + assertMissingArg(() -> SopCLI.execute("decrypt")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt, IOException { when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"decrypt"}); + assertBadData(() -> SopCLI.execute("decrypt")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE) public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable"); when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable()); - SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertPasswordNotHumanReadable(() -> + SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath()) + ); } @Test public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { File passwordFile = TestFileUtil.writeTempStringFile("orange"); - SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertSuccess(() -> SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath())); verify(decrypt, times(1)).withPassword("orange"); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { File passwordFile = TestFileUtil.writeTempStringFile("swordfish"); when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported.")); - SopCLI.main(new String[] {"decrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("decrypt", "--with-password", passwordFile.getAbsolutePath()) + ); } @Test public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption { Date now = new Date(); - SopCLI.main(new String[] {"decrypt"}); + assertSuccess(() -> SopCLI.execute("decrypt")); verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotAfter( ArgumentMatchers.argThat(argument -> { @@ -125,7 +136,8 @@ public class DecryptCmdTest { @Test public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"decrypt", "--verify-not-before", "-", "--verify-not-after", "-"}); + assertSuccess(() -> + SopCLI.execute("decrypt", "--verify-not-before", "-", "--verify-not-after", "-")); verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME); } @@ -138,54 +150,57 @@ public class DecryptCmdTest { return Math.abs(now.getTime() - argument.getTime()) <= 1000; }; - SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now", "--verify-not-after", "now"}); + assertSuccess(() -> + SopCLI.execute("decrypt", "--verify-not-before", "now", "--verify-not-after", "now")); verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff)); verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff)); } @Test - @ExpectSystemExitWithStatus(1) public void assertMalformedDateInNotBeforeCausesExit1() { // ParserException causes exit(1) - SopCLI.main(new String[] {"decrypt", "--verify-not-before", "invalid"}); + assertGenericError(() -> + SopCLI.execute("decrypt", "--verify-not-before", "invalid")); } @Test - @ExpectSystemExitWithStatus(1) public void assertMalformedDateInNotAfterCausesExit1() { // ParserException causes exit(1) - SopCLI.main(new String[] {"decrypt", "--verify-not-after", "invalid"}); + assertGenericError(() -> + SopCLI.execute("decrypt", "--verify-not-after", "invalid")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption { - when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); - SopCLI.main(new String[] {"decrypt", "--verify-not-after", "now"}); + when(decrypt.verifyNotAfter(any())).thenThrow( + new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); + assertUnsupportedOption(() -> + SopCLI.execute("decrypt", "--verify-not-after", "now")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption { - when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); - SopCLI.main(new String[] {"decrypt", "--verify-not-before", "now"}); + when(decrypt.verifyNotBefore(any())).thenThrow( + new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); + assertUnsupportedOption(() -> + SopCLI.execute("decrypt", "--verify-not-before", "now")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE) public void assertExistingSessionKeyOutFileCausesExit59() throws IOException { File tempFile = File.createTempFile("existing-session-key-", ".tmp"); tempFile.deleteOnExit(); - SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()}); + assertOutputExists(() -> + SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void assertWhenSessionKeyCannotBeExtractedExit37() throws IOException { Path tempDir = Files.createTempDirectory("session-key-out-dir"); File tempFile = new File(tempDir.toFile(), "session-key"); tempFile.deleteOnExit(); - SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("decrypt", "--session-key-out", tempFile.getAbsolutePath())); } @Test @@ -210,8 +225,10 @@ public class DecryptCmdTest { File verificationsFile = new File(tempDir.toFile(), "verifications"); File keyFile = new File(tempDir.toFile(), "key.asc"); keyFile.createNewFile(); - SopCLI.main(new String[] {"decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), - "--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", keyFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("decrypt", "--session-key-out", sessionKeyFile.getAbsolutePath(), + "--verifications-out", verificationsFile.getAbsolutePath(), "--verify-with", + keyFile.getAbsolutePath())); ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream(); try (FileInputStream fileIn = new FileInputStream(sessionKeyFile)) { @@ -241,10 +258,10 @@ public class DecryptCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.CannotDecrypt.EXIT_CODE) public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException { when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.CannotDecrypt()); - SopCLI.main(new String[] {"decrypt"}); + assertCannotDecrypt(() -> + SopCLI.execute("decrypt")); } @Test @@ -258,30 +275,32 @@ public class DecryptCmdTest { return new DecryptionResult(null, Collections.emptyList()); } }); - SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath(), "--verifications-out", verifyOut.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath(), "--verifications-out", + verifyOut.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void badDataInVerifyWithCausesExit41() throws IOException, SOPGPException.BadData { when(decrypt.verifyWithCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); File tempFile = File.createTempFile("verify-with-", ".tmp"); - SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("decrypt", "--verify-with", tempFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void unexistentCertFileCausesExit61() { - SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"}); + assertMissingInput(() -> + SopCLI.execute("decrypt", "--verify-with", "invalid")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE) public void existingVerifyOutCausesExit59() throws IOException { File certFile = File.createTempFile("existing-verify-out-cert", ".asc"); File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp"); - SopCLI.main(new String[] {"decrypt", "--verifications-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); + assertOutputExists(() -> SopCLI.execute("decrypt", "--verifications-out", + existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath())); } @Test @@ -305,7 +324,9 @@ public class DecryptCmdTest { } }); - SopCLI.main(new String[] {"decrypt", "--verifications-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("decrypt", "--verifications-out", verifyOut.getAbsolutePath(), + "--verify-with", certFile.getAbsolutePath())); try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) { String line = reader.readLine(); assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line); @@ -320,66 +341,64 @@ public class DecryptCmdTest { File sessionKeyFile1 = TestFileUtil.writeTempStringFile(key1.toString()); File sessionKeyFile2 = TestFileUtil.writeTempStringFile(key2.toString()); - SopCLI.main(new String[] {"decrypt", - "--with-session-key", sessionKeyFile1.getAbsolutePath(), - "--with-session-key", sessionKeyFile2.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("decrypt", + "--with-session-key", sessionKeyFile1.getAbsolutePath(), + "--with-session-key", sessionKeyFile2.getAbsolutePath())); verify(decrypt).withSessionKey(key1); verify(decrypt).withSessionKey(key2); } @Test - @ExpectSystemExitWithStatus(1) public void assertMalformedSessionKeysResultInExit1() throws IOException { File sessionKeyFile = TestFileUtil.writeTempStringFile("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"); - SopCLI.main(new String[] {"decrypt", - "--with-session-key", sessionKeyFile.getAbsolutePath()}); + assertGenericError(() -> + SopCLI.execute("decrypt", + "--with-session-key", sessionKeyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void assertBadDataInKeysResultsInExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); File tempKeyFile = File.createTempFile("key-", ".tmp"); - SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); + assertBadData(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void assertKeyFileNotFoundCausesExit61() { - SopCLI.main(new String[] {"decrypt", "nonexistent-key"}); + assertMissingInput(() -> SopCLI.execute("decrypt", "nonexistent-key")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE) public void assertProtectedKeyCausesExit67() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); File tempKeyFile = File.createTempFile("key-", ".tmp"); - SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); + assertKeyIsProtected(() -> SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException { when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new IOException())); File tempKeyFile = File.createTempFile("key-", ".tmp"); - SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()}); + assertUnsupportedAsymmetricAlgo(() -> + SopCLI.execute("decrypt", tempKeyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void assertMissingPassphraseFileCausesExit61() { - SopCLI.main(new String[] {"decrypt", "--with-password", "missing"}); + assertMissingInput(() -> + SopCLI.execute("decrypt", "--with-password", "missing")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void assertMissingSessionKeyFileCausesExit61() { - SopCLI.main(new String[] {"decrypt", "--with-session-key", "missing"}); + assertMissingInput(() -> + SopCLI.execute("decrypt", "--with-session-key", "missing")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.IncompleteVerification.EXIT_CODE) public void verifyOutWithoutVerifyWithCausesExit23() { - SopCLI.main(new String[] {"decrypt", "--verifications-out", "out.file"}); + assertIncompleteVerification(() -> + SopCLI.execute("decrypt", "--verifications-out", "out.file")); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java index 09346af..85ae052 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/EncryptCmdTest.java @@ -4,7 +4,6 @@ package sop.cli.picocli.commands; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +27,17 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertCertCannotEncrypt; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyCannotSign; +import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput; +import static sop.testsuite.assertions.SopExecutionAssertions.assertPasswordNotHumanReadable; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption; public class EncryptCmdTest { @@ -50,48 +60,50 @@ public class EncryptCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) - public void missingBothPasswordAndCertFileCauseExit19() { - SopCLI.main(new String[] {"encrypt", "--no-armor"}); + public void missingBothPasswordAndCertFileCausesMissingArg() { + assertMissingArg(() -> + SopCLI.execute("encrypt", "--no-armor")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) - public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption { + public void as_unsupportedEncryptAsCausesUnsupportedOption() throws SOPGPException.UnsupportedOption { when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported.")); - SopCLI.main(new String[] {"encrypt", "--as", "Binary"}); + assertUnsupportedOption(() -> + SopCLI.execute("encrypt", "--as", "Binary")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) - public void as_invalidModeOptionCausesExit37() { - SopCLI.main(new String[] {"encrypt", "--as", "invalid"}); + public void as_invalidModeOptionCausesUnsupportedOption() { + assertUnsupportedOption(() -> + SopCLI.execute("encrypt", "--as", "invalid")); } @Test public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption, IOException { File passwordFile = TestFileUtil.writeTempStringFile("0rbit"); for (EncryptAs mode : EncryptAs.values()) { - SopCLI.main(new String[] {"encrypt", "--as", mode.name(), "--with-password", passwordFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("encrypt", "--as", mode.name(), + "--with-password", passwordFile.getAbsolutePath())); verify(encrypt, times(1)).mode(mode); } } @Test - @ExpectSystemExitWithStatus(SOPGPException.PasswordNotHumanReadable.EXIT_CODE) - public void withPassword_notHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { + public void withPassword_notHumanReadablePasswordCausesPWNotHumanReadable() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable()); File passwordFile = TestFileUtil.writeTempStringFile("pretendThisIsNotReadable"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertPasswordNotHumanReadable(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) - public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { + public void withPassword_unsupportedWithPasswordCausesUnsupportedOption() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption, IOException { when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported.")); File passwordFile = TestFileUtil.writeTempStringFile("orange"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath())); } @Test @@ -99,99 +111,107 @@ public class EncryptCmdTest { File keyFile1 = File.createTempFile("sign-with-1-", ".asc"); File keyFile2 = File.createTempFile("sign-with-2-", ".asc"); File passwordFile = TestFileUtil.writeTempStringFile("password"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile1.getAbsolutePath(), "--sign-with", keyFile2.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), + "--sign-with", keyFile1.getAbsolutePath(), + "--sign-with", keyFile2.getAbsolutePath())); verify(encrypt, times(2)).signWith((InputStream) any()); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) - public void signWith_nonExistentKeyFileCausesExit61() { - SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"}); + public void signWith_nonExistentKeyFileCausesMissingInput() { + assertMissingInput(() -> + SopCLI.execute("encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE) - public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { + public void signWith_keyIsProtectedCausesKeyIsProtected() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); File keyFile = File.createTempFile("sign-with", ".asc"); File passwordFile = TestFileUtil.writeTempStringFile("starship"); - SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", passwordFile.getAbsolutePath()}); + assertKeyIsProtected(() -> + SopCLI.execute("encrypt", "--sign-with", keyFile.getAbsolutePath(), + "--with-password", passwordFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) - public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { + public void signWith_unsupportedAsymmetricAlgoCausesUnsupportedAsymAlgo() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); File keyFile = File.createTempFile("sign-with", ".asc"); File passwordFile = TestFileUtil.writeTempStringFile("123456"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); + assertUnsupportedAsymmetricAlgo(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), + "--sign-with", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE) - public void signWith_certCannotSignCausesExit79() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData { + public void signWith_certCannotSignCausesKeyCannotSign() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData { when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign()); File keyFile = File.createTempFile("sign-with", ".asc"); File passwordFile = TestFileUtil.writeTempStringFile("dragon"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); + assertKeyCannotSign(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), + "--sign-with", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) - public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { + public void signWith_badDataCausesBadData() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException { when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); File keyFile = File.createTempFile("sign-with", ".asc"); File passwordFile = TestFileUtil.writeTempStringFile("orange"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--sign-with", keyFile.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), + "--sign-with", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) - public void cert_nonExistentCertFileCausesExit61() { - SopCLI.main(new String[] {"encrypt", "invalid.asc"}); + public void cert_nonExistentCertFileCausesMissingInput() { + assertMissingInput(() -> + SopCLI.execute("encrypt", "invalid.asc")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) - public void cert_unsupportedAsymmetricAlgorithmCausesExit13() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { + public void cert_unsupportedAsymmetricAlgorithmCausesUnsupportedAsymAlg() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); File certFile = File.createTempFile("cert", ".asc"); - SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); + assertUnsupportedAsymmetricAlgo(() -> + SopCLI.execute("encrypt", certFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.CertCannotEncrypt.EXIT_CODE) - public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { + public void cert_certCannotEncryptCausesCertCannotEncrypt() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.CertCannotEncrypt("Certificate cannot encrypt.", new Exception())); File certFile = File.createTempFile("cert", ".asc"); - SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); + assertCertCannotEncrypt(() -> + SopCLI.execute("encrypt", certFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) - public void cert_badDataCausesExit41() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { + public void cert_badDataCausesBadData() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData { when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); File certFile = File.createTempFile("cert", ".asc"); - SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("encrypt", certFile.getAbsolutePath())); } @Test public void noArmor_notCalledByDefault() throws IOException { File passwordFile = TestFileUtil.writeTempStringFile("clownfish"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath())); verify(encrypt, never()).noArmor(); } @Test public void noArmor_callGetsPassedDown() throws IOException { File passwordFile = TestFileUtil.writeTempStringFile("monkey"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor"}); + assertSuccess(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath(), "--no-armor")); verify(encrypt, times(1)).noArmor(); } @Test - @ExpectSystemExitWithStatus(1) - public void writeTo_ioExceptionCausesExit1() throws IOException { + public void writeTo_ioExceptionCausesGenericError() throws IOException { when(encrypt.plaintext((InputStream) any())).thenReturn(new ReadyWithResult() { @Override public EncryptionResult writeTo(@NotNull OutputStream outputStream) throws IOException, SOPGPException { @@ -199,6 +219,7 @@ public class EncryptCmdTest { } }); File passwordFile = TestFileUtil.writeTempStringFile("wildcat"); - SopCLI.main(new String[] {"encrypt", "--with-password", passwordFile.getAbsolutePath()}); + assertGenericError(() -> + SopCLI.execute("encrypt", "--with-password", passwordFile.getAbsolutePath())); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ExtractCertCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ExtractCertCmdTest.java index 12f837d..3b046a0 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ExtractCertCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/ExtractCertCmdTest.java @@ -10,12 +10,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.Ready; @@ -45,32 +47,34 @@ public class ExtractCertCmdTest { @Test public void noArmor_notCalledByDefault() { - SopCLI.main(new String[] {"extract-cert"}); + assertSuccess(() -> + SopCLI.execute("extract-cert")); verify(extractCert, never()).noArmor(); } @Test public void noArmor_passedDown() { - SopCLI.main(new String[] {"extract-cert", "--no-armor"}); + assertSuccess(() -> + SopCLI.execute("extract-cert", "--no-armor")); verify(extractCert, times(1)).noArmor(); } @Test - @ExpectSystemExitWithStatus(1) - public void key_ioExceptionCausesExit1() throws IOException, SOPGPException.BadData { + public void key_ioExceptionCausesGenericError() throws IOException, SOPGPException.BadData { when(extractCert.key((InputStream) any())).thenReturn(new Ready() { @Override public void writeTo(OutputStream outputStream) throws IOException { throw new IOException(); } }); - SopCLI.main(new String[] {"extract-cert"}); + assertGenericError(() -> + SopCLI.execute("extract-cert")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) - public void key_badDataCausesExit41() throws IOException, SOPGPException.BadData { + public void key_badDataCausesBadData() throws IOException, SOPGPException.BadData { when(extractCert.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"extract-cert"}); + assertBadData(() -> + SopCLI.execute("extract-cert")); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/GenerateKeyCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/GenerateKeyCmdTest.java index e7ebf1a..126c851 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/GenerateKeyCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/GenerateKeyCmdTest.java @@ -10,11 +10,14 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedAsymmetricAlgo; import java.io.IOException; import java.io.OutputStream; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InOrder; @@ -47,19 +50,22 @@ public class GenerateKeyCmdTest { @Test public void noArmor_notCalledByDefault() { - SopCLI.main(new String[] {"generate-key", "Alice"}); + assertSuccess(() -> + SopCLI.execute("generate-key", "Alice")); verify(generateKey, never()).noArmor(); } @Test public void noArmor_passedDown() { - SopCLI.main(new String[] {"generate-key", "--no-armor", "Alice"}); + assertSuccess(() -> + SopCLI.execute("generate-key", "--no-armor", "Alice")); verify(generateKey, times(1)).noArmor(); } @Test public void userId_multipleUserIdsPassedDownInProperOrder() { - SopCLI.main(new String[] {"generate-key", "Alice ", "Bob "}); + assertSuccess(() -> + SopCLI.execute("generate-key", "Alice ", "Bob ")); InOrder inOrder = Mockito.inOrder(generateKey); inOrder.verify(generateKey).userId("Alice "); @@ -69,30 +75,32 @@ public class GenerateKeyCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) public void missingArgumentCausesExit19() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { // TODO: RFC4880-bis and the current Stateless OpenPGP CLI spec allow keys to have no user-ids, // so we might want to change this test in the future. when(generateKey.generate()).thenThrow(new SOPGPException.MissingArg("Missing user-id.")); - SopCLI.main(new String[] {"generate-key"}); + assertMissingArg(() -> + SopCLI.execute("generate-key")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE) public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception())); - SopCLI.main(new String[] {"generate-key", "Alice"}); + assertUnsupportedAsymmetricAlgo(() -> + SopCLI.execute("generate-key", "Alice")); + } @Test - @ExpectSystemExitWithStatus(1) - public void ioExceptionCausesExit1() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { + public void ioExceptionCausesGenericError() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException { when(generateKey.generate()).thenReturn(new Ready() { @Override public void writeTo(OutputStream outputStream) throws IOException { throw new IOException(); } }); - SopCLI.main(new String[] {"generate-key", "Alice"}); + + assertGenericError(() -> + SopCLI.execute("generate-key", "Alice")); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java index 3a16c61..a230aaa 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/InlineDetachCmdTest.java @@ -4,7 +4,6 @@ package sop.cli.picocli.commands; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.ReadyWithResult; @@ -26,6 +25,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; public class InlineDetachCmdTest { @@ -41,9 +42,9 @@ public class InlineDetachCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) - public void testMissingSignaturesOutResultsInExit19() { - SopCLI.main(new String[] {"inline-detach"}); + public void testMissingSignaturesOutResultsInMissingArg() { + assertMissingArg(() -> + SopCLI.execute("inline-detach")); } @Test @@ -67,7 +68,8 @@ public class InlineDetachCmdTest { } }); - SopCLI.main(new String[] {"inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor"}); + assertSuccess(() -> + SopCLI.execute("inline-detach", "--signatures-out", tempFile.getAbsolutePath(), "--no-armor")); verify(inlineDetach, times(1)).noArmor(); verify(inlineDetach, times(1)).message((InputStream) any()); } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java index c3d6b59..324d39a 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/SignCmdTest.java @@ -10,13 +10,20 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertExpectedText; +import static sop.testsuite.assertions.SopExecutionAssertions.assertGenericError; +import static sop.testsuite.assertions.SopExecutionAssertions.assertKeyIsProtected; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingArg; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.ReadyWithResult; @@ -54,70 +61,77 @@ public class SignCmdTest { @Test public void as_optionsAreCaseInsensitive() { - SopCLI.main(new String[] {"sign", "--as", "Binary", keyFile.getAbsolutePath()}); - SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()}); - SopCLI.main(new String[] {"sign", "--as", "BINARY", keyFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("sign", "--as", "Binary", keyFile.getAbsolutePath())); + assertSuccess(() -> + SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath())); + assertSuccess(() -> + SopCLI.execute("sign", "--as", "BINARY", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void as_invalidOptionCausesExit37() { - SopCLI.main(new String[] {"sign", "--as", "Invalid", keyFile.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("sign", "--as", "Invalid", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { when(detachedSign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported.")); - SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("sign", "--as", "binary", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void key_nonExistentKeyFileCausesExit61() { - SopCLI.main(new String[] {"sign", "invalid.asc"}); + assertMissingInput(() -> + SopCLI.execute("sign", "invalid.asc")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.KeyIsProtected.EXIT_CODE) public void key_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); - SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); + assertKeyIsProtected(() -> + SopCLI.execute("sign", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("sign", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingArg.EXIT_CODE) public void key_missingKeyFileCausesExit19() { - SopCLI.main(new String[] {"sign"}); + assertMissingArg(() -> + SopCLI.execute("sign")); } @Test public void noArmor_notCalledByDefault() { - SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("sign", keyFile.getAbsolutePath())); verify(detachedSign, never()).noArmor(); } @Test public void noArmor_passedDown() { - SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("sign", "--no-armor", keyFile.getAbsolutePath())); verify(detachedSign, times(1)).noArmor(); } @Test public void withKeyPassword_passedDown() { - SopCLI.main(new String[] {"sign", "--with-key-password", passFile.getAbsolutePath(), keyFile.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("sign", + "--with-key-password", passFile.getAbsolutePath(), + keyFile.getAbsolutePath())); verify(detachedSign, times(1)).withKeyPassword("sw0rdf1sh"); } @Test - @ExpectSystemExitWithStatus(1) public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText { when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult() { @Override @@ -125,13 +139,14 @@ public class SignCmdTest { throw new IOException(); } }); - SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); + assertGenericError(() -> + SopCLI.execute("sign", keyFile.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.ExpectedText.EXIT_CODE) public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText { when(detachedSign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText()); - SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); + assertExpectedText(() -> + SopCLI.execute("sign", keyFile.getAbsolutePath())); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java index 50a8043..3c9724f 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VerifyCmdTest.java @@ -10,6 +10,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertBadData; +import static sop.testsuite.assertions.SopExecutionAssertions.assertMissingInput; +import static sop.testsuite.assertions.SopExecutionAssertions.assertNoSignature; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption; import java.io.ByteArrayOutputStream; import java.io.File; @@ -21,7 +26,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -76,60 +80,75 @@ public class VerifyCmdTest { @Test public void notAfter_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); - SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notAfter(date); } @Test public void notAfter_now() throws SOPGPException.UnsupportedOption { Date now = new Date(); - SopCLI.main(new String[] {"verify", "--not-after", "now", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-after", "now", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notAfter(dateMatcher(now)); } @Test public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"verify", "--not-after", "-", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-after", "-", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notAfter(AbstractSopCmd.END_OF_TIME); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { when(detachedVerify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); - SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("verify", "--not-after", "2019-10-29T18:36:45Z", + signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test public void notBefore_passedDown() throws SOPGPException.UnsupportedOption, ParseException { Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"); - SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-before", "2019-10-29T18:36:45Z", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notBefore(date); } @Test public void notBefore_now() throws SOPGPException.UnsupportedOption { Date now = new Date(); - SopCLI.main(new String[] {"verify", "--not-before", "now", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-before", "now", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notBefore(dateMatcher(now)); } @Test public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"verify", "--not-before", "-", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", "--not-before", "-", + signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { when(detachedVerify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); - SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertUnsupportedOption(() -> + SopCLI.execute("verify", "--not-before", "2019-10-29T18:36:45Z", + signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption { - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); verify(detachedVerify, times(1)).notAfter(dateMatcher(new Date())); verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); } @@ -139,43 +158,43 @@ public class VerifyCmdTest { } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void cert_fileNotFoundCausesExit61() { - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"}); + assertMissingInput(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), "invalid.asc")); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void cert_badDataCausesExit41() throws SOPGPException.BadData, IOException { when(detachedVerify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.MissingInput.EXIT_CODE) public void signature_fileNotFoundCausesExit61() { - SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()}); + assertMissingInput(() -> + SopCLI.execute("verify", "invalid.sig", cert.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void signature_badDataCausesExit41() throws SOPGPException.BadData, IOException { when(detachedVerify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.NoSignature.EXIT_CODE) public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature()); - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertNoSignature(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test - @ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE) public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertBadData(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); } @Test @@ -192,7 +211,8 @@ public class VerifyCmdTest { ByteArrayOutputStream out = new ByteArrayOutputStream(); System.setOut(new PrintStream(out)); - SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); + assertSuccess(() -> + SopCLI.execute("verify", signature.getAbsolutePath(), cert.getAbsolutePath())); System.setOut(originalSout); diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VersionCmdTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VersionCmdTest.java index e284e35..92850bd 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VersionCmdTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/commands/VersionCmdTest.java @@ -4,19 +4,19 @@ package sop.cli.picocli.commands; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.ginsberg.junit.exit.ExpectSystemExitWithStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sop.SOP; import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; import sop.operation.Version; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static sop.testsuite.assertions.SopExecutionAssertions.assertSuccess; +import static sop.testsuite.assertions.SopExecutionAssertions.assertUnsupportedOption; + public class VersionCmdTest { private Version version; @@ -29,6 +29,8 @@ public class VersionCmdTest { when(version.getVersion()).thenReturn("1.0"); when(version.getExtendedVersion()).thenReturn("MockSop Extended Version Information"); when(version.getBackendVersion()).thenReturn("Foo"); + when(version.getSopSpecVersion()).thenReturn("draft-dkg-openpgp-stateless-cli-XX"); + when(version.getSopVVersion()).thenReturn("1.0"); when(sop.version()).thenReturn(version); SopCLI.setSopInstance(sop); @@ -36,26 +38,41 @@ public class VersionCmdTest { @Test public void assertVersionCommandWorks() { - SopCLI.main(new String[] {"version"}); + assertSuccess(() -> + SopCLI.execute("version")); verify(version, times(1)).getVersion(); verify(version, times(1)).getName(); } @Test public void assertExtendedVersionCommandWorks() { - SopCLI.main(new String[] {"version", "--extended"}); + assertSuccess(() -> + SopCLI.execute("version", "--extended")); verify(version, times(1)).getExtendedVersion(); } @Test public void assertBackendVersionCommandWorks() { - SopCLI.main(new String[] {"version", "--backend"}); + assertSuccess(() -> + SopCLI.execute("version", "--backend")); verify(version, times(1)).getBackendVersion(); } @Test - @ExpectSystemExitWithStatus(SOPGPException.UnsupportedOption.EXIT_CODE) + public void assertSpecVersionCommandWorks() { + assertSuccess(() -> + SopCLI.execute("version", "--sop-spec")); + } + + @Test + public void assertSOPVVersionCommandWorks() { + assertSuccess(() -> + SopCLI.execute("version", "--sopv")); + } + + @Test public void assertInvalidOptionResultsInExit37() { - SopCLI.main(new String[] {"version", "--invalid"}); + assertUnsupportedOption(() -> + SopCLI.execute("version", "--invalid")); } } diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java b/sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java new file mode 100644 index 0000000..bd07f0b --- /dev/null +++ b/sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.assertions; + +import sop.exception.SOPGPException; + +import java.util.function.IntSupplier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +/** + * DSL for testing the return values of SOP method calls. + */ +public class SopExecutionAssertions { + + /** + * Assert that the execution of the given function returns 0. + * + * @param function function to execute + */ + public static void assertSuccess(IntSupplier function) { + assertEquals(0, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns a generic error with error code 1. + * + * @param function function to execute. + */ + public static void assertGenericError(IntSupplier function) { + assertEquals(1, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns a non-zero error code. + * + * @param function function to execute + */ + public static void assertAnyError(IntSupplier function) { + assertNotEquals(0, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 3 + * (which corresponds to {@link sop.exception.SOPGPException.NoSignature}). + * + * @param function function to execute. + */ + public static void assertNoSignature(IntSupplier function) { + assertEquals(SOPGPException.NoSignature.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 13 + * (which corresponds to {@link sop.exception.SOPGPException.UnsupportedAsymmetricAlgo}). + * + * @param function function to execute. + */ + public static void assertUnsupportedAsymmetricAlgo(IntSupplier function) { + assertEquals(SOPGPException.UnsupportedAsymmetricAlgo.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 17 + * (which corresponds to {@link sop.exception.SOPGPException.CertCannotEncrypt}). + * + * @param function function to execute. + */ + public static void assertCertCannotEncrypt(IntSupplier function) { + assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 19 + * (which corresponds to {@link sop.exception.SOPGPException.MissingArg}). + * + * @param function function to execute. + */ + public static void assertMissingArg(IntSupplier function) { + assertEquals(SOPGPException.MissingArg.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 23 + * (which corresponds to {@link sop.exception.SOPGPException.IncompleteVerification}). + * + * @param function function to execute. + */ + public static void assertIncompleteVerification(IntSupplier function) { + assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 29 + * (which corresponds to {@link sop.exception.SOPGPException.CannotDecrypt}). + * + * @param function function to execute. + */ + public static void assertCannotDecrypt(IntSupplier function) { + assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 31 + * (which corresponds to {@link sop.exception.SOPGPException.PasswordNotHumanReadable}). + * + * @param function function to execute. + */ + public static void assertPasswordNotHumanReadable(IntSupplier function) { + assertEquals(SOPGPException.PasswordNotHumanReadable.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 37 + * (which corresponds to {@link sop.exception.SOPGPException.UnsupportedOption}). + * + * @param function function to execute. + */ + public static void assertUnsupportedOption(IntSupplier function) { + assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 41 + * (which corresponds to {@link sop.exception.SOPGPException.BadData}). + * + * @param function function to execute. + */ + public static void assertBadData(IntSupplier function) { + assertEquals(SOPGPException.BadData.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 53 + * (which corresponds to {@link sop.exception.SOPGPException.ExpectedText}). + * + * @param function function to execute. + */ + public static void assertExpectedText(IntSupplier function) { + assertEquals(SOPGPException.ExpectedText.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 59 + * (which corresponds to {@link sop.exception.SOPGPException.OutputExists}). + * + * @param function function to execute. + */ + public static void assertOutputExists(IntSupplier function) { + assertEquals(SOPGPException.OutputExists.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 61 + * (which corresponds to {@link sop.exception.SOPGPException.MissingInput}). + * + * @param function function to execute. + */ + public static void assertMissingInput(IntSupplier function) { + assertEquals(SOPGPException.MissingInput.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 67 + * (which corresponds to {@link sop.exception.SOPGPException.KeyIsProtected}). + * + * @param function function to execute. + */ + public static void assertKeyIsProtected(IntSupplier function) { + assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 69 + * (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSubcommand}). + * + * @param function function to execute. + */ + public static void assertUnsupportedSubcommand(IntSupplier function) { + assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 71 + * (which corresponds to {@link sop.exception.SOPGPException.UnsupportedSpecialPrefix}). + * + * @param function function to execute. + */ + public static void assertUnsupportedSpecialPrefix(IntSupplier function) { + assertEquals(SOPGPException.UnsupportedSpecialPrefix.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 73 + * (which corresponds to {@link sop.exception.SOPGPException.AmbiguousInput}). + * + * @param function function to execute. + */ + public static void assertAmbiguousInput(IntSupplier function) { + assertEquals(SOPGPException.AmbiguousInput.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 79 + * (which corresponds to {@link sop.exception.SOPGPException.KeyCannotSign}). + * + * @param function function to execute. + */ + public static void assertKeyCannotSign(IntSupplier function) { + assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 83 + * (which corresponds to {@link sop.exception.SOPGPException.IncompatibleOptions}). + * + * @param function function to execute. + */ + public static void assertIncompatibleOptions(IntSupplier function) { + assertEquals(SOPGPException.IncompatibleOptions.EXIT_CODE, function.getAsInt()); + } + + /** + * Assert that the execution of the given function returns error code 89 + * (which corresponds to {@link sop.exception.SOPGPException.UnsupportedProfile}). + * + * @param function function to execute. + */ + public static void assertUnsupportedProfile(IntSupplier function) { + assertEquals(SOPGPException.UnsupportedProfile.EXIT_CODE, function.getAsInt()); + } +} From bb026bcbebcffeda0333333f3725f00e769e24f5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 27 Mar 2024 21:57:04 +0100 Subject: [PATCH 340/444] Mark ProxyOutputStream as deprecated --- sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt index da6c4fa..a608c89 100644 --- a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt +++ b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt @@ -15,6 +15,7 @@ import java.io.OutputStream * class is useful if we need to provide an [OutputStream] at one point in time when the final * target output stream is not yet known. */ +@Deprecated("Marked for removal.") class ProxyOutputStream : OutputStream() { private val buffer = ByteArrayOutputStream() private var swapped: OutputStream? = null From 547acdb740e5dae2f24dca06795794ea9dc7c640 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 30 Mar 2024 19:00:09 +0100 Subject: [PATCH 341/444] Remove label() option from armor() operation --- .../kotlin/sop/external/operation/ArmorExternal.kt | 3 --- .../src/main/resources/msg_armor.properties | 1 - .../src/main/resources/msg_armor_de.properties | 1 - sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt | 14 -------------- sop-java/src/main/kotlin/sop/operation/Armor.kt | 12 ------------ 5 files changed, 31 deletions(-) delete mode 100644 sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt diff --git a/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt index f80c57b..b202746 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/ArmorExternal.kt @@ -7,7 +7,6 @@ package sop.external.operation import java.io.InputStream import java.util.Properties import sop.Ready -import sop.enums.ArmorLabel import sop.exception.SOPGPException import sop.external.ExternalSOP import sop.operation.Armor @@ -18,8 +17,6 @@ class ArmorExternal(binary: String, environment: Properties) : Armor { private val commandList: MutableList = mutableListOf(binary, "armor") private val envList: List = ExternalSOP.propertiesToEnv(environment) - override fun label(label: ArmorLabel): Armor = apply { commandList.add("--label=$label") } - @Throws(SOPGPException.BadData::class) override fun data(data: InputStream): Ready = ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, data) diff --git a/sop-java-picocli/src/main/resources/msg_armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties index 2f4e217..b4dcb59 100644 --- a/sop-java-picocli/src/main/resources/msg_armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Add ASCII Armor to standard input -label=Label to be used in the header and tail of the armoring stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 diff --git a/sop-java-picocli/src/main/resources/msg_armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties index a2303e9..4c365a8 100644 --- a/sop-java-picocli/src/main/resources/msg_armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: Apache-2.0 usage.header=Schütze Standard-Eingabe mit ASCII Armor -label=Label für Kopf- und Fußzeile der ASCII Armor stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 diff --git a/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt b/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt deleted file mode 100644 index 8b4e2cd..0000000 --- a/sop-java/src/main/kotlin/sop/enums/ArmorLabel.kt +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.enums - -@Deprecated("Use of armor labels is deprecated.") -enum class ArmorLabel { - auto, - sig, - key, - cert, - message -} diff --git a/sop-java/src/main/kotlin/sop/operation/Armor.kt b/sop-java/src/main/kotlin/sop/operation/Armor.kt index e89708b..be7f1a3 100644 --- a/sop-java/src/main/kotlin/sop/operation/Armor.kt +++ b/sop-java/src/main/kotlin/sop/operation/Armor.kt @@ -7,22 +7,10 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.Ready -import sop.enums.ArmorLabel import sop.exception.SOPGPException.BadData -import sop.exception.SOPGPException.UnsupportedOption interface Armor { - /** - * Overrides automatic detection of label. - * - * @param label armor label - * @return builder instance - */ - @Deprecated("Use of armor labels is deprecated and will be removed in a future release.") - @Throws(UnsupportedOption::class) - fun label(label: ArmorLabel): Armor - /** * Armor the provided data. * From eadea08d3c6cdfb67b0d9699e00e1f41df7f5851 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 9 Jul 2024 14:29:22 +0200 Subject: [PATCH 342/444] Add new SOPGPException types related to hardware modules --- .../kotlin/sop/exception/SOPGPException.kt | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 2473258..bc9131f 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -305,4 +305,36 @@ abstract class SOPGPException : RuntimeException { const val EXIT_CODE = 89 } } + + /** + * The sop implementation supports some form of hardware-backed secret keys, but could not + * identify the hardware device. + */ + class NoHardwareKeyFound : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 97 + } + } + + /** + * The sop implementation tried to use a hardware-backed secret key, but the cryptographic + * hardware refused the operation for some reason other than a bad PIN or password. + */ + class HardwareKeyFailure : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 101 + } + } } From a8a753536a3e438b6fc8cd4a4fa2d99d646cdcb0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 9 Jul 2024 14:39:03 +0200 Subject: [PATCH 343/444] Add translations for new hardware exception error messages --- sop-java-picocli/src/main/resources/msg_sop.properties | 2 ++ sop-java-picocli/src/main/resources/msg_sop_de.properties | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 7979eb3..94e4dc0 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -36,6 +36,8 @@ usage.exitCodeList.17=73:Ambiguous input (a filename matching the designator alr usage.exitCodeList.18=79:Key is not signing capable usage.exitCodeList.19=83:Options were supplied that are incompatible with each other usage.exitCodeList.20=89:The requested profile is unsupported, or the indicated subcommand does not accept profiles +usage.exitCodeList.21=97:The implementation supports some form of hardware-backed secret keys, but could not identify the hardware device +usage.exitCodeList.22=101:The implementation tried to use a hardware-backed secret key, but the cryptographic hardware refused the operation for some reason other than a bad PIN or password ## SHARED RESOURCES stacktrace=Print stacktrace diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 40a316d..786fa36 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -36,6 +36,8 @@ usage.exitCodeList.17=73:Mehrdeutige Eingabe (ein Dateiname, der dem Bezeichner usage.exitCodeList.18=79:Schlüssel ist nicht fähig zu signieren usage.exitCodeList.19=83:Miteinander inkompatible Optionen spezifiziert usage.exitCodeList.20=89:Das angeforderte Profil wird nicht unterstützt, oder der angegebene Unterbefehl akzeptiert keine Profile +usage.exitCodeList.21=97:Die Anwendung unterstützt hardwaregestützte private Schlüssel, aber kann das Gerät nicht identifizieren +usage.exitCodeList.22=101:Die Anwendung versuchte, einen hardwaregestützten Schlüssel zu verwenden, aber das Gerät lehnte den Vorgang aus einem anderen Grund als einer falschen PIN oder einem falschen Passwort ab ## SHARED RESOURCES stacktrace=Stacktrace ausgeben From 1fd316185184f3981f16741040d64a18ccafd6f2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 19:20:33 +0200 Subject: [PATCH 344/444] Properly match MissingArg exception code --- .../main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt index 29aa77b..5778bb9 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SOPExceptionExitCodeMapper.kt @@ -21,6 +21,8 @@ class SOPExceptionExitCodeMapper : IExitCodeExceptionMapper { // Unmatched subcommand SOPGPException.UnsupportedSubcommand.EXIT_CODE } + } else if (exception is MissingParameterException) { + SOPGPException.MissingArg.EXIT_CODE } else if (exception is ParameterException) { // Invalid option (e.g. `--as invalid`) SOPGPException.UnsupportedOption.EXIT_CODE From 471947ef9ccbf1a7cce8056c0367adff7e1b2c19 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 19:38:59 +0200 Subject: [PATCH 345/444] Fix woodpecker warnings --- .woodpecker/build.yml | 2 ++ .woodpecker/reuse.yml | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index ff59c4e..fab075a 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -1,5 +1,7 @@ steps: run: + when: + event: push image: gradle:7.6-jdk11-jammy commands: # Install Sequoia-SOP diff --git a/.woodpecker/reuse.yml b/.woodpecker/reuse.yml index d78c61e..b278a39 100644 --- a/.woodpecker/reuse.yml +++ b/.woodpecker/reuse.yml @@ -2,6 +2,8 @@ # See https://reuse.software/ steps: reuse: + when: + event: push image: fsfe/reuse:latest commands: - - reuse lint \ No newline at end of file + - reuse lint From 594b9029b252af8f322852b4219534f7c042a056 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Oct 2024 16:33:31 +0200 Subject: [PATCH 346/444] Document logback spam --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index e8bcbef..37c5577 100644 --- a/version.gradle +++ b/version.gradle @@ -12,7 +12,7 @@ allprojects { jsrVersion = '3.0.2' junitVersion = '5.8.2' junitSysExitVersion = '1.1.2' - logbackVersion = '1.2.13' + logbackVersion = '1.2.13' // 1.4+ cause CLI spam mockitoVersion = '4.5.1' picocliVersion = '4.6.3' slf4jVersion = '1.7.36' From 4eb6d1fdcb418b2bb02e86f9818fb4faa623cc04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 20:39:54 +0100 Subject: [PATCH 347/444] Prevent unmatched parameters when setting locale --- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 1d5d46b..4ec3d3f 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -74,7 +74,9 @@ class SopCLI { @JvmStatic fun execute(vararg args: String): Int { // Set locale - CommandLine(InitLocale()).parseArgs(*args) + CommandLine(InitLocale()) + .setUnmatchedArgumentsAllowed(true) + .parseArgs(*args) // Re-set bundle with updated locale cliMsg = ResourceBundle.getBundle("msg_sop") From ca65cbe668c79f066c9a19234fa734ad4e234ff6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 12 Dec 2024 20:40:19 +0100 Subject: [PATCH 348/444] For now, do not re-set msg bundle (graal) --- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 4ec3d3f..fcc7e74 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -83,8 +83,6 @@ class SopCLI { return CommandLine(SopCLI::class.java) .apply { - // explicitly set help command resource bundle - subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) // Hide generate-completion command subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) // overwrite executable name From b3b8da4e358fa02b2009a073a2c4a00bbc3a48ab Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Dec 2024 16:41:01 +0100 Subject: [PATCH 349/444] Move testfixtures to own artifact --- external-sop/build.gradle | 2 +- settings.gradle | 1 + sop-java-picocli/build.gradle | 2 +- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 4 +--- sop-java-testfixtures/build.gradle | 24 +++++++++++++++++++ .../src/main}/java/sop/testsuite/JUtils.java | 0 .../sop/testsuite/SOPInstanceFactory.java | 0 .../main}/java/sop/testsuite/TestData.java | 0 .../assertions/SopExecutionAssertions.java | 0 .../assertions/VerificationAssert.java | 0 .../assertions/VerificationListAssert.java | 0 .../testsuite/assertions/package-info.java | 0 .../testsuite/operation/AbstractSOPTest.java | 0 .../testsuite/operation/ArmorDearmorTest.java | 0 .../operation/ChangeKeyPasswordTest.java | 0 .../operation/DecryptWithSessionKeyTest.java | 0 .../DetachedSignDetachedVerifyTest.java | 0 .../operation/EncryptDecryptTest.java | 0 .../testsuite/operation/ExtractCertTest.java | 0 .../testsuite/operation/GenerateKeyTest.java | 0 ...ineSignInlineDetachDetachedVerifyTest.java | 0 .../operation/InlineSignInlineVerifyTest.java | 0 .../testsuite/operation/ListProfilesTest.java | 0 .../testsuite/operation/RevokeKeyTest.java | 0 .../sop/testsuite/operation/VersionTest.java | 0 .../sop/testsuite/operation/package-info.java | 0 .../java/sop/testsuite/package-info.java | 0 .../sop/testsuite/AbortOnUnsupportedOption.kt | 0 .../AbortOnUnsupportedOptionExtension.kt | 0 sop-java/build.gradle | 4 +--- 30 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 sop-java-testfixtures/build.gradle rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/JUtils.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/SOPInstanceFactory.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/TestData.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/assertions/SopExecutionAssertions.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/assertions/VerificationAssert.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/assertions/VerificationListAssert.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/assertions/package-info.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/AbstractSOPTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/ArmorDearmorTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/ChangeKeyPasswordTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/EncryptDecryptTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/ExtractCertTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/GenerateKeyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/ListProfilesTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/RevokeKeyTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/VersionTest.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/operation/package-info.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/java/sop/testsuite/package-info.java (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt (100%) rename {sop-java/src/testFixtures => sop-java-testfixtures/src/main}/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt (100%) diff --git a/external-sop/build.gradle b/external-sop/build.gradle index 1bb86fc..d1a7ffb 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -27,7 +27,7 @@ dependencies { // The ExternalTestSubjectFactory reads json config file to find configured SOP binaries... testImplementation "com.google.code.gson:gson:$gsonVersion" // ...and extends TestSubjectFactory - testImplementation(testFixtures(project(":sop-java"))) + testImplementation(project(":sop-java-testfixtures")) } test { diff --git a/settings.gradle b/settings.gradle index 5dc6372..1cb66be 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,5 +6,6 @@ rootProject.name = 'SOP-Java' include 'sop-java', 'sop-java-picocli', + 'sop-java-testfixtures', 'external-sop' diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index 0596ad3..dbf0cc1 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -17,7 +17,7 @@ dependencies { // SOP implementation(project(":sop-java")) - testImplementation(testFixtures(project(":sop-java"))) + testImplementation(project(":sop-java-testfixtures")) // CLI implementation "info.picocli:picocli:$picocliVersion" diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index fcc7e74..b919370 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -74,9 +74,7 @@ class SopCLI { @JvmStatic fun execute(vararg args: String): Int { // Set locale - CommandLine(InitLocale()) - .setUnmatchedArgumentsAllowed(true) - .parseArgs(*args) + CommandLine(InitLocale()).setUnmatchedArgumentsAllowed(true).parseArgs(*args) // Re-set bundle with updated locale cliMsg = ResourceBundle.getBundle("msg_sop") diff --git a/sop-java-testfixtures/build.gradle b/sop-java-testfixtures/build.gradle new file mode 100644 index 0000000..d3d4a1e --- /dev/null +++ b/sop-java-testfixtures/build.gradle @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id 'java-library' +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +dependencies { + implementation(project(":sop-java")) + implementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + implementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + runtimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + // @Nullable, @Nonnull annotations + implementation "com.google.code.findbugs:jsr305:3.0.2" + +} diff --git a/sop-java/src/testFixtures/java/sop/testsuite/JUtils.java b/sop-java-testfixtures/src/main/java/sop/testsuite/JUtils.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/JUtils.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/JUtils.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java b/sop-java-testfixtures/src/main/java/sop/testsuite/SOPInstanceFactory.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/SOPInstanceFactory.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/SOPInstanceFactory.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/TestData.java b/sop-java-testfixtures/src/main/java/sop/testsuite/TestData.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/TestData.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/TestData.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/SopExecutionAssertions.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/assertions/SopExecutionAssertions.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/assertions/SopExecutionAssertions.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationAssert.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationListAssert.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/assertions/VerificationListAssert.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationListAssert.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/package-info.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/assertions/package-info.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/assertions/package-info.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/AbstractSOPTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/ArmorDearmorTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/ChangeKeyPasswordTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/EncryptDecryptTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/ExtractCertTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/GenerateKeyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/ListProfilesTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/RevokeKeyTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/VersionTest.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/package-info.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/operation/package-info.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/operation/package-info.java diff --git a/sop-java/src/testFixtures/java/sop/testsuite/package-info.java b/sop-java-testfixtures/src/main/java/sop/testsuite/package-info.java similarity index 100% rename from sop-java/src/testFixtures/java/sop/testsuite/package-info.java rename to sop-java-testfixtures/src/main/java/sop/testsuite/package-info.java diff --git a/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt b/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt similarity index 100% rename from sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt rename to sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt diff --git a/sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt b/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt similarity index 100% rename from sop-java/src/testFixtures/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt rename to sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt diff --git a/sop-java/build.gradle b/sop-java/build.gradle index ca546bf..a235a35 100644 --- a/sop-java/build.gradle +++ b/sop-java/build.gradle @@ -4,7 +4,6 @@ plugins { id 'java-library' - id 'java-test-fixtures' } group 'org.pgpainless' @@ -17,8 +16,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" - testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" - testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + testImplementation(project(":sop-java-testfixtures")) // @Nullable, @Nonnull annotations implementation "com.google.code.findbugs:jsr305:3.0.2" From b1e1a2283f82ab63c393b6c2f5efa6b5ffc0b676 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 15 Dec 2024 18:19:30 +0100 Subject: [PATCH 350/444] Update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index feee975..367064c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 10.1.0-SNAPSHOT +- `sop-java`: + - Remove `label()` option from `armor()` subcommand + - Move test-fixtures artifact built with the `testFixtures` plugin into + its own module `sop-java-testfixtures`, which can be consumed by maven builds. +- `sop-java-picocli`: + - Properly map `MissingParameterException` to `MissingArg` exit code + - As a workaround for native builds using graalvm: + - Do not re-set message bundles dynamically (fails in native builds) + - Prevent an unmatched argument error + ## 10.0.3 - CLI `change-key-password`: Fix indirect parameter passing for new and old passwords (thanks to @dkg for the report) - Backport: `revoke-key`: Allow for multiple password options From 84e381fe8eac974547cdd4f39804d9a9a24b1b48 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 21 Dec 2024 14:49:45 +0100 Subject: [PATCH 351/444] Write sop-java version to sop-java-version.properties --- sop-java/build.gradle | 9 +++++++++ sop-java/src/main/resources/sop-java-version.properties | 1 + 2 files changed, 10 insertions(+) create mode 100644 sop-java/src/main/resources/sop-java-version.properties diff --git a/sop-java/build.gradle b/sop-java/build.gradle index a235a35..c6f4e4e 100644 --- a/sop-java/build.gradle +++ b/sop-java/build.gradle @@ -1,7 +1,10 @@ +import org.apache.tools.ant.filters.ReplaceTokens + // SPDX-FileCopyrightText: 2021 Paul Schaub // // SPDX-License-Identifier: Apache-2.0 +import org.apache.tools.ant.filters.* plugins { id 'java-library' } @@ -23,6 +26,12 @@ dependencies { } +processResources { + filter ReplaceTokens, tokens: [ + "project.version": project.version.toString() + ] +} + test { useJUnitPlatform() } diff --git a/sop-java/src/main/resources/sop-java-version.properties b/sop-java/src/main/resources/sop-java-version.properties new file mode 100644 index 0000000..5f6f682 --- /dev/null +++ b/sop-java/src/main/resources/sop-java-version.properties @@ -0,0 +1 @@ +sop-java-version=@project.version@ \ No newline at end of file From 2b6015f59ad00fb62f35da74cfa839c23973ab08 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:01:15 +0100 Subject: [PATCH 352/444] Add license header to properties files --- sop-java/src/main/resources/sop-java-version.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sop-java/src/main/resources/sop-java-version.properties b/sop-java/src/main/resources/sop-java-version.properties index 5f6f682..a2f509b 100644 --- a/sop-java/src/main/resources/sop-java-version.properties +++ b/sop-java/src/main/resources/sop-java-version.properties @@ -1 +1,4 @@ +# SPDX-FileCopyrightText: 2025 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 sop-java-version=@project.version@ \ No newline at end of file From f92a73a5ad46eb104929055e62317e088c3c0b38 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:01:46 +0100 Subject: [PATCH 353/444] Add back legacy --verify-out option alias for decrypt cmd --- .../src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt index 3f15f07..de98f17 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/DecryptCmd.kt @@ -29,7 +29,7 @@ class DecryptCmd : AbstractSopCmd() { @Option(names = [OPT_WITH_PASSWORD], paramLabel = "PASSWORD") var withPassword: List = listOf() - @Option(names = [OPT_VERIFICATIONS_OUT], paramLabel = "VERIFICATIONS") + @Option(names = [OPT_VERIFICATIONS_OUT, "--verify-out"], paramLabel = "VERIFICATIONS") var verifyOut: String? = null @Option(names = [OPT_VERIFY_WITH], paramLabel = "CERT") var certs: List = listOf() From 9ec3cc911bf1d69593231b1a1221362bec21297c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:12:04 +0100 Subject: [PATCH 354/444] Add bcsop reference in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index baeb874..6be51d0 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ allowing to delegate the implementation logic to an arbitrary SOP CLI implementa |-------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| | [pgpainless-sop](https://github.com/pgpainless/pgpainless/tree/main/pgpainless-sop) | Implementation of `sop-java` using PGPainless | | [external-sop](https://github.com/pgpainless/sop-java/tree/main/external-sop) | Implementation of `sop-java` that allows binding to external SOP binaries such as `sqop` | +| [bcsop](https://codeberg.org/PGPainless/bc-sop) | Implementation of `sop-java` using vanilla Bouncy Castle | ### Implementations in other languages | Project | Language | From 690ba6dc16efa6ca60029620af3af0f28ac7efcf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:12:17 +0100 Subject: [PATCH 355/444] Add rpgpie-sop reference in README --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6be51d0..e6ee9ab 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,8 @@ allowing to delegate the implementation logic to an arbitrary SOP CLI implementa | [bcsop](https://codeberg.org/PGPainless/bc-sop) | Implementation of `sop-java` using vanilla Bouncy Castle | ### Implementations in other languages -| Project | Language | -|-------------------------------------------------|----------| -| [sop-rs](https://sequoia-pgp.gitlab.io/sop-rs/) | Rust | -| [SOP for python](https://pypi.org/project/sop/) | Python | +| Project | Language | +|---------------------------------------------------|----------| +| [sop-rs](https://sequoia-pgp.gitlab.io/sop-rs/) | Rust | +| [SOP for python](https://pypi.org/project/sop/) | Python | +| [rpgpie-sop](https://crates.io/crates/rpgpie-sop) | Rust | From 97e91f50ab924dc42533491751a57e2640df62d1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 28 Jan 2025 12:15:30 +0100 Subject: [PATCH 356/444] Migrate pipeline definition to use from_secret https://woodpecker-ci.org/docs/usage/secrets\#use-secrets-in-settings-and-environment --- .woodpecker/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index fab075a..2138cb4 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -16,4 +16,6 @@ steps: - gradle check javadocAll # Code has coverage - gradle jacocoRootReport coveralls - secrets: [coveralls_repo_token] + environment: + coveralls_repo_token: + from_secret: coveralls_repo_token From f2602bb413ba8b6e4f3edded7226814392252e25 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 21:04:50 +0100 Subject: [PATCH 357/444] Bump version to 10.1.0-SNAPSHOT --- .../src/main/kotlin/sop/operation/Version.kt | 16 ++++++++++++++++ version.gradle | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt index 5f26491..a10fe7c 100644 --- a/sop-java/src/main/kotlin/sop/operation/Version.kt +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -4,6 +4,9 @@ package sop.operation +import java.io.IOException +import java.io.InputStream +import java.util.* import kotlin.jvm.Throws import sop.exception.SOPGPException @@ -107,4 +110,17 @@ interface Version { * this method throws an [SOPGPException.UnsupportedOption] instead. */ @Throws(SOPGPException.UnsupportedOption::class) fun getSopVVersion(): String + + /** Return the current version of the SOP-Java library. */ + fun getSopJavaVersion(): String? { + return try { + val resourceIn: InputStream = + javaClass.getResourceAsStream("/sop-java-version.properties") + ?: throw IOException("File sop-java-version.properties not found.") + val properties = Properties().apply { load(resourceIn) } + properties.getProperty("sop-java-version") + } catch (e: IOException) { + null + } + } } diff --git a/version.gradle b/version.gradle index 37c5577..3b0de55 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '10.0.4' + shortVersion = '10.1.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 From 924cfaa140b53c5652adacd0f2115bdd0a5b2544 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 21:18:24 +0100 Subject: [PATCH 358/444] Update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e6ee9ab..f2e207f 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ The repository contains the following modules: compatible with the SOP-CLI specification. * [external-sop](/external-sop) contains an API implementation that can be used to forward API calls to a SOP executable, allowing to delegate the implementation logic to an arbitrary SOP CLI implementation. +* [sop-java-testfixtures](/sop-java-testfixtures) contains a test suite that can be shared by downstream implementations + of `sop-java`. ## Known Implementations (Please expand!) From c145f8bb37967722f821e2516f09945ea7a34ef7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 21:19:41 +0100 Subject: [PATCH 359/444] SOP-Java 10.1.0 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 367064c..49113f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 10.1.0-SNAPSHOT +## 10.1.0 - `sop-java`: - Remove `label()` option from `armor()` subcommand - Move test-fixtures artifact built with the `testFixtures` plugin into diff --git a/version.gradle b/version.gradle index 3b0de55..13c3b5f 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '10.1.0' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From d1893c5ea0ee239d3cef344922ce95c4d9fdb104 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 11 Mar 2025 21:22:31 +0100 Subject: [PATCH 360/444] SOP-Java 10.1.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 13c3b5f..33a2251 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.1.0' - isSnapshot = false + shortVersion = '10.1.1' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 1.8 gsonVersion = '2.10.1' From 51ba24ddbeaa8d5642fe8ac1bf90ac3a19553779 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 3 Apr 2025 14:18:46 +0200 Subject: [PATCH 361/444] Enable kapt annotation processing to properly embed picocli configuration files for native images into the -cli jar file For this it is apparently necessary to upgrade kotlin to 1.9.21 See https://stackoverflow.com/a/79030947/11150851 --- build.gradle | 3 ++- sop-java-picocli/build.gradle | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 577c2aa..78a7267 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { plugins { id 'ru.vyarus.animalsniffer' version '1.5.3' - id 'org.jetbrains.kotlin.jvm' version "1.8.10" + id 'org.jetbrains.kotlin.jvm' version "1.9.21" id 'com.diffplug.spotless' version '6.22.0' apply false } @@ -32,6 +32,7 @@ allprojects { apply plugin: 'jacoco' apply plugin: 'checkstyle' apply plugin: 'kotlin' + apply plugin: 'kotlin-kapt' apply plugin: 'com.diffplug.spotless' // For non-cli modules enable android api compatibility check diff --git a/sop-java-picocli/build.gradle b/sop-java-picocli/build.gradle index dbf0cc1..2203abe 100644 --- a/sop-java-picocli/build.gradle +++ b/sop-java-picocli/build.gradle @@ -21,7 +21,7 @@ dependencies { // CLI implementation "info.picocli:picocli:$picocliVersion" - annotationProcessor "info.picocli:picocli-codegen:$picocliVersion" + kapt "info.picocli:picocli-codegen:$picocliVersion" // @Nonnull, @Nullable... implementation "com.google.code.findbugs:jsr305:$jsrVersion" @@ -33,6 +33,10 @@ application { mainClass = mainClassName } +compileJava { + options.compilerArgs += ["-Aproject=${project.group}/${project.name}"] +} + jar { dependsOn(":sop-java:jar") duplicatesStrategy(DuplicatesStrategy.EXCLUDE) From 57e2f8391bed9935d4b4c469dbb2b2dafdb08ddb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Apr 2025 10:43:49 +0200 Subject: [PATCH 362/444] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49113f6..3028216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 10.1.1-SNAPSHOT +- Prepare jar files for use in native images, e.g. using GraalVM by generating and including + configuration files for reflection, resources and dynamic proxies. + ## 10.1.0 - `sop-java`: - Remove `label()` option from `armor()` subcommand From edb405d79e2321c921b61b8ee350004c24817301 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Apr 2025 12:11:20 +0200 Subject: [PATCH 363/444] Add TODO to remove ProxyOutputStream in 11.X --- sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt index a608c89..4ba24b8 100644 --- a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt +++ b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt @@ -16,6 +16,7 @@ import java.io.OutputStream * target output stream is not yet known. */ @Deprecated("Marked for removal.") +// TODO: Remove in 11.X class ProxyOutputStream : OutputStream() { private val buffer = ByteArrayOutputStream() private var swapped: OutputStream? = null From 859bb5bddecf9d13e7733da1f0eb9de0e6f8cafc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 4 Apr 2025 12:16:00 +0200 Subject: [PATCH 364/444] Fix issues in kdoc --- .../main/kotlin/sop/external/operation/EncryptExternal.kt | 2 +- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 2 +- sop-java/src/main/kotlin/sop/SigningResult.kt | 5 +++-- sop-java/src/main/kotlin/sop/operation/GenerateKey.kt | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt index 12d9cff..679e09b 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/EncryptExternal.kt @@ -37,7 +37,7 @@ class EncryptExternal( override fun signWith(key: InputStream): Encrypt = apply { commandList.add("--sign-with=@ENV:SIGN_WITH_$argCounter") - envList.add("SIGN_WITH_$argCounter=${ExternalSOP.readString(key)}") + envList.add("SIGN_WITH_$argCounter=${readString(key)}") argCounter += 1 } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index b919370..62065f4 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -60,7 +60,7 @@ class SopCLI { @JvmField var EXECUTABLE_NAME = "sop" @JvmField - @Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) + @Option(names = ["--stacktrace"], scope = ScopeType.INHERIT) var stacktrace = false @JvmStatic diff --git a/sop-java/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt index 29304ea..60888e0 100644 --- a/sop-java/src/main/kotlin/sop/SigningResult.kt +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -9,8 +9,9 @@ package sop * * @param micAlg string identifying the digest mechanism used to create the signed message. This is * useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME - * object as described in section 5 of [RFC3156]. If more than one signature was generated and - * different digest mechanisms were used, the value of the micalg object is an empty string. + * object as described in section 5 of [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5). + * If more than one signature was generated and different digest mechanisms were used, the value + * of the micalg object is an empty string. */ data class SigningResult(val micAlg: MicAlg) { diff --git a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt index 3b83b99..13de39a 100644 --- a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt @@ -78,7 +78,7 @@ interface GenerateKey { fun signingOnly(): GenerateKey /** - * Generate the OpenPGP key and return it encoded as an [InputStream]. + * Generate the OpenPGP key and return it encoded as an [java.io.InputStream]. * * @return key * @throws MissingArg if no user-id was provided From 2c26ab2da524cbf28f705262e5b4f9952becff7f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:01:20 +0200 Subject: [PATCH 365/444] Improve reproducibility --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 78a7267..4cc8dc4 100644 --- a/build.gradle +++ b/build.gradle @@ -78,6 +78,9 @@ allprojects { tasks.withType(AbstractArchiveTask) { preserveFileTimestamps = false reproducibleFileOrder = true + + dirMode = 0755 + fileMode = 0644 } // Compatibility of default implementations in kotlin interfaces with Java implementations. From 8394f2e5a8e40df8461a4845e09e921e0ab81c79 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:01:45 +0200 Subject: [PATCH 366/444] Make use of toolchain functionality and raise min Java API level to 11 --- build.gradle | 22 ++++++++++------------ version.gradle | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 4cc8dc4..c73b60c 100644 --- a/build.gradle +++ b/build.gradle @@ -68,8 +68,6 @@ allprojects { description = "Stateless OpenPGP Protocol API for Java" version = shortVersion - sourceCompatibility = javaSourceCompatibility - repositories { mavenCentral() } @@ -83,6 +81,10 @@ allprojects { fileMode = 0644 } + kotlin { + jvmToolchain(javaSourceCompatibility) + } + // Compatibility of default implementations in kotlin interfaces with Java implementations. tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { kotlinOptions { @@ -115,7 +117,7 @@ allprojects { } jacoco { - toolVersion = "0.8.7" + toolVersion = "0.8.8" } jacocoTestReport { @@ -123,7 +125,7 @@ allprojects { sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs)) classDirectories.setFrom(project.files(sourceSets.main.output)) reports { - xml.enabled true + xml.required = true } } @@ -141,15 +143,15 @@ subprojects { apply plugin: 'signing' task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } task testsJar(type: Jar, dependsOn: testClasses) { - classifier = 'tests' + archiveClassifier = 'tests' from sourceSets.test.output } @@ -246,7 +248,7 @@ task jacocoRootReport(type: JacocoReport) { classDirectories.setFrom(files(subprojects.sourceSets.main.output)) executionData.setFrom(files(subprojects.jacocoTestReport.executionData)) reports { - xml.enabled true + xml.required = true xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") } // We could remove the following setOnlyIf line, but then @@ -257,10 +259,6 @@ task jacocoRootReport(type: JacocoReport) { } task javadocAll(type: Javadoc) { - def currentJavaVersion = JavaVersion.current() - if (currentJavaVersion.compareTo(JavaVersion.VERSION_1_9) >= 0) { - options.addStringOption("-release", "8"); - } source subprojects.collect {project -> project.sourceSets.main.allJava } destinationDir = new File(buildDir, 'javadoc') diff --git a/version.gradle b/version.gradle index 33a2251..9619f9a 100644 --- a/version.gradle +++ b/version.gradle @@ -7,7 +7,7 @@ allprojects { shortVersion = '10.1.1' isSnapshot = true minAndroidSdk = 10 - javaSourceCompatibility = 1.8 + javaSourceCompatibility = 11 gsonVersion = '2.10.1' jsrVersion = '3.0.2' junitVersion = '5.8.2' From cddc92bd92ef5354c10a5236fe8ba75e890c5096 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:13:40 +0200 Subject: [PATCH 367/444] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3028216..1746078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0 ## 10.1.1-SNAPSHOT - Prepare jar files for use in native images, e.g. using GraalVM by generating and including configuration files for reflection, resources and dynamic proxies. +- gradle: Make use of jvmToolchain functionality +- gradle: Improve reproducibility ## 10.1.0 - `sop-java`: From a2a3bda2b3c321c1561d217cb2f5cae7f5f6a5c8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:26:09 +0200 Subject: [PATCH 368/444] Migrate AbortOnUnsupportedOption annotation back to java --- .../testsuite/AbortOnUnsupportedOption.java | 18 +++++++++++++ .../AbortOnUnsupportedOptionExtension.java | 26 +++++++++++++++++++ .../sop/testsuite/AbortOnUnsupportedOption.kt | 12 --------- .../AbortOnUnsupportedOptionExtension.kt | 21 --------------- 4 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOption.java create mode 100644 sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOptionExtension.java delete mode 100644 sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt delete mode 100644 sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOption.java b/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOption.java new file mode 100644 index 0000000..cbd0746 --- /dev/null +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOption.java @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface AbortOnUnsupportedOption { + +} diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOptionExtension.java b/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOptionExtension.java new file mode 100644 index 0000000..0bf366d --- /dev/null +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/AbortOnUnsupportedOptionExtension.java @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import sop.exception.SOPGPException; + +import java.lang.annotation.Annotation; + +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +public class AbortOnUnsupportedOptionExtension implements TestExecutionExceptionHandler { + + @Override + public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable { + Class testClass = extensionContext.getRequiredTestClass(); + Annotation annotation = testClass.getAnnotation(AbortOnUnsupportedOption.class); + if (annotation != null && throwable instanceof SOPGPException.UnsupportedOption) { + assumeTrue(false, "Test aborted due to: " + throwable.getMessage()); + } + throw throwable; + } +} diff --git a/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt b/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt deleted file mode 100644 index cf99671..0000000 --- a/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOption.kt +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite - -import java.lang.annotation.Inherited - -@Target(AnnotationTarget.TYPE) -@Retention(AnnotationRetention.RUNTIME) -@Inherited -annotation class AbortOnUnsupportedOption diff --git a/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt b/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt deleted file mode 100644 index 809c78f..0000000 --- a/sop-java-testfixtures/src/main/kotlin/sop/testsuite/AbortOnUnsupportedOptionExtension.kt +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite - -import org.junit.jupiter.api.Assumptions -import org.junit.jupiter.api.extension.ExtensionContext -import org.junit.jupiter.api.extension.TestExecutionExceptionHandler -import sop.exception.SOPGPException - -class AbortOnUnsupportedOptionExtension : TestExecutionExceptionHandler { - override fun handleTestExecutionException(context: ExtensionContext, throwable: Throwable) { - val testClass = context.requiredTestClass - val annotation = testClass.getAnnotation(AbortOnUnsupportedOption::class.java) - if (annotation != null && SOPGPException.UnsupportedOption::class.isInstance(throwable)) { - Assumptions.assumeTrue(false, "Test aborted due to: " + throwable.message) - } - throw throwable - } -} From e3fe9410d7dbd30c08e86810758fb47a20c71970 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:27:54 +0200 Subject: [PATCH 369/444] reuse: Migrate to toml format --- .reuse/dep5 | 29 ----------------------------- REUSE.toml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) delete mode 100644 .reuse/dep5 create mode 100644 REUSE.toml diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index f43556c..0000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,29 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: SOP-Java -Upstream-Contact: Paul Schaub -Source: https://pgpainless.org - -# Sample paragraph, commented out: -# -# Files: src/* -# Copyright: $YEAR $NAME <$CONTACT> -# License: ... - -# Gradle build tool -Files: gradle* -Copyright: 2015 the original author or authors. -License: Apache-2.0 - -# Woodpecker build files -Files: .woodpecker/* -Copyright: 2022 the original author or authors. -License: Apache-2.0 - -Files: external-sop/src/main/resources/sop/testsuite/external/* -Copyright: 2023 the original author or authors -License: Apache-2.0 - -# Github Issue Templates -Files: .github/ISSUE_TEMPLATE/* -Copyright: 2024 the original author or authors -License: Apache-2.0 diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..f82d8f8 --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 + +version = 1 +SPDX-PackageName = "SOP-Java" +SPDX-PackageSupplier = "Paul Schaub " +SPDX-PackageDownloadLocation = "https://pgpainless.org" + +[[annotations]] +path = "gradle**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2015 the original author or authors." +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = ".woodpecker/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2022 the original author or authors." +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = "external-sop/src/main/resources/sop/testsuite/external/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2023 the original author or authors" +SPDX-License-Identifier = "Apache-2.0" + +[[annotations]] +path = ".github/ISSUE_TEMPLATE/**" +precedence = "aggregate" +SPDX-FileCopyrightText = "2024 the original author or authors" +SPDX-License-Identifier = "Apache-2.0" From 4d2876a296539ec1e867d33c7228e105c9f390e0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:28:14 +0200 Subject: [PATCH 370/444] Fix formatting issue --- sop-java/src/main/kotlin/sop/SigningResult.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/SigningResult.kt b/sop-java/src/main/kotlin/sop/SigningResult.kt index 60888e0..651f8c1 100644 --- a/sop-java/src/main/kotlin/sop/SigningResult.kt +++ b/sop-java/src/main/kotlin/sop/SigningResult.kt @@ -9,9 +9,10 @@ package sop * * @param micAlg string identifying the digest mechanism used to create the signed message. This is * useful for setting the `micalg=` parameter for the multipart/signed content-type of a PGP/MIME - * object as described in section 5 of [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5). - * If more than one signature was generated and different digest mechanisms were used, the value - * of the micalg object is an empty string. + * object as described in section 5 of + * [RFC3156](https://www.rfc-editor.org/rfc/rfc3156#section-5). If more than one signature was + * generated and different digest mechanisms were used, the value of the micalg object is an empty + * string. */ data class SigningResult(val micAlg: MicAlg) { From 2d99aea4ab7ec2fa1e32f309fc21f7f7fcf0e492 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:29:04 +0200 Subject: [PATCH 371/444] Bump animalsniffer to 2.0.0 --- CHANGELOG.md | 1 + build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1746078..b2f0a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 configuration files for reflection, resources and dynamic proxies. - gradle: Make use of jvmToolchain functionality - gradle: Improve reproducibility +- gradle: Bump animalsniffer to `2.0.0` ## 10.1.0 - `sop-java`: diff --git a/build.gradle b/build.gradle index c73b60c..1bff704 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ buildscript { } plugins { - id 'ru.vyarus.animalsniffer' version '1.5.3' + id 'ru.vyarus.animalsniffer' version '2.0.0' id 'org.jetbrains.kotlin.jvm' version "1.9.21" id 'com.diffplug.spotless' version '6.22.0' apply false } From 701f9453ca0bb4125a07a91d3b6313a9f5d6df80 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:41:50 +0200 Subject: [PATCH 372/444] SOP-Java 10.1.1 --- CHANGELOG.md | 2 +- version.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f0a18..c0500cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 10.1.1-SNAPSHOT +## 10.1.1 - Prepare jar files for use in native images, e.g. using GraalVM by generating and including configuration files for reflection, resources and dynamic proxies. - gradle: Make use of jvmToolchain functionality diff --git a/version.gradle b/version.gradle index 9619f9a..5fdbcad 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '10.1.1' - isSnapshot = true + isSnapshot = false minAndroidSdk = 10 javaSourceCompatibility = 11 gsonVersion = '2.10.1' From cbeec9c90d68ff7ebdcb8ca146ae4481ba2b787f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 11:44:22 +0200 Subject: [PATCH 373/444] SOP-Java 10.1.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 5fdbcad..b4908ee 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '10.1.1' - isSnapshot = false + shortVersion = '10.1.2' + isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 11 gsonVersion = '2.10.1' From ad137d63514822945cb94a0487f36b0dc5eb91b8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 15 May 2025 02:16:52 +0200 Subject: [PATCH 374/444] Try to fix coveralls repo token --- .woodpecker/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 2138cb4..4c23ffb 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -17,5 +17,5 @@ steps: # Code has coverage - gradle jacocoRootReport coveralls environment: - coveralls_repo_token: + COVERALLS_REPO_TOKEN: from_secret: coveralls_repo_token From 1dcf13244d1a0e4ac668f01a1d10dc6f3f4294ec Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:40:36 +0200 Subject: [PATCH 375/444] Add new exceptions --- .../src/main/resources/msg_sop.properties | 2 ++ .../src/main/resources/msg_sop_de.properties | 2 ++ .../kotlin/sop/exception/SOPGPException.kt | 30 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 94e4dc0..8179676 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -38,6 +38,8 @@ usage.exitCodeList.19=83:Options were supplied that are incompatible with each o usage.exitCodeList.20=89:The requested profile is unsupported, or the indicated subcommand does not accept profiles usage.exitCodeList.21=97:The implementation supports some form of hardware-backed secret keys, but could not identify the hardware device usage.exitCodeList.22=101:The implementation tried to use a hardware-backed secret key, but the cryptographic hardware refused the operation for some reason other than a bad PIN or password +usage.exitCodeList.23=103:The primary key of a KEYS object is too weak or revoked +usage.exitCodeList.24=107:The CERTS object has no matching User ID ## SHARED RESOURCES stacktrace=Print stacktrace diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 786fa36..0538cd9 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -38,6 +38,8 @@ usage.exitCodeList.19=83:Miteinander inkompatible Optionen spezifiziert usage.exitCodeList.20=89:Das angeforderte Profil wird nicht unterstützt, oder der angegebene Unterbefehl akzeptiert keine Profile usage.exitCodeList.21=97:Die Anwendung unterstützt hardwaregestützte private Schlüssel, aber kann das Gerät nicht identifizieren usage.exitCodeList.22=101:Die Anwendung versuchte, einen hardwaregestützten Schlüssel zu verwenden, aber das Gerät lehnte den Vorgang aus einem anderen Grund als einer falschen PIN oder einem falschen Passwort ab +usage.exitCodeList.23=103:Der primäre private Schlüssel ist zu schwach oder widerrufen +usage.exitCodeList.24=107:Das Zertifikat hat keine übereinstimmende User ID ## SHARED RESOURCES stacktrace=Stacktrace ausgeben diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index bc9131f..1f9ce6b 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -337,4 +337,34 @@ abstract class SOPGPException : RuntimeException { const val EXIT_CODE = 101 } } + + /** + * The primary key of a KEYS object is too weak or revoked. + */ + class PrimaryKeyBad : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 103 + } + } + + /** + * The CERTS object has no matching User ID. + */ + class CertUserIdNoMatch : SOPGPException { + constructor() : super() + + constructor(errorMsg: String) : super(errorMsg) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 107 + } + } } From 4115a5041d0c49242f61231e37c283863e862df0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:43:36 +0200 Subject: [PATCH 376/444] Add implementation of update-key command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../sop/cli/picocli/commands/UpdateKeyCmd.kt | 76 +++++++++++++++++++ .../main/resources/msg_update-key.properties | 19 +++++ .../resources/msg_update-key_de.properties | 18 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 18 ++--- .../main/kotlin/sop/operation/UpdateKey.kt | 43 +++++++++++ 6 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_update-key.properties create mode 100644 sop-java-picocli/src/main/resources/msg_update-key_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/UpdateKey.kt diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 62065f4..df8e883 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -27,6 +27,7 @@ import sop.exception.SOPGPException ChangeKeyPasswordCmd::class, RevokeKeyCmd::class, ExtractCertCmd::class, + UpdateKeyCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt new file mode 100644 index 0000000..2afa015 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.* +import java.io.IOException + +@Command( + name = "update-key", + resourceBundle = "msg_update-key", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class UpdateKeyCmd : AbstractSopCmd() { + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--signing-only"]) var signingOnly = false + + @Option(names = ["--no-new-mechanisms"]) var noNewMechanisms = false + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--merge-certs"], paramLabel = "CERTS") + var mergeCerts: List = listOf() + + override fun run() { + val updateKey = throwIfUnsupportedSubcommand(SopCLI.getSop().updateKey(), "update-key") + + if (!armor) { + updateKey.noArmor() + } + + if (signingOnly) { + updateKey.signingOnly() + } + + if (noNewMechanisms) { + updateKey.noNewMechanisms() + } + + for (passwordFileName in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + updateKey.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (certInput in mergeCerts) { + try { + getInput(certInput).use { certIn -> updateKey.mergeCerts(certIn) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", certInput) + throw BadData(errorMsg, badData) + } + } + + try { + val ready = updateKey.key(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_update-key.properties b/sop-java-picocli/src/main/resources/msg_update-key.properties new file mode 100644 index 0000000..dd4446d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_update-key.properties @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Keep a secret key up-to-date +no-armor=ASCII armor the output +signing-only=TODO: Document +no-new-mechanisms=Do not add feature support for new mechanisms, which the key did not previously support +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +merge-certs.0=Merge additional elements found in the corresponding CERTS objects into the updated secret keys +merge-certs.1=This can be used, for example, to absorb a third-party certification into the Transferable Secret Key + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key_de.properties b/sop-java-picocli/src/main/resources/msg_update-key_de.properties new file mode 100644 index 0000000..86b999e --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_update-key_de.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Halte einen Schlüssel auf dem neusten Stand +no-armor=Schütze Ausgabe mit ASCII Armor +signing-only=TODO: Dokumentieren +no-new-mechanisms=Füge keine neuen Funktionen hinzu, die der Schlüssel nicht bereits zuvor unterstützt hat +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +merge-certs.0=Führe zusätzliche Elemente aus entsprechenden CERTS Objekten mit dem privaten Schlüssel zusammen +merge-certs.1=Dies kann zum Beispiel dazu genutzt werden, Zertifizierungen dritter in den privaten Schlüssel zu übernehmen + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 7fdd414..c53bb7d 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -4,18 +4,7 @@ package sop -import sop.operation.Armor -import sop.operation.ChangeKeyPassword -import sop.operation.Dearmor -import sop.operation.Decrypt -import sop.operation.DetachedSign -import sop.operation.Encrypt -import sop.operation.ExtractCert -import sop.operation.GenerateKey -import sop.operation.InlineDetach -import sop.operation.InlineSign -import sop.operation.ListProfiles -import sop.operation.RevokeKey +import sop.operation.* /** * Stateless OpenPGP Interface. This class provides a stateless interface to various OpenPGP related @@ -70,4 +59,9 @@ interface SOP : SOPV { /** Update a key's password. */ fun changeKeyPassword(): ChangeKeyPassword + + /** + * Keep a secret key up-to-date. + */ + fun updateKey(): UpdateKey } diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt new file mode 100644 index 0000000..1b12f6f --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import sop.util.UTF8Util +import java.io.IOException +import java.io.InputStream + +interface UpdateKey { + + /** + * Disable ASCII armor encoding of the output. + * + * @return builder instance + */ + fun noArmor(): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + fun mergeCerts(certs: InputStream): UpdateKey + + @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + fun key(key: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + fun key(key: ByteArray): Ready = key(key.inputStream()) +} \ No newline at end of file From 84404d629fa91a5d70399a9043486093ee7632c2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Sep 2024 22:43:50 +0200 Subject: [PATCH 377/444] Add implementation of merge-certs command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../sop/cli/picocli/commands/MergeCertsCmd.kt | 48 +++++++++++++++++++ .../main/resources/msg_merge-certs.properties | 15 ++++++ .../resources/msg_merge-certs_de.properties | 19 ++++++++ sop-java/src/main/kotlin/sop/SOP.kt | 5 ++ .../main/kotlin/sop/operation/MergeCerts.kt | 28 +++++++++++ 6 files changed, 116 insertions(+) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_merge-certs.properties create mode 100644 sop-java-picocli/src/main/resources/msg_merge-certs_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/MergeCerts.kt diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index df8e883..9d1a305 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -28,6 +28,7 @@ import sop.exception.SOPGPException RevokeKeyCmd::class, ExtractCertCmd::class, UpdateKeyCmd::class, + MergeCertsCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt new file mode 100644 index 0000000..15b33f8 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import picocli.CommandLine +import picocli.CommandLine.Command +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import java.io.IOException + +@Command( + name = "merge-certs", + resourceBundle = "msg_merge-certs", + exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) +class MergeCertsCmd : AbstractSopCmd() { + + @CommandLine.Option(names = ["--no-armor"], negatable = true) + var armor = false + + @CommandLine.Parameters(paramLabel = "CERTS") + var updates: List = listOf() + + override fun run() { + val mergeCerts = throwIfUnsupportedSubcommand(SopCLI.getSop().mergeCerts(), "merge-certs") + + if (!armor) { + mergeCerts.noArmor() + } + + for (certFileName in updates) { + try { + getInput(certFileName).use { mergeCerts.updates(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + try { + + val ready = mergeCerts.baseCertificates(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties new file mode 100644 index 0000000..866db4b --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.headerHeading=Merge OpenPGP certificates%n +usage.description=BLABLA +no-armor=ASCII armor the output +CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +usage.optionListHeading = %nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties new file mode 100644 index 0000000..021c535 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.headerHeading=OpenPGP Zertifikate zusammenführen%n%n +usage.header=Führe OpenPGP Zertifikate aus der Standardeingabe mit ensprechenden Elementen aus CERTS zusammen und gebe das Ergebnis auf der Standardausgabe aus +usage.description=Es werden nur Zertifikate auf die Standardausgabe geschrieben, welche Teil der Standardeingabe waren +no-armor=Schütze Ausgabe mit ASCII Armor +CERTS[0..*]=OpenPGP Zertifikate aus denen neue Elemente in die Basiszertifikate aus der Standardeingabe übernommen werden sollen + +usage.parameterList.0=STANDARDIN +usage.parameterList.1=STANDARDOUT + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.descriptionHeading=%nHinweise:%n +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading = %nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index c53bb7d..1640d5f 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -64,4 +64,9 @@ interface SOP : SOPV { * Keep a secret key up-to-date. */ fun updateKey(): UpdateKey + + /** + * Merge OpenPGP certificates. + */ + fun mergeCerts(): MergeCerts } diff --git a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt new file mode 100644 index 0000000..f60d291 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import java.io.IOException +import java.io.InputStream + +interface MergeCerts { + + @Throws(SOPGPException.UnsupportedOption::class) + fun noArmor(): MergeCerts + + @Throws(SOPGPException.BadData::class, IOException::class) + fun updates(updateCerts: InputStream): MergeCerts + + @Throws(SOPGPException.BadData::class, IOException::class) + fun updates(updateCerts: ByteArray): MergeCerts = updates(updateCerts.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class) + fun baseCertificates(certs: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class) + fun baseCertificates(certs: ByteArray): Ready = baseCertificates(certs.inputStream()) +} \ No newline at end of file From ada77be955720dfca424c27b763878e453b2b65b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 15:50:17 +0200 Subject: [PATCH 378/444] Add support for rendering help info for input and output --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 8 +- .../cli/picocli/commands/AbstractSopCmd.kt | 100 ++++++++++++++++++ .../src/main/resources/msg_sop.properties | 7 +- .../src/main/resources/msg_sop_de.properties | 5 +- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 9d1a305..4db6b30 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -83,16 +83,20 @@ class SopCLI { return CommandLine(SopCLI::class.java) .apply { + // explicitly set help command resource bundle + subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) // Hide generate-completion command subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) + // render Input/Output sections in help command + subcommands.values.filter { (it.getCommand() as Any) is AbstractSopCmd } // Only for AbstractSopCmd objects + .forEach { (it.getCommand() as AbstractSopCmd).installIORenderer(it) } // overwrite executable name commandName = EXECUTABLE_NAME // setup exception handling executionExceptionHandler = SOPExecutionExceptionHandler() exitCodeExceptionMapper = SOPExceptionExitCodeMapper() isCaseInsensitiveEnumValuesAllowed = true - } - .execute(*args) + }.execute(*args) } } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt index 4629e57..65be1be 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/AbstractSopCmd.kt @@ -7,6 +7,11 @@ package sop.cli.picocli.commands import java.io.* import java.text.ParseException import java.util.* +import picocli.CommandLine +import picocli.CommandLine.Help +import picocli.CommandLine.Help.Column +import picocli.CommandLine.Help.TextTable +import picocli.CommandLine.IHelpSectionRenderer import sop.cli.picocli.commands.AbstractSopCmd.EnvironmentVariableResolver import sop.exception.SOPGPException.* import sop.util.UTCUtil.Companion.parseUTCDate @@ -215,11 +220,106 @@ abstract class AbstractSopCmd(locale: Locale = Locale.getDefault()) : Runnable { } } + /** + * See + * [Example](https://github.com/remkop/picocli/blob/main/picocli-examples/src/main/java/picocli/examples/customhelp/EnvironmentVariablesSection.java) + */ + class InputOutputHelpSectionRenderer(private val argument: Pair) : + IHelpSectionRenderer { + + override fun render(help: Help): String { + return argument.let { + val calcLen = + help.calcLongOptionColumnWidth( + help.commandSpec().options(), + help.commandSpec().positionalParameters(), + help.colorScheme()) + val keyLength = + help + .commandSpec() + .usageMessage() + .longOptionsMaxWidth() + .coerceAtMost(calcLen - 1) + val table = + TextTable.forColumns( + help.colorScheme(), + Column(keyLength + 7, 6, Column.Overflow.SPAN), + Column(width(help) - (keyLength + 7), 0, Column.Overflow.WRAP)) + table.setAdjustLineBreaksForWideCJKCharacters(adjustCJK(help)) + table.addRowValues("@|yellow ${argument.first}|@", argument.second ?: "") + table.toString() + } + } + + private fun adjustCJK(help: Help) = + help.commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters() + + private fun width(help: Help) = help.commandSpec().usageMessage().width() + } + + fun installIORenderer(cmd: CommandLine) { + val inputName = getResString(cmd, "standardInput") + if (inputName != null) { + cmd.helpSectionMap[SECTION_KEY_STANDARD_INPUT_HEADING] = IHelpSectionRenderer { + getResString(cmd, "standardInputHeading") + } + cmd.helpSectionMap[SECTION_KEY_STANDARD_INPUT_DETAILS] = + InputOutputHelpSectionRenderer( + inputName to getResString(cmd, "standardInputDescription")) + cmd.helpSectionKeys = + insertKey( + cmd.helpSectionKeys, + SECTION_KEY_STANDARD_INPUT_HEADING, + SECTION_KEY_STANDARD_INPUT_DETAILS) + } + + val outputName = getResString(cmd, "standardOutput") + if (outputName != null) { + cmd.helpSectionMap[SECTION_KEY_STANDARD_OUTPUT_HEADING] = IHelpSectionRenderer { + getResString(cmd, "standardOutputHeading") + } + cmd.helpSectionMap[SECTION_KEY_STANDARD_OUTPUT_DETAILS] = + InputOutputHelpSectionRenderer( + outputName to getResString(cmd, "standardOutputDescription")) + cmd.helpSectionKeys = + insertKey( + cmd.helpSectionKeys, + SECTION_KEY_STANDARD_OUTPUT_HEADING, + SECTION_KEY_STANDARD_OUTPUT_DETAILS) + } + } + + private fun insertKey(keys: List, header: String, details: String): List { + val index = + keys.indexOf(CommandLine.Model.UsageMessageSpec.SECTION_KEY_EXIT_CODE_LIST_HEADING) + val result = keys.toMutableList() + result.add(index, header) + result.add(index + 1, details) + return result + } + + private fun getResString(cmd: CommandLine, key: String): String? = + try { + cmd.resourceBundle.getString(key) + } catch (m: MissingResourceException) { + try { + cmd.parent.resourceBundle.getString(key) + } catch (m: MissingResourceException) { + null + } + } + ?.let { String.format(it) } + companion object { const val PRFX_ENV = "@ENV:" const val PRFX_FD = "@FD:" + const val SECTION_KEY_STANDARD_INPUT_HEADING = "standardInputHeading" + const val SECTION_KEY_STANDARD_INPUT_DETAILS = "standardInput" + const val SECTION_KEY_STANDARD_OUTPUT_HEADING = "standardOutputHeading" + const val SECTION_KEY_STANDARD_OUTPUT_DETAILS = "standardOutput" + @JvmField val DAWN_OF_TIME = Date(0) @JvmField diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 8179676..d5d997a 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -9,10 +9,13 @@ locale=Locale for description texts # Generic usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n +standardInputHeading=%nInput:%n +standardOutputHeading=%nOutput:%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Successful program execution diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 0538cd9..73efe89 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -10,9 +10,12 @@ locale=Gebietsschema f # Generic usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n +standardInputHeading=%nEingabe:%n +standardOutputHeading=%nAusgabe:%n + # Exit Codes usage.exitCodeListHeading=%nExit Codes:%n usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung From d6c133087435fa78f0ae246bbf8f4d8c2a388295 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:01:30 +0200 Subject: [PATCH 379/444] Implement certify-userid command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../cli/picocli/commands/CertifyUserIdCmd.kt | 87 +++++++++++++++++++ .../resources/msg_certify-userid.properties | 23 +++++ .../msg_certify-userid_de.properties | 20 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 5 ++ .../kotlin/sop/operation/CertifyUserId.kt | 41 +++++++++ 6 files changed, 177 insertions(+) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_certify-userid.properties create mode 100644 sop-java-picocli/src/main/resources/msg_certify-userid_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 4db6b30..d234e54 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -29,6 +29,7 @@ import sop.exception.SOPGPException ExtractCertCmd::class, UpdateKeyCmd::class, MergeCertsCmd::class, + CertifyUserIdCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt new file mode 100644 index 0000000..71ef79f --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Model.CommandSpec +import picocli.CommandLine.Option +import picocli.CommandLine.Parameters +import picocli.CommandLine.Spec +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException.BadData +import sop.exception.SOPGPException.UnsupportedOption + +@Command( + name = "certify-userid", + resourceBundle = "msg_certify-userid", + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) +class CertifyUserIdCmd : AbstractSopCmd() { + + @Spec var spec: CommandSpec? = null + + @Option(names = ["--no-armor"], negatable = true) var armor = true + + @Option(names = ["--userid"], required = true, arity = "1..*", paramLabel = "USERID") + var userIds: List = listOf() + + @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") + var withKeyPassword: List = listOf() + + @Option(names = ["--no-require-self-sig"]) var noRequireSelfSig = false + + @Parameters(paramLabel = "KEYS", arity = "1..*") var keys: List = listOf() + + override fun run() { + val certifyUserId = + throwIfUnsupportedSubcommand(SopCLI.getSop().certifyUserId(), "certify-userid") + + if (!armor) { + certifyUserId.noArmor() + } + + if (noRequireSelfSig) { + certifyUserId.noRequireSelfSig() + } + + for (userId in userIds) { + certifyUserId.userId(userId) + } + + for (passwordFileName in withKeyPassword) { + try { + val password = stringFromInputStream(getInput(passwordFileName)) + certifyUserId.withKeyPassword(password) + } catch (unsupportedOption: UnsupportedOption) { + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + throw UnsupportedOption(errorMsg, unsupportedOption) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + for (keyInput in keys) { + try { + getInput(keyInput).use { certifyUserId.keys(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput) + throw BadData(errorMsg, badData) + } + } + + try { + val ready = certifyUserId.certs(System.`in`) + ready.writeTo(System.out) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", "STDIN") + throw BadData(errorMsg, badData) + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties new file mode 100644 index 0000000..5eb7aa3 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Certify OpenPGP Certificate User IDs +no-armor=ASCII armor the output +userid=Identities that shall be certified +with-key-password.0=Passphrase to unlock the secret key(s). +with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +no-require-self-sig=Certify the UserID regardless of whether self-certifications are present +KEYS[0..*]=Private keys + +standardInput=CERTS +standardInputDescription=Certificates that shall be certified +standardOutput=CERTS +standardOutputDescription=Certified certificates + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=Parameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = Commands:%n +usage.optionListHeading = Options:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties new file mode 100644 index 0000000..0237fa6 --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Zertifiziere OpenPGP Zertifikat Identitäten +no-armor=Schütze Ausgabe mit ASCII Armor +userid=Identität, die zertifiziert werden soll +with-key-password.0=Passwort zum Entsperren der privaten Schlüssel +with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +no-require-self-sig=Zertifiziere die Identität, unabhängig davon, ob eine Selbstzertifizierung vorhanden ist +KEYS[0..*]=Private Schlüssel + +standardInputDescription=Zertifikate, auf denen Identitäten zertifiziert werden sollen +standardOutputDescription=Zertifizierte Zertifikate + +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=Parameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=Befehle:%n +usage.optionListHeading = Optionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 1640d5f..5435cad 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -69,4 +69,9 @@ interface SOP : SOPV { * Merge OpenPGP certificates. */ fun mergeCerts(): MergeCerts + + /** + * Certify OpenPGP Certificate User-IDs. + */ + fun certifyUserId(): CertifyUserId } diff --git a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt new file mode 100644 index 0000000..92fff20 --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import sop.Ready +import sop.exception.SOPGPException +import sop.util.UTF8Util +import java.io.IOException +import java.io.InputStream + +interface CertifyUserId { + + @Throws(SOPGPException.UnsupportedOption::class) + fun noArmor(): CertifyUserId + + @Throws(SOPGPException.UnsupportedOption::class) + fun userId(userId: String): CertifyUserId + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: String): CertifyUserId = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + + @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + fun withKeyPassword(password: ByteArray): CertifyUserId + + @Throws(SOPGPException.UnsupportedOption::class) + fun noRequireSelfSig(): CertifyUserId + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) + fun keys(keys: InputStream): CertifyUserId + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) + fun keys(keys: ByteArray): CertifyUserId = keys(keys.inputStream()) + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun certs(certs: InputStream): Ready + + @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun certs(certs: ByteArray): Ready = certs(certs.inputStream()) +} \ No newline at end of file From 2b6a5dd651d0063be1558e1de7ae357a83eff0af Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:01:58 +0200 Subject: [PATCH 380/444] Checkstyle and exception handling improvements --- .../sop/cli/picocli/commands/MergeCertsCmd.kt | 10 ++++------ .../sop/cli/picocli/commands/UpdateKeyCmd.kt | 15 +++++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt index 15b33f8..16d56e3 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt @@ -4,11 +4,11 @@ package sop.cli.picocli.commands +import java.io.IOException import picocli.CommandLine import picocli.CommandLine.Command import sop.cli.picocli.SopCLI import sop.exception.SOPGPException -import java.io.IOException @Command( name = "merge-certs", @@ -16,11 +16,9 @@ import java.io.IOException exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) class MergeCertsCmd : AbstractSopCmd() { - @CommandLine.Option(names = ["--no-armor"], negatable = true) - var armor = false + @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = false - @CommandLine.Parameters(paramLabel = "CERTS") - var updates: List = listOf() + @CommandLine.Parameters(paramLabel = "CERTS") var updates: List = listOf() override fun run() { val mergeCerts = throwIfUnsupportedSubcommand(SopCLI.getSop().mergeCerts(), "merge-certs") @@ -45,4 +43,4 @@ class MergeCertsCmd : AbstractSopCmd() { throw RuntimeException(e) } } -} \ No newline at end of file +} diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt index 2afa015..08f9297 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt @@ -4,11 +4,11 @@ package sop.cli.picocli.commands +import java.io.IOException import picocli.CommandLine.Command import picocli.CommandLine.Option import sop.cli.picocli.SopCLI import sop.exception.SOPGPException.* -import java.io.IOException @Command( name = "update-key", @@ -25,8 +25,7 @@ class UpdateKeyCmd : AbstractSopCmd() { @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: List = listOf() - @Option(names = ["--merge-certs"], paramLabel = "CERTS") - var mergeCerts: List = listOf() + @Option(names = ["--merge-certs"], paramLabel = "CERTS") var mergeCerts: List = listOf() override fun run() { val updateKey = throwIfUnsupportedSubcommand(SopCLI.getSop().updateKey(), "update-key") @@ -48,7 +47,8 @@ class UpdateKeyCmd : AbstractSopCmd() { val password = stringFromInputStream(getInput(passwordFileName)) updateKey.withKeyPassword(password) } catch (unsupportedOption: UnsupportedOption) { - val errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") + val errorMsg = + getMsg("sop.error.feature_support.option_not_supported", "--with-key-password") throw UnsupportedOption(errorMsg, unsupportedOption) } catch (e: IOException) { throw RuntimeException(e) @@ -57,7 +57,7 @@ class UpdateKeyCmd : AbstractSopCmd() { for (certInput in mergeCerts) { try { - getInput(certInput).use { certIn -> updateKey.mergeCerts(certIn) } + getInput(certInput).use { updateKey.mergeCerts(it) } } catch (e: IOException) { throw RuntimeException(e) } catch (badData: BadData) { @@ -71,6 +71,9 @@ class UpdateKeyCmd : AbstractSopCmd() { ready.writeTo(System.out) } catch (e: IOException) { throw RuntimeException(e) + } catch (badData: BadData) { + val errorMsg = getMsg("sop.error.input.not_a_private_key", "STDIN") + throw BadData(errorMsg, badData) } } -} \ No newline at end of file +} From 122cd016a1330bf6b723e84fc829da990d8de36f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 18 Sep 2024 16:56:26 +0200 Subject: [PATCH 381/444] Update msg files with input/output information --- .../src/main/resources/msg_armor.properties | 8 ++++++-- .../src/main/resources/msg_armor_de.properties | 6 ++++-- .../src/main/resources/msg_certify-userid.properties | 6 +++--- .../main/resources/msg_certify-userid_de.properties | 6 +++--- .../main/resources/msg_change-key-password.properties | 9 +++++++-- .../resources/msg_change-key-password_de.properties | 5 ++++- .../src/main/resources/msg_dearmor.properties | 9 +++++++-- .../src/main/resources/msg_dearmor_de.properties | 5 ++++- .../src/main/resources/msg_decrypt.properties | 9 +++++++-- .../src/main/resources/msg_decrypt_de.properties | 5 ++++- .../src/main/resources/msg_detached-sign.properties | 9 +++++++-- .../main/resources/msg_detached-sign_de.properties | 5 ++++- .../src/main/resources/msg_detached-verify.properties | 9 +++++++-- .../main/resources/msg_detached-verify_de.properties | 5 ++++- .../src/main/resources/msg_encrypt.properties | 9 +++++++-- .../src/main/resources/msg_encrypt_de.properties | 5 ++++- .../src/main/resources/msg_extract-cert.properties | 9 +++++++-- .../src/main/resources/msg_extract-cert_de.properties | 5 ++++- .../src/main/resources/msg_generate-key.properties | 7 +++++-- .../src/main/resources/msg_generate-key_de.properties | 4 +++- .../src/main/resources/msg_help.properties | 4 ++-- .../src/main/resources/msg_help_de.properties | 2 +- .../src/main/resources/msg_inline-detach.properties | 9 +++++++-- .../main/resources/msg_inline-detach_de.properties | 5 ++++- .../src/main/resources/msg_inline-sign.properties | 9 +++++++-- .../src/main/resources/msg_inline-sign_de.properties | 5 ++++- .../src/main/resources/msg_inline-verify.properties | 9 +++++++-- .../main/resources/msg_inline-verify_de.properties | 5 ++++- .../src/main/resources/msg_list-profiles.properties | 7 +++++-- .../main/resources/msg_list-profiles_de.properties | 4 +++- .../src/main/resources/msg_merge-certs.properties | 10 ++++++++-- .../src/main/resources/msg_merge-certs_de.properties | 10 +++++----- .../src/main/resources/msg_revoke-key.properties | 11 ++++++++--- .../src/main/resources/msg_revoke-key_de.properties | 5 ++++- .../src/main/resources/msg_sop.properties | 1 + .../src/main/resources/msg_sop_de.properties | 1 + .../src/main/resources/msg_update-key.properties | 9 +++++++-- .../src/main/resources/msg_update-key_de.properties | 5 ++++- .../src/main/resources/msg_version.properties | 6 ++++-- .../src/main/resources/msg_version_de.properties | 4 +++- 40 files changed, 190 insertions(+), 66 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_armor.properties b/sop-java-picocli/src/main/resources/msg_armor.properties index b4dcb59..1b7c1fb 100644 --- a/sop-java-picocli/src/main/resources/msg_armor.properties +++ b/sop-java-picocli/src/main/resources/msg_armor.properties @@ -3,9 +3,13 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Add ASCII Armor to standard input +standardInput=BINARY +standardInputDescription=OpenPGP material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutput=ARMORED +standardOutputDescription=Same material, but with ASCII-armoring added, if not already present + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_armor_de.properties b/sop-java-picocli/src/main/resources/msg_armor_de.properties index 4c365a8..34383c8 100644 --- a/sop-java-picocli/src/main/resources/msg_armor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_armor_de.properties @@ -3,9 +3,11 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Schütze Standard-Eingabe mit ASCII Armor +standardInputDescription=OpenPGP Material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutputDescription=Dasselbe Material, aber mit ASCII Armor kodiert, falls noch nicht geschehen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 -usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties index 5eb7aa3..252aae4 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -16,8 +16,8 @@ standardOutputDescription=Certified certificates stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.parameterListHeading=Parameters:%n +usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = Commands:%n -usage.optionListHeading = Options:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties index 0237fa6..9f0a673 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -13,8 +13,8 @@ standardInputDescription=Zertifikate, auf denen Identit standardOutputDescription=Zertifizierte Zertifikate # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.parameterListHeading=Parameter:%n +usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 -usage.commandListHeading=Befehle:%n -usage.optionListHeading = Optionen:%n +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password.properties b/sop-java-picocli/src/main/resources/msg_change-key-password.properties index 3de3608..79bc11b 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password.properties @@ -12,10 +12,15 @@ old-key-password.0=Old passwords to unlock the keys with. old-key-password.1=Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys. old-key-password.2=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardInput=KEYS +standardInputDescription=OpenPGP keys whose passphrases shall be changed +standardOutput=KEYS +standardOutputDescription=OpenPGP keys with changed passphrases + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties index 014c3e7..5515c1d 100644 --- a/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties +++ b/sop-java-picocli/src/main/resources/msg_change-key-password_de.properties @@ -12,10 +12,13 @@ old-key-password.0=Alte Passw old-key-password.1=Mehrere Passwortkandidaten können übergeben werden, welche der Reihe nach durchprobiert werden, um Unterschlüssel zu entsperren. old-key-password.2=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardInputDescription=OpenPGP Schlüssel deren Passwörter geändert werden sollen +standardOutputDescription=OpenPGP Schlüssel mit geänderten Passwörtern + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor.properties b/sop-java-picocli/src/main/resources/msg_dearmor.properties index b088de1..55cbf45 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor.properties @@ -3,9 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Remove ASCII Armor from standard input +standardInput=ARMORED +standardInputDescription=Armored OpenPGP material (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutput=BINARY +standardOutputDescription=Same material, but with ASCII-armoring removed + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties index 362ccef..e01ab7a 100644 --- a/sop-java-picocli/src/main/resources/msg_dearmor_de.properties +++ b/sop-java-picocli/src/main/resources/msg_dearmor_de.properties @@ -3,9 +3,12 @@ # SPDX-License-Identifier: Apache-2.0 usage.header=Entferne ASCII Armor von Standard-Eingabe +standardInputDescription=OpenPGP Material mit ASCII Armor (SIGNATURES, KEYS, CERTS, CIPHERTEXT, INLINESIGNED) +standardOutputDescription=Dasselbe Material, aber mit entfernter ASCII Armor + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt.properties b/sop-java-picocli/src/main/resources/msg_decrypt.properties index 5903ded..bec315f 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt.properties @@ -22,10 +22,15 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). KEY[0..*]=Secret keys to attempt decryption with +standardInput=CIPHERTEXT +standardInputDescription=Encrypted OpenPGP message +standardOutput=DATA +standardOutputDescription=Decrypted OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties index ba40897..395a89f 100644 --- a/sop-java-picocli/src/main/resources/msg_decrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_decrypt_de.properties @@ -22,10 +22,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). KEY[0..*]=Private Schlüssel zum Entschlüsseln der Nachricht +standardInputDescription=Verschlüsselte OpenPGP Nachricht +standardOutputDescription=Entschlüsselte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign.properties b/sop-java-picocli/src/main/resources/msg_detached-sign.properties index 83359a6..6ebfd0b 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign.properties @@ -11,10 +11,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg-out=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +standardInput=DATA +standardInputDescription=Data that shall be signed +standardOutput=SIGNATURES +standardOutputDescription=Detached OpenPGP signature(s) + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties index b943da5..39b59b5 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-sign_de.properties @@ -11,10 +11,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg-out=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +standardInputDescription=Daten die signiert werden sollen +standardOutputDescription=Abgetrennte OpenPGP Signatur(en) + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify.properties b/sop-java-picocli/src/main/resources/msg_detached-verify.properties index ee1a468..074a318 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify.properties @@ -13,11 +13,16 @@ not-after.3=Accepts special value "-" for end of time. SIGNATURE[0]=Detached signature CERT[1..*]=Public key certificates for signature verification +standardInput=DATA +standardInputDescription=Data over which the detached signatures were calculated +standardOutput=VERIFICATIONS +standardOutputDescription=Information about successfully verified signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties index 332bff6..e21ee2a 100644 --- a/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_detached-verify_de.properties @@ -13,11 +13,14 @@ not-after.3=Akzeptiert speziellen Wert '-' f SIGNATURE[0]=Abgetrennte Signatur CERT[1..*]=Zertifikate (öffentliche Schlüssel) zur Signaturprüfung +standardInputDescription=Daten, über die die abgetrennten Signaturen erstellt wurden +standardOutputDescription=Informationen über erfolgreich verifizierte Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt.properties b/sop-java-picocli/src/main/resources/msg_encrypt.properties index c0f7f7d..7bbf593 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt.properties @@ -12,10 +12,15 @@ with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). CERTS[0..*]=Certificates the message gets encrypted to +standardInput=DATA +standardInputDescription=Data that shall be encrypted +standardOutput=CIPHERTEXT +standardOutputDescription=Encrypted OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties index 6a3055c..55b0338 100644 --- a/sop-java-picocli/src/main/resources/msg_encrypt_de.properties +++ b/sop-java-picocli/src/main/resources/msg_encrypt_de.properties @@ -12,10 +12,13 @@ with-key-password.0=Passwort zum Entsperren der privaten Schl with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). CERTS[0..*]=Zertifikate für die die Nachricht verschlüsselt werden soll +standardInputDescription=Daten, die verschlüsselt werden sollen +standardOutputDescription=Verschlüsselte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert.properties b/sop-java-picocli/src/main/resources/msg_extract-cert.properties index 82cac0f..1d1dee4 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert.properties @@ -5,10 +5,15 @@ usage.header=Extract a public key certificate from a secret key usage.description=Read a secret key from STDIN and emit the public key certificate to STDOUT. no-armor=ASCII armor the output +standardInput=KEYS +standardInputDescription=Private key(s), from which certificate(s) shall be extracted +standardOutput=CERTS +standardOutputDescription=Extracted certificate(s) + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nDescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties index 0946cfc..c92d31d 100644 --- a/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties +++ b/sop-java-picocli/src/main/resources/msg_extract-cert_de.properties @@ -5,10 +5,13 @@ usage.header=Extrahiere Zertifikat ( usage.description=Lese einen Schlüssel von Standard-Eingabe und gebe das Zertifikat auf Standard-Ausgabe aus. no-armor=Schütze Ausgabe mit ASCII Armor +standardInputDescription=Private Schlüssel, deren Zertifikate extrahiert werden sollen +standardOutputDescription=Extrahierte Zertifikate + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key.properties b/sop-java-picocli/src/main/resources/msg_generate-key.properties index 60ff4a4..c17f7f6 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key.properties @@ -9,10 +9,13 @@ signing-only=Generate a key that can only be used for signing with-key-password.0=Password to protect the private key with with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardOutput=KEYS +standardOutputDescription=Generated OpenPGP key + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties index 6a0ce13..84db04d 100644 --- a/sop-java-picocli/src/main/resources/msg_generate-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_generate-key_de.properties @@ -9,10 +9,12 @@ signing-only=Generiere einen Schl with-key-password.0=Passwort zum Schutz des privaten Schlüssels with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardOutputDescription=Erzeugter OpenPGP Schlüssel + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help.properties b/sop-java-picocli/src/main/resources/msg_help.properties index 797cc79..637c1d0 100644 --- a/sop-java-picocli/src/main/resources/msg_help.properties +++ b/sop-java-picocli/src/main/resources/msg_help.properties @@ -6,6 +6,6 @@ usage.header=Display usage information for the specified subcommand stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_help_de.properties b/sop-java-picocli/src/main/resources/msg_help_de.properties index beea45c..8471188 100644 --- a/sop-java-picocli/src/main/resources/msg_help_de.properties +++ b/sop-java-picocli/src/main/resources/msg_help_de.properties @@ -7,5 +7,5 @@ stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach.properties b/sop-java-picocli/src/main/resources/msg_inline-detach.properties index c100c51..ca0ed6b 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach.properties @@ -5,9 +5,14 @@ usage.header=Split signatures from a clearsigned message no-armor=ASCII armor the output signatures-out=Destination to which a detached signatures block will be written +standardInput=INLINESIGNED +standardInputDescription=Inline-signed OpenPGP message +standardOutput=DATA +standardOutputDescription=The message without any signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties index e59aa34..84b8c47 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-detach_de.properties @@ -5,9 +5,12 @@ usage.header=Trenne Signaturen von Klartext-signierter Nachricht no-armor=Schütze Ausgabe mit ASCII Armor signatures-out=Schreibe abgetrennte Signaturen in Ausgabe +standardInputDescription=Klartext-signierte OpenPGP Nachricht +standardOutputDescription=Nachricht ohne Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign.properties b/sop-java-picocli/src/main/resources/msg_inline-sign.properties index f5143bb..936b417 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign.properties @@ -13,10 +13,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f micalg=Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content-Type (RFC3156). KEYS[0..*]=Secret keys used for signing +standardInput=DATA +standardInputDescription=Data that shall be signed +standardOutput=INLINESIGNED +standardOutputDescription=Inline-signed OpenPGP message + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties index b09b7e4..f8fe906 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-sign_de.properties @@ -13,10 +13,13 @@ with-key-password.1=Ist ein INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, micalg=Gibt den verwendeten Digest-Algorithmus an die angegebene Ausgabe in einer Form aus, die zum Auffüllen des micalg-Parameters für den PGP/MIME Content-Type (RFC3156) verwendet werden kann. KEYS[0..*]=Private Signaturschlüssel +standardInputDescription=Daten, die signiert werden sollen +standardOutputDescription=Inline-signierte OpenPGP Nachricht + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify.properties b/sop-java-picocli/src/main/resources/msg_inline-verify.properties index dfa94d7..2e0d69f 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify.properties @@ -12,10 +12,15 @@ not-after.3=Accepts special value "-" for end of time. verifications-out=File to write details over successful verifications to CERT[0..*]=Public key certificates for signature verification +standardInput=INLINESIGNED +standardInputDescription=Inline-signed OpenPGP message +standardOutput=DATA +standardOutputDescription=The message without any signatures + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties index a9a5722..9b70504 100644 --- a/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties +++ b/sop-java-picocli/src/main/resources/msg_inline-verify_de.properties @@ -12,10 +12,13 @@ not-after.3=Akzeptiert speziellen Wert '-' f verifications-out=Schreibe Status der Signaturprüfung in angegebene Ausgabe CERT[0..*]=Zertifikate (öffentlich Schlüssel) zur Signaturprüfung +standardInputDescription=Inline-signierte OpenPGP Nachricht +standardOutputDescription=Nachricht ohne Signaturen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles.properties b/sop-java-picocli/src/main/resources/msg_list-profiles.properties index 6d5f1a8..3defe8e 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles.properties @@ -4,10 +4,13 @@ usage.header=Emit a list of profiles supported by the identified subcommand subcommand=Subcommand for which to list profiles +standardOutput=PROFILELIST +standardOutputDescription=List of profiles supported by the identified subcommand + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties index ac03c0d..093aeb3 100644 --- a/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties +++ b/sop-java-picocli/src/main/resources/msg_list-profiles_de.properties @@ -4,10 +4,12 @@ usage.header=Gebe eine Liste von Profilen aus, welche vom angegebenen Unterbefehl unterstützt werden subcommand=Unterbefehl, für welchen Profile gelistet werden sollen +standardOutputDescription=Liste von Profilen, die der identifizierte Unterbefehl unterstützt + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties index 866db4b..b01f577 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -6,10 +6,16 @@ usage.description=BLABLA no-armor=ASCII armor the output CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input +standardInput=CERTS +standardInputDescription=Base certificates into which additional elements from the command line shall be merged +standardOutput=CERTS +standardOutputDescription=Merged certificates + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.descriptionHeading=%nNote:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties index 021c535..b1f008c 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs_de.properties @@ -1,19 +1,19 @@ # SPDX-FileCopyrightText: 2024 Paul Schaub # # SPDX-License-Identifier: Apache-2.0 -usage.headerHeading=OpenPGP Zertifikate zusammenführen%n%n +usage.headerHeading=OpenPGP Zertifikate zusammenführen%n usage.header=Führe OpenPGP Zertifikate aus der Standardeingabe mit ensprechenden Elementen aus CERTS zusammen und gebe das Ergebnis auf der Standardausgabe aus usage.description=Es werden nur Zertifikate auf die Standardausgabe geschrieben, welche Teil der Standardeingabe waren no-armor=Schütze Ausgabe mit ASCII Armor CERTS[0..*]=OpenPGP Zertifikate aus denen neue Elemente in die Basiszertifikate aus der Standardeingabe übernommen werden sollen -usage.parameterList.0=STANDARDIN -usage.parameterList.1=STANDARDOUT +standardInputDescription=Basis-Zertifikate, in welche zusätzliche Elemente von der Kommandozeile zusammengeführt werden sollen +standardOutputDescription=Zusammengeführte Zertifikate # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 -usage.descriptionHeading=%nHinweise:%n +usage.descriptionHeading=%nHinweis:%n usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key.properties b/sop-java-picocli/src/main/resources/msg_revoke-key.properties index c7d72b3..f68b774 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key.properties @@ -7,10 +7,15 @@ no-armor=ASCII armor the output with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). +standardInput=KEYS +standardInputDescription=OpenPGP key that shall be revoked +standardOutput=CERTS +standardOutputDescription=Revocation certificate + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 -usage.descriptionHeading=%nDescription:%n +usage.descriptionHeading=D%nescription:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties index 95db272..fa8c5b4 100644 --- a/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_revoke-key_de.properties @@ -7,10 +7,13 @@ no-armor=Sch with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). +standardInputDescription=OpenPGP Schlüssel, der widerrufen werden soll +standardOutputDescription=Widerrufszertifikat + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.descriptionHeading=%nBeschreibung:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index d5d997a..097a2e2 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -11,6 +11,7 @@ locale=Locale for description texts usage.synopsisHeading=Usage:\u0020 usage.commandListHeading=%nCommands:%n usage.optionListHeading=%nOptions:%n +usage.parameterListHeading=%nParameters:%n usage.footerHeading=Powered by picocli%n standardInputHeading=%nInput:%n diff --git a/sop-java-picocli/src/main/resources/msg_sop_de.properties b/sop-java-picocli/src/main/resources/msg_sop_de.properties index 73efe89..99d28a7 100644 --- a/sop-java-picocli/src/main/resources/msg_sop_de.properties +++ b/sop-java-picocli/src/main/resources/msg_sop_de.properties @@ -11,6 +11,7 @@ locale=Gebietsschema f usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n usage.optionListHeading=%nOptionen:%n +usage.parameterListHeading=%nParameter:%n usage.footerHeading=Powered by Picocli%n standardInputHeading=%nEingabe:%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key.properties b/sop-java-picocli/src/main/resources/msg_update-key.properties index dd4446d..e12fbbc 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key.properties @@ -10,10 +10,15 @@ with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, f merge-certs.0=Merge additional elements found in the corresponding CERTS objects into the updated secret keys merge-certs.1=This can be used, for example, to absorb a third-party certification into the Transferable Secret Key +standardInput=KEYS +standardInputDescription=OpenPGP key that shall be kept up-to-date +standardOutput=KEYS +standardOutputDescription=Updated OpenPGP key + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_update-key_de.properties b/sop-java-picocli/src/main/resources/msg_update-key_de.properties index 86b999e..1b8a84d 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key_de.properties @@ -10,9 +10,12 @@ with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dat merge-certs.0=Führe zusätzliche Elemente aus entsprechenden CERTS Objekten mit dem privaten Schlüssel zusammen merge-certs.1=Dies kann zum Beispiel dazu genutzt werden, Zertifizierungen dritter in den privaten Schlüssel zu übernehmen +standardInputDescription=OpenPGP Schlüssel, der auf den neusten Stand gebracht werden soll +standardOutputDescription=Erneuerter OpenPGP Schlüssel + # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index 9e1451b..c7d0168 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -6,9 +6,11 @@ extended=Print an extended version string backend=Print information about the cryptographic backend sop-spec=Print the latest revision of the SOP specification targeted by the implementation +standardOutput=version information + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Usage:\u0020 -usage.commandListHeading = %nCommands:%n -usage.optionListHeading = %nOptions:%n +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index 608b0c6..c317916 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -6,9 +6,11 @@ extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird +standardOutput=Versionsinformationen + stacktrace=Stacktrace ausgeben # Generic TODO: Remove when bumping picocli to 4.7.0 usage.synopsisHeading=Aufruf:\u0020 usage.commandListHeading=%nBefehle:%n -usage.optionListHeading = %nOptionen:%n +usage.optionListHeading=%nOptionen:%n usage.footerHeading=Powered by Picocli%n From 4ed326a14297e2dcae58e1ad9e13dcff70ff064d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 16:56:25 +0200 Subject: [PATCH 382/444] Implement validate-userid command --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 1 + .../cli/picocli/commands/ValidateUserIdCmd.kt | 74 ++++++++++++++++++ .../src/main/resources/msg_sop.properties | 2 + .../resources/msg_validate-userid.properties | 18 +++++ .../msg_validate-userid_de.properties | 18 +++++ sop-java/src/main/kotlin/sop/SOP.kt | 15 ++-- .../kotlin/sop/exception/SOPGPException.kt | 27 +++++-- .../kotlin/sop/operation/ValidateUserId.kt | 78 +++++++++++++++++++ 8 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt create mode 100644 sop-java-picocli/src/main/resources/msg_validate-userid.properties create mode 100644 sop-java-picocli/src/main/resources/msg_validate-userid_de.properties create mode 100644 sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index d234e54..dc14907 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -30,6 +30,7 @@ import sop.exception.SOPGPException UpdateKeyCmd::class, MergeCertsCmd::class, CertifyUserIdCmd::class, + ValidateUserIdCmd::class, // Messaging subcommands SignCmd::class, VerifyCmd::class, diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt new file mode 100644 index 0000000..c2de148 --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli.commands + +import java.io.IOException +import picocli.CommandLine.Command +import picocli.CommandLine.Option +import picocli.CommandLine.Parameters +import sop.cli.picocli.SopCLI +import sop.exception.SOPGPException +import sop.util.HexUtil.Companion.bytesToHex + +@Command( + name = "validate-userid", + resourceBundle = "msg_validate-userid", + exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE) +class ValidateUserIdCmd : AbstractSopCmd() { + + @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false + + @Parameters(index = "0", arity = "1", paramLabel = "USERID") lateinit var userId: String + + @Parameters(index = "1..*", arity = "1..*", paramLabel = "CERTS") + var authorities: List = listOf() + + override fun run() { + val validateUserId = + throwIfUnsupportedSubcommand(SopCLI.getSop().validateUserId(), "validate-userid") + + if (addrSpecOnly) { + validateUserId.addrSpecOnly() + } + + validateUserId.userId(userId) + + for (authority in authorities) { + try { + getInput(authority).use { validateUserId.authorities(it) } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (b: SOPGPException.BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", authority) + throw SOPGPException.BadData(errorMsg, b) + } + } + + try { + val valid = validateUserId.subjects(System.`in`) + + if (!valid) { + val errorMsg = getMsg("sop.error.runtime.any_cert_user_id_no_match", userId) + throw SOPGPException.CertUserIdNoMatch(errorMsg) + } + } catch (e: SOPGPException.CertUserIdNoMatch) { + val errorMsg = + if (e.fingerprint != null) { + getMsg( + "sop.error.runtime.cert_user_id_no_match", + bytesToHex(e.fingerprint!!), + userId) + } else { + getMsg("sop.error.runtime.any_cert_user_id_no_match", userId) + } + throw SOPGPException.CertUserIdNoMatch(errorMsg, e) + } catch (e: SOPGPException.BadData) { + val errorMsg = getMsg("sop.error.input.not_a_certificate", "STDIN") + throw SOPGPException.BadData(errorMsg, e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } +} diff --git a/sop-java-picocli/src/main/resources/msg_sop.properties b/sop-java-picocli/src/main/resources/msg_sop.properties index 097a2e2..520533a 100644 --- a/sop-java-picocli/src/main/resources/msg_sop.properties +++ b/sop-java-picocli/src/main/resources/msg_sop.properties @@ -80,6 +80,8 @@ sop.error.runtime.cert_cannot_encrypt=Certificate from input '%s' cannot encrypt sop.error.runtime.no_session_key_extracted=Session key not extracted. Feature potentially not supported. sop.error.runtime.no_verifiable_signature_found=No verifiable signature found. sop.error.runtime.cannot_decrypt_message=Message could not be decrypted. +sop.error.runtime.cert_user_id_no_match=Certificate '%s' does not contain a valid binding for user id '%s'. +sop.error.runtime.any_cert_user_id_no_match=Any certificate does not contain a valid binding for user id '%s'. ## Usage errors sop.error.usage.password_or_cert_required=At least one password file or cert file required for encryption. sop.error.usage.argument_required=Argument '%s' is required. diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid.properties b/sop-java-picocli/src/main/resources/msg_validate-userid.properties new file mode 100644 index 0000000..5cfed2d --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_validate-userid.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Validate a UserID in an OpenPGP certificate +addr-spec-only=Treat the USERID as an email address, match only against the email address part of each correctly bound UserID +USERID[0]=UserID +CERTS[1..*]=Authority OpenPGP certificates + +standardInput=CERTS +standardInputDescription=OpenPGP certificates in which UserID bindings shall be validated + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameters:%n +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading=%nCommands:%n +usage.optionListHeading=%nOptions:%n +usage.footerHeading=Powered by picocli%n diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties new file mode 100644 index 0000000..8231c6a --- /dev/null +++ b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2024 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +usage.header=Validiere eine UserID auf OpenPGP Zertifikaten +addr-spec-only=Behandle die USERID als E-Mail-Adresse, vergleiche sie nur mit dem E-Mail-Adressen-Teil jeder korrekten UserID +USERID[0]=UserID +CERTS[1..*]=Autoritäre OpenPGP Zertifikate + +standardInput=CERTS +standardInputDescription=OpenPGP Zertifikate auf denen UserIDs validiert werden sollen + +stacktrace=Print stacktrace +# Generic TODO: Remove when bumping picocli to 4.7.0 +usage.parameterListHeading=%nParameter:%n +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +usage.optionListHeading=%nOptionen:%n +usage.footerHeading=Powered by Picocli%n diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index 5435cad..c5f05e2 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -60,18 +60,15 @@ interface SOP : SOPV { /** Update a key's password. */ fun changeKeyPassword(): ChangeKeyPassword - /** - * Keep a secret key up-to-date. - */ + /** Keep a secret key up-to-date. */ fun updateKey(): UpdateKey - /** - * Merge OpenPGP certificates. - */ + /** Merge OpenPGP certificates. */ fun mergeCerts(): MergeCerts - /** - * Certify OpenPGP Certificate User-IDs. - */ + /** Certify OpenPGP Certificate User-IDs. */ fun certifyUserId(): CertifyUserId + + /** Validate a UserID in an OpenPGP certificate. */ + fun validateUserId(): ValidateUserId } diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 1f9ce6b..862e1bd 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -338,9 +338,7 @@ abstract class SOPGPException : RuntimeException { } } - /** - * The primary key of a KEYS object is too weak or revoked. - */ + /** The primary key of a KEYS object is too weak or revoked. */ class PrimaryKeyBad : SOPGPException { constructor() : super() @@ -353,13 +351,26 @@ abstract class SOPGPException : RuntimeException { } } - /** - * The CERTS object has no matching User ID. - */ + /** The CERTS object has no matching User ID. */ class CertUserIdNoMatch : SOPGPException { - constructor() : super() - constructor(errorMsg: String) : super(errorMsg) + val fingerprint: ByteArray? + + constructor() : super() { + fingerprint = null + } + + constructor(fingerprint: ByteArray) : super() { + this.fingerprint = fingerprint + } + + constructor(errorMsg: String) : super(errorMsg) { + fingerprint = null + } + + constructor(errorMsg: String, cause: Throwable) : super(errorMsg, cause) { + fingerprint = null + } override fun getExitCode(): Int = EXIT_CODE diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt new file mode 100644 index 0000000..4f4c51a --- /dev/null +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.operation + +import java.io.IOException +import java.io.InputStream +import sop.exception.SOPGPException + +/** Subcommand to validate UserIDs on certificates. */ +interface ValidateUserId { + + /** + * If this is set, then the USERID is treated as an e-mail address, and matched only against the + * e-mail address part of each correctly bound User ID. The rest of each correctly bound User ID + * is ignored. + * + * @return this + */ + @Throws(SOPGPException.UnsupportedOption::class) fun addrSpecOnly(): ValidateUserId + + /** + * Set the UserID to validate. To match only the email address, call [addrSpecOnly]. + * + * @param userId UserID or email address + * @return this + */ + fun userId(userId: String): ValidateUserId + + /** + * Add certificates, which act as authorities. The [userId] is only considered correctly bound, + * if it was bound by an authoritative certificate. + * + * @param certs authoritative certificates + * @return this + */ + @Throws(SOPGPException.BadData::class, IOException::class) + fun authorities(certs: InputStream): ValidateUserId + + /** + * Add certificates, which act as authorities. The [userId] is only considered correctly bound, + * if it was bound by an authoritative certificate. + * + * @param certs authoritative certificates + * @return this + */ + @Throws(SOPGPException.BadData::class, IOException::class) + fun authorities(certs: ByteArray): ValidateUserId = authorities(certs.inputStream()) + + /** + * Add subject certificates, on which UserID bindings are validated. + * + * @param certs subject certificates + * @return true if all subject certificates have a correct binding to the UserID. + * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws IOException if a parser exception happens + * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly + * bound UserID that matches [userId]. + */ + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun subjects(certs: InputStream): Boolean + + /** + * Add subject certificates, on which UserID bindings are validated. + * + * @param certs subject certificates + * @return true if all subject certificates have a correct binding to the UserID. + * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws IOException if a parser exception happens + * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly + * bound UserID that matches [userId]. + */ + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream()) +} From dd12e289263557357be7287edc9400361ee61e75 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 16:56:43 +0200 Subject: [PATCH 383/444] Checkstyle --- .../kotlin/sop/operation/CertifyUserId.kt | 24 ++++++++--------- .../main/kotlin/sop/operation/MergeCerts.kt | 9 +++---- .../main/kotlin/sop/operation/UpdateKey.kt | 27 +++++++++++++------ 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt index 92fff20..642966b 100644 --- a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt @@ -4,28 +4,26 @@ package sop.operation +import java.io.IOException +import java.io.InputStream import sop.Ready import sop.exception.SOPGPException import sop.util.UTF8Util -import java.io.IOException -import java.io.InputStream interface CertifyUserId { - @Throws(SOPGPException.UnsupportedOption::class) - fun noArmor(): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) - fun userId(userId: String): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun userId(userId: String): CertifyUserId @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) - fun withKeyPassword(password: String): CertifyUserId = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + fun withKeyPassword(password: String): CertifyUserId = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) - fun noRequireSelfSig(): CertifyUserId + @Throws(SOPGPException.UnsupportedOption::class) fun noRequireSelfSig(): CertifyUserId @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) fun keys(keys: InputStream): CertifyUserId @@ -33,9 +31,11 @@ interface CertifyUserId { @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) fun keys(keys: ByteArray): CertifyUserId = keys(keys.inputStream()) - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun certs(certs: InputStream): Ready - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws( + SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun certs(certs: ByteArray): Ready = certs(certs.inputStream()) -} \ No newline at end of file +} diff --git a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt index f60d291..f922490 100644 --- a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt +++ b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt @@ -4,15 +4,14 @@ package sop.operation -import sop.Ready -import sop.exception.SOPGPException import java.io.IOException import java.io.InputStream +import sop.Ready +import sop.exception.SOPGPException interface MergeCerts { - @Throws(SOPGPException.UnsupportedOption::class) - fun noArmor(): MergeCerts + @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): MergeCerts @Throws(SOPGPException.BadData::class, IOException::class) fun updates(updateCerts: InputStream): MergeCerts @@ -25,4 +24,4 @@ interface MergeCerts { @Throws(SOPGPException.BadData::class, IOException::class) fun baseCertificates(certs: ByteArray): Ready = baseCertificates(certs.inputStream()) -} \ No newline at end of file +} diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 1b12f6f..6c32b22 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -4,11 +4,11 @@ package sop.operation +import java.io.IOException +import java.io.InputStream import sop.Ready import sop.exception.SOPGPException import sop.util.UTF8Util -import java.io.IOException -import java.io.InputStream interface UpdateKey { @@ -24,20 +24,31 @@ interface UpdateKey { @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) - fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + fun withKeyPassword(password: String): UpdateKey = + withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws( + SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: InputStream): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws( + SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + @Throws( + SOPGPException.BadData::class, + IOException::class, + SOPGPException.KeyIsProtected::class, + SOPGPException.PrimaryKeyBad::class) fun key(key: InputStream): Ready - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class, SOPGPException.PrimaryKeyBad::class) + @Throws( + SOPGPException.BadData::class, + IOException::class, + SOPGPException.KeyIsProtected::class, + SOPGPException.PrimaryKeyBad::class) fun key(key: ByteArray): Ready = key(key.inputStream()) -} \ No newline at end of file +} From 69fbfc09a7a77040dc0b7004e3674ec07cc6d4ed Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 17:51:04 +0200 Subject: [PATCH 384/444] Implement external variants of new subcommands --- .../main/kotlin/sop/external/ExternalSOP.kt | 20 ++++++++ .../operation/CertifyUserIdExternal.kt | 48 +++++++++++++++++++ .../external/operation/MergeCertsExternal.kt | 30 ++++++++++++ .../external/operation/UpdateKeyExternal.kt | 41 ++++++++++++++++ .../operation/ValidateUserIdExternal.kt | 38 +++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt create mode 100644 external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index 27c93ae..8ab7737 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -69,6 +69,14 @@ class ExternalSOP( override fun changeKeyPassword(): ChangeKeyPassword = ChangeKeyPasswordExternal(binaryName, properties) + override fun updateKey(): UpdateKey = UpdateKeyExternal(binaryName, properties) + + override fun mergeCerts(): MergeCerts = MergeCertsExternal(binaryName, properties) + + override fun certifyUserId(): CertifyUserId = CertifyUserIdExternal(binaryName, properties) + + override fun validateUserId(): ValidateUserId = ValidateUserIdExternal(binaryName, properties) + /** * This interface can be used to provide a directory in which external SOP binaries can * temporarily store additional results of OpenPGP operations such that the binding classes can @@ -169,6 +177,18 @@ class ExternalSOP( UnsupportedProfile.EXIT_CODE -> throw UnsupportedProfile( "External SOP backend reported error UnsupportedProfile ($exitCode):\n$errorMessage") + NoHardwareKeyFound.EXIT_CODE -> + throw NoHardwareKeyFound( + "External SOP backend reported error NoHardwareKeyFound ($exitCode):\n$errorMessage") + HardwareKeyFailure.EXIT_CODE -> + throw HardwareKeyFailure( + "External SOP backend reported error HardwareKeyFalure ($exitCode):\n$errorMessage") + PrimaryKeyBad.EXIT_CODE -> + throw PrimaryKeyBad( + "External SOP backend reported error PrimaryKeyBad ($exitCode):\n$errorMessage") + CertUserIdNoMatch.EXIT_CODE -> + throw CertUserIdNoMatch( + "External SOP backend reported error CertUserIdNoMatch ($exitCode):\n$errorMessage") // Did you forget to add a case for a new exception type? else -> diff --git a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt new file mode 100644 index 0000000..abf4d50 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.CertifyUserId + +class CertifyUserIdExternal(binary: String, environment: Properties) : CertifyUserId { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + private val keys: MutableList = mutableListOf() + + override fun noArmor(): CertifyUserId = apply { commandList.add("--no-armor") } + + override fun userId(userId: String): CertifyUserId = apply { + commandList.add("--userid") + commandList.add(userId) + } + + override fun withKeyPassword(password: ByteArray): CertifyUserId = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") + envList.add("KEY_PASSWORD_$argCount=${String(password)}") + argCount += 1 + } + + override fun noRequireSelfSig(): CertifyUserId = apply { + commandList.add("--no-require-self-sig") + } + + override fun keys(keys: InputStream): CertifyUserId = apply { + this.keys.add("@ENV:KEY_$argCount") + envList.add("KEY_$argCount=${ExternalSOP.readString(keys)}") + argCount += 1 + } + + override fun certs(certs: InputStream): Ready = + ExternalSOP.executeTransformingOperation( + Runtime.getRuntime(), commandList.plus(keys), envList, certs) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt new file mode 100644 index 0000000..0869fab --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.MergeCerts + +class MergeCertsExternal(binary: String, environment: Properties) : MergeCerts { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + override fun noArmor(): MergeCerts = apply { commandList.add("--no-armor") } + + override fun updates(updateCerts: InputStream): MergeCerts = apply { + commandList.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(updateCerts)}") + argCount += 1 + } + + override fun baseCertificates(certs: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, certs) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt new file mode 100644 index 0000000..9aa1d29 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.Ready +import sop.external.ExternalSOP +import sop.operation.UpdateKey + +class UpdateKeyExternal(binary: String, environment: Properties) : UpdateKey { + + private val commandList = mutableListOf(binary, "update-key") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + override fun noArmor(): UpdateKey = apply { commandList.add("--no-armor") } + + override fun signingOnly(): UpdateKey = apply { commandList.add("--signing-only") } + + override fun noNewMechanisms(): UpdateKey = apply { commandList.add("--no-new-mechanisms") } + + override fun withKeyPassword(password: ByteArray): UpdateKey = apply { + commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") + envList.add("KEY_PASSWORD_$argCount=${String(password)}") + argCount += 1 + } + + override fun mergeCerts(certs: InputStream): UpdateKey = apply { + commandList.add("--merge-certs") + commandList.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(certs)}") + argCount += 1 + } + + override fun key(key: InputStream): Ready = + ExternalSOP.executeTransformingOperation(Runtime.getRuntime(), commandList, envList, key) +} diff --git a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt new file mode 100644 index 0000000..867a755 --- /dev/null +++ b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.external.operation + +import java.io.InputStream +import java.util.* +import sop.external.ExternalSOP +import sop.operation.ValidateUserId + +class ValidateUserIdExternal(binary: String, environment: Properties) : ValidateUserId { + + private val commandList = mutableListOf(binary, "version") + private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() + + private var argCount = 0 + + private var userId: String? = null + private val authorities: MutableList = mutableListOf() + + override fun addrSpecOnly(): ValidateUserId = apply { commandList.add("--addr-spec-only") } + + override fun userId(userId: String): ValidateUserId = apply { this.userId = userId } + + override fun authorities(certs: InputStream): ValidateUserId = apply { + this.authorities.add("@ENV:CERT_$argCount") + envList.add("CERT_$argCount=${ExternalSOP.readString(certs)}") + argCount += 1 + } + + override fun subjects(certs: InputStream): Boolean { + ExternalSOP.executeTransformingOperation( + Runtime.getRuntime(), commandList.plus(userId!!).plus(authorities), envList, certs) + .bytes + return true + } +} From 0bb50952c5baa48d237990bd739102b192d280e8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:25:03 +0200 Subject: [PATCH 385/444] Show endOfOptions delimiter in help --- .../kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt | 7 ++----- .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt index 71ef79f..228809b 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/CertifyUserIdCmd.kt @@ -6,10 +6,8 @@ package sop.cli.picocli.commands import java.io.IOException import picocli.CommandLine.Command -import picocli.CommandLine.Model.CommandSpec import picocli.CommandLine.Option import picocli.CommandLine.Parameters -import picocli.CommandLine.Spec import sop.cli.picocli.SopCLI import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.UnsupportedOption @@ -17,11 +15,10 @@ import sop.exception.SOPGPException.UnsupportedOption @Command( name = "certify-userid", resourceBundle = "msg_certify-userid", - exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE) + exitCodeOnInvalidInput = UnsupportedOption.EXIT_CODE, + showEndOfOptionsDelimiterInUsageHelp = true) class CertifyUserIdCmd : AbstractSopCmd() { - @Spec var spec: CommandSpec? = null - @Option(names = ["--no-armor"], negatable = true) var armor = true @Option(names = ["--userid"], required = true, arity = "1..*", paramLabel = "USERID") diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt index c2de148..da81a27 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -15,7 +15,8 @@ import sop.util.HexUtil.Companion.bytesToHex @Command( name = "validate-userid", resourceBundle = "msg_validate-userid", - exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE) + exitCodeOnInvalidInput = SOPGPException.MissingArg.EXIT_CODE, + showEndOfOptionsDelimiterInUsageHelp = true) class ValidateUserIdCmd : AbstractSopCmd() { @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false From 40ccb8cc9936f8d32252e67fcbfe7e853c3ad9da Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:25:17 +0200 Subject: [PATCH 386/444] Add first test for new commands --- .../test/java/sop/cli/picocli/SOPTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index fe49472..4d36322 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -13,10 +13,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; import sop.operation.Armor; +import sop.operation.CertifyUserId; import sop.operation.ChangeKeyPassword; import sop.operation.Dearmor; import sop.operation.Decrypt; @@ -29,7 +31,10 @@ import sop.operation.InlineVerify; import sop.operation.DetachedSign; import sop.operation.DetachedVerify; import sop.operation.ListProfiles; +import sop.operation.MergeCerts; import sop.operation.RevokeKey; +import sop.operation.UpdateKey; +import sop.operation.ValidateUserId; import sop.operation.Version; public class SOPTest { @@ -52,6 +57,30 @@ public class SOPTest { @Test public void UnsupportedSubcommandsTest() { SOP nullCommandSOP = new SOP() { + @NotNull + @Override + public ValidateUserId validateUserId() { + return null; + } + + @NotNull + @Override + public CertifyUserId certifyUserId() { + return null; + } + + @NotNull + @Override + public MergeCerts mergeCerts() { + return null; + } + + @NotNull + @Override + public UpdateKey updateKey() { + return null; + } + @Override public Version version() { return null; @@ -140,6 +169,11 @@ public class SOPTest { commands.add(new String[] {"sign"}); commands.add(new String[] {"verify", "signature.asc", "cert.asc"}); commands.add(new String[] {"version"}); + commands.add(new String[] {"list-profiles", "generate-key"}); + commands.add(new String[] {"certify-userid", "--userid", "Alice ", "--", "alice.pgp"}); + commands.add(new String[] {"validate-userid", "Alice ", "bob.pgp", "--", "alice.pgp"}); + commands.add(new String[] {"update-key"}); + commands.add(new String[] {"merge-certs"}); for (String[] command : commands) { int exit = SopCLI.execute(command); From f7cc9ab816f51ffa17780bd437a960443fbaddc2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 18:40:55 +0200 Subject: [PATCH 387/444] Fix nullability of sop commands --- .../test/java/sop/cli/picocli/SOPTest.java | 5 --- sop-java/src/main/kotlin/sop/SOP.kt | 34 +++++++++---------- sop-java/src/main/kotlin/sop/SOPV.kt | 8 ++--- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 4d36322..62c7581 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import sop.SOP; import sop.exception.SOPGPException; @@ -57,25 +56,21 @@ public class SOPTest { @Test public void UnsupportedSubcommandsTest() { SOP nullCommandSOP = new SOP() { - @NotNull @Override public ValidateUserId validateUserId() { return null; } - @NotNull @Override public CertifyUserId certifyUserId() { return null; } - @NotNull @Override public MergeCerts mergeCerts() { return null; } - @NotNull @Override public UpdateKey updateKey() { return null; diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index c5f05e2..fbd0428 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -15,60 +15,60 @@ import sop.operation.* interface SOP : SOPV { /** Generate a secret key. */ - fun generateKey(): GenerateKey + fun generateKey(): GenerateKey? /** Extract a certificate (public key) from a secret key. */ - fun extractCert(): ExtractCert + fun extractCert(): ExtractCert? /** * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. */ - fun sign(): DetachedSign = detachedSign() + fun sign(): DetachedSign? = detachedSign() /** * Create detached signatures. If you want to sign a message inline, use [inlineSign] instead. */ - fun detachedSign(): DetachedSign + fun detachedSign(): DetachedSign? /** * Sign a message using inline signatures. If you need to create detached signatures, use * [detachedSign] instead. */ - fun inlineSign(): InlineSign + fun inlineSign(): InlineSign? /** Detach signatures from an inline signed message. */ - fun inlineDetach(): InlineDetach + fun inlineDetach(): InlineDetach? /** Encrypt a message. */ - fun encrypt(): Encrypt + fun encrypt(): Encrypt? /** Decrypt a message. */ - fun decrypt(): Decrypt + fun decrypt(): Decrypt? /** Convert binary OpenPGP data to ASCII. */ - fun armor(): Armor + fun armor(): Armor? /** Converts ASCII armored OpenPGP data to binary. */ - fun dearmor(): Dearmor + fun dearmor(): Dearmor? /** List supported [Profiles][Profile] of a subcommand. */ - fun listProfiles(): ListProfiles + fun listProfiles(): ListProfiles? /** Revoke one or more secret keys. */ - fun revokeKey(): RevokeKey + fun revokeKey(): RevokeKey? /** Update a key's password. */ - fun changeKeyPassword(): ChangeKeyPassword + fun changeKeyPassword(): ChangeKeyPassword? /** Keep a secret key up-to-date. */ - fun updateKey(): UpdateKey + fun updateKey(): UpdateKey? /** Merge OpenPGP certificates. */ - fun mergeCerts(): MergeCerts + fun mergeCerts(): MergeCerts? /** Certify OpenPGP Certificate User-IDs. */ - fun certifyUserId(): CertifyUserId + fun certifyUserId(): CertifyUserId? /** Validate a UserID in an OpenPGP certificate. */ - fun validateUserId(): ValidateUserId + fun validateUserId(): ValidateUserId? } diff --git a/sop-java/src/main/kotlin/sop/SOPV.kt b/sop-java/src/main/kotlin/sop/SOPV.kt index d331559..58a7f13 100644 --- a/sop-java/src/main/kotlin/sop/SOPV.kt +++ b/sop-java/src/main/kotlin/sop/SOPV.kt @@ -12,23 +12,23 @@ import sop.operation.Version interface SOPV { /** Get information about the implementations name and version. */ - fun version(): Version + fun version(): Version? /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. */ - fun verify(): DetachedVerify = detachedVerify() + fun verify(): DetachedVerify? = detachedVerify() /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. */ - fun detachedVerify(): DetachedVerify + fun detachedVerify(): DetachedVerify? /** * Verify signatures of an inline-signed message. If you need to verify detached signatures over * a message, use [detachedVerify] instead. */ - fun inlineVerify(): InlineVerify + fun inlineVerify(): InlineVerify? } From f1bdce99cb4ccc14d76db6f2bd425f3766cb8388 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 19:19:56 +0200 Subject: [PATCH 388/444] Document endOfOptionsDelimiter --- .../src/main/resources/msg_certify-userid.properties | 2 ++ .../src/main/resources/msg_certify-userid_de.properties | 2 ++ .../src/main/resources/msg_validate-userid.properties | 2 ++ .../src/main/resources/msg_validate-userid_de.properties | 2 ++ 4 files changed, 8 insertions(+) diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid.properties b/sop-java-picocli/src/main/resources/msg_certify-userid.properties index 252aae4..36dc6f4 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid.properties @@ -14,6 +14,8 @@ standardInputDescription=Certificates that shall be certified standardOutput=CERTS standardOutputDescription=Certified certificates +picocli.endofoptions.description=End of options. Remainder are positional parameters. Fixes 'Missing required parameter' error + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n diff --git a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties index 9f0a673..d634c59 100644 --- a/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_certify-userid_de.properties @@ -12,6 +12,8 @@ KEYS[0..*]=Private Schl standardInputDescription=Zertifikate, auf denen Identitäten zertifiziert werden sollen standardOutputDescription=Zertifizierte Zertifikate +picocli.endofoptions.description=Ende der Optionen. Der Rest sind Positionsparameter. Behebt 'Missing required parameter' Fehler + # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n usage.synopsisHeading=Aufruf:\u0020 diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid.properties b/sop-java-picocli/src/main/resources/msg_validate-userid.properties index 5cfed2d..d25fa3a 100644 --- a/sop-java-picocli/src/main/resources/msg_validate-userid.properties +++ b/sop-java-picocli/src/main/resources/msg_validate-userid.properties @@ -9,6 +9,8 @@ CERTS[1..*]=Authority OpenPGP certificates standardInput=CERTS standardInputDescription=OpenPGP certificates in which UserID bindings shall be validated +picocli.endofoptions.description=End of options. Remainder are positional parameters. Fixes 'Missing required parameter' error + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameters:%n diff --git a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties index 8231c6a..f919465 100644 --- a/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties +++ b/sop-java-picocli/src/main/resources/msg_validate-userid_de.properties @@ -9,6 +9,8 @@ CERTS[1..*]=Autorit standardInput=CERTS standardInputDescription=OpenPGP Zertifikate auf denen UserIDs validiert werden sollen +picocli.endofoptions.description=Ende der Optionen. Der Rest sind Positionsparameter. Behebt 'Missing required parameter' Fehler + stacktrace=Print stacktrace # Generic TODO: Remove when bumping picocli to 4.7.0 usage.parameterListHeading=%nParameter:%n From 4cf410a9f9282fb142d922ac30efc32e66e29286 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:26:36 +0200 Subject: [PATCH 389/444] Bump version --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index b4908ee..c757e1e 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '10.1.2' + shortVersion = '11.0.0' isSnapshot = true minAndroidSdk = 10 javaSourceCompatibility = 11 From 7f1c1b1aae0e27b8f0c5efdbbb436a1ae60b4ced Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 19 Sep 2024 20:49:00 +0200 Subject: [PATCH 390/444] Fix documentation of merge-certs command --- sop-java-picocli/src/main/resources/msg_merge-certs.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/resources/msg_merge-certs.properties b/sop-java-picocli/src/main/resources/msg_merge-certs.properties index b01f577..8c0bfa3 100644 --- a/sop-java-picocli/src/main/resources/msg_merge-certs.properties +++ b/sop-java-picocli/src/main/resources/msg_merge-certs.properties @@ -2,7 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 usage.headerHeading=Merge OpenPGP certificates%n -usage.description=BLABLA +usage.header=Merge OpenPGP certificates from standard input with related elements from CERTS and emit the result to standard output +usage.description=Only certificates that were part of standard input will be emitted to standard output no-armor=ASCII armor the output CERTS[0..*]=OpenPGP certificates from which updates shall be merged into the base certificates from standard input From 5105b6f4addc139e21b1e6025927a4b3a98fa1a0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Apr 2025 13:13:15 +0200 Subject: [PATCH 391/444] Remove call to explicitly set bundle to fix native image --- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index dc14907..943f0f3 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -85,8 +85,6 @@ class SopCLI { return CommandLine(SopCLI::class.java) .apply { - // explicitly set help command resource bundle - subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) // Hide generate-completion command subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) // render Input/Output sections in help command From b300be42a4c2c3c1be0ee1e70dde9ceb3d1ffb72 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 8 Apr 2025 17:08:06 +0200 Subject: [PATCH 392/444] validate-userid: Add --validate-at option --- .../sop/external/operation/ValidateUserIdExternal.kt | 5 +++++ .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 7 +++++++ sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt | 3 +++ 3 files changed, 15 insertions(+) diff --git a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt index 867a755..581d6f5 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt @@ -8,6 +8,7 @@ import java.io.InputStream import java.util.* import sop.external.ExternalSOP import sop.operation.ValidateUserId +import sop.util.UTCUtil class ValidateUserIdExternal(binary: String, environment: Properties) : ValidateUserId { @@ -35,4 +36,8 @@ class ValidateUserIdExternal(binary: String, environment: Properties) : Validate .bytes return true } + + override fun validateAt(date: Date): ValidateUserId = apply { + commandList.add("--validate-at=${UTCUtil.formatUTCDate(date)}") + } } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt index da81a27..9a09a15 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -11,6 +11,7 @@ import picocli.CommandLine.Parameters import sop.cli.picocli.SopCLI import sop.exception.SOPGPException import sop.util.HexUtil.Companion.bytesToHex +import java.util.* @Command( name = "validate-userid", @@ -21,6 +22,8 @@ class ValidateUserIdCmd : AbstractSopCmd() { @Option(names = ["--addr-spec-only"]) var addrSpecOnly: Boolean = false + @Option(names = ["--validate-at"]) var validateAt: Date? = null + @Parameters(index = "0", arity = "1", paramLabel = "USERID") lateinit var userId: String @Parameters(index = "1..*", arity = "1..*", paramLabel = "CERTS") @@ -34,6 +37,10 @@ class ValidateUserIdCmd : AbstractSopCmd() { validateUserId.addrSpecOnly() } + if (validateAt != null) { + validateUserId.validateAt(validateAt!!) + } + validateUserId.userId(userId) for (authority in authorities) { diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index 4f4c51a..fb8cab6 100644 --- a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -7,6 +7,7 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.exception.SOPGPException +import java.util.* /** Subcommand to validate UserIDs on certificates. */ interface ValidateUserId { @@ -75,4 +76,6 @@ interface ValidateUserId { @Throws( SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream()) + + fun validateAt(date: Date): ValidateUserId } From 082cbde869e72615eec2ca6ffe50570f545cabf4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:26:29 +0200 Subject: [PATCH 393/444] MergeCertsCmd: Fix default value of armor --- .../src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt index 16d56e3..3dcef38 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/MergeCertsCmd.kt @@ -16,7 +16,7 @@ import sop.exception.SOPGPException exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) class MergeCertsCmd : AbstractSopCmd() { - @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = false + @CommandLine.Option(names = ["--no-armor"], negatable = true) var armor = true @CommandLine.Parameters(paramLabel = "CERTS") var updates: List = listOf() From a72545e3b9d4f2cb2ca8be3c36b7a7a3ced49296 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:46:50 +0200 Subject: [PATCH 394/444] Fix formatting --- .../src/main/kotlin/sop/cli/picocli/SopCLI.kt | 8 ++++++-- .../kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt | 2 +- sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 943f0f3..98701fc 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -88,7 +88,10 @@ class SopCLI { // Hide generate-completion command subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) // render Input/Output sections in help command - subcommands.values.filter { (it.getCommand() as Any) is AbstractSopCmd } // Only for AbstractSopCmd objects + subcommands.values + .filter { + (it.getCommand() as Any) is AbstractSopCmd + } // Only for AbstractSopCmd objects .forEach { (it.getCommand() as AbstractSopCmd).installIORenderer(it) } // overwrite executable name commandName = EXECUTABLE_NAME @@ -96,7 +99,8 @@ class SopCLI { executionExceptionHandler = SOPExecutionExceptionHandler() exitCodeExceptionMapper = SOPExceptionExitCodeMapper() isCaseInsensitiveEnumValuesAllowed = true - }.execute(*args) + } + .execute(*args) } } diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt index 9a09a15..b83e5a8 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/ValidateUserIdCmd.kt @@ -5,13 +5,13 @@ package sop.cli.picocli.commands import java.io.IOException +import java.util.* import picocli.CommandLine.Command import picocli.CommandLine.Option import picocli.CommandLine.Parameters import sop.cli.picocli.SopCLI import sop.exception.SOPGPException import sop.util.HexUtil.Companion.bytesToHex -import java.util.* @Command( name = "validate-userid", diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index fb8cab6..fe20fd4 100644 --- a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -6,8 +6,8 @@ package sop.operation import java.io.IOException import java.io.InputStream -import sop.exception.SOPGPException import java.util.* +import sop.exception.SOPGPException /** Subcommand to validate UserIDs on certificates. */ interface ValidateUserId { From 65aa0afd4ee760b60a8b6162551a54f0b5bd113d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 12:47:00 +0200 Subject: [PATCH 395/444] Add new Exception types --- .../kotlin/sop/exception/SOPGPException.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt index 862e1bd..9df1628 100644 --- a/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt +++ b/sop-java/src/main/kotlin/sop/exception/SOPGPException.kt @@ -16,6 +16,22 @@ abstract class SOPGPException : RuntimeException { abstract fun getExitCode(): Int + /** An otherwise unspecified failure occurred */ + class UnspecificFailure : SOPGPException { + + constructor(message: String) : super(message) + + constructor(message: String, e: Throwable) : super(message, e) + + constructor(e: Throwable) : super(e) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 1 + } + } + /** No acceptable signatures found (sop verify, inline-verify). */ class NoSignature : SOPGPException { @JvmOverloads @@ -378,4 +394,23 @@ abstract class SOPGPException : RuntimeException { const val EXIT_CODE = 107 } } + + /** + * Key not certification-capable (e.g., expired, revoked, unacceptable usage flags) (sop + * certify-userid) + */ + class KeyCannotCertify : SOPGPException { + + constructor(message: String) : super(message) + + constructor(message: String, e: Throwable) : super(message, e) + + constructor(e: Throwable) : super(e) + + override fun getExitCode(): Int = EXIT_CODE + + companion object { + const val EXIT_CODE = 109 + } + } } From 68bab9cbb433ba0d5f1ee91bd3f6270e9781f6ea Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 13:50:35 +0200 Subject: [PATCH 396/444] reuse: convert dep5 file to toml file --- REUSE.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/REUSE.toml b/REUSE.toml index f82d8f8..7e1b250 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,6 +1,6 @@ -# SPDX-FileCopyrightText: 2025 Paul Schaub +# SPDX-FileCopyrightText: 2025 Paul Schaub # -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: CC0-1.0 version = 1 SPDX-PackageName = "SOP-Java" From a8497617d58e5c70391715713d809a74e99fed5a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 13:50:57 +0200 Subject: [PATCH 397/444] Add basic test for certify-userid and validate-userid subcommands --- .../operation/CertifyValidateUserIdTest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java new file mode 100644 index 0000000..eabda44 --- /dev/null +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; +import sop.exception.SOPGPException; + +import java.io.IOException; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class CertifyValidateUserIdTest { + + static Stream provideInstances() { + return AbstractSOPTest.provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void certifyUserId(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .withKeyPassword("sw0rdf1sh") + .userId("Alice ") + .generate() + .getBytes(); + byte[] aliceCert = sop.extractCert() + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .userId("Bob ") + .generate() + .getBytes(); + byte[] bobCert = sop.extractCert() + .key(bobKey) + .getBytes(); + + // Alice has her own user-id self-certified + assertTrue(sop.validateUserId() + .authorities(aliceCert) + .userId("Alice ") + .subjects(aliceCert)); + + // Alice has not yet certified Bobs user-id + assertFalse(sop.validateUserId() + .authorities(aliceCert) + .userId("Bob ") + .subjects(bobCert)); + + byte[] bobCertifiedByAlice = sop.certifyUserId() + .userId("Bob ") + .withKeyPassword("sw0rdf1sh") + .keys(aliceKey) + .certs(bobCert) + .getBytes(); + + assertTrue(sop.validateUserId() + .userId("Bob ") + .authorities(aliceCert) + .subjects(bobCertifiedByAlice)); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void addPetName(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .userId("Alice ") + .generate() + .getBytes(); + byte[] aliceCert = sop.extractCert() + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .userId("Bob ") + .generate() + .getBytes(); + byte[] bobCert = sop.extractCert() + .key(bobKey) + .getBytes(); + + assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> + sop.certifyUserId() + .userId("Bobby") + .keys(aliceKey) + .certs(bobCert) + .getBytes()); + + byte[] bobWithPetName = sop.certifyUserId() + .userId("Bobby") + .noRequireSelfSig() + .keys(aliceKey) + .certs(bobCert) + .getBytes(); + + assertTrue(sop.validateUserId() + .userId("Bobby") + .authorities(aliceCert) + .subjects(bobWithPetName)); + } +} From a8cfb8fbf468f324d39cf00a2f501ade96f54a83 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:17:38 +0200 Subject: [PATCH 398/444] Improve test --- .../operation/CertifyValidateUserIdTest.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java index eabda44..488db9a 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -49,13 +49,15 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .authorities(aliceCert) .userId("Alice ") - .subjects(aliceCert)); + .subjects(aliceCert), + "Alice accepts her own self-certified user-id"); // Alice has not yet certified Bobs user-id assertFalse(sop.validateUserId() .authorities(aliceCert) .userId("Bob ") - .subjects(bobCert)); + .subjects(bobCert), + "Alice has not yet certified Bobs user-id"); byte[] bobCertifiedByAlice = sop.certifyUserId() .userId("Bob ") @@ -67,7 +69,8 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .userId("Bob ") .authorities(aliceCert) - .subjects(bobCertifiedByAlice)); + .subjects(bobCertifiedByAlice), + "Alice accepts Bobs user-id after she certified it"); } @ParameterizedTest @@ -94,7 +97,8 @@ public class CertifyValidateUserIdTest { .userId("Bobby") .keys(aliceKey) .certs(bobCert) - .getBytes()); + .getBytes(), + "Alice cannot create a pet-name for Bob without the --no-require-self-sig flag"); byte[] bobWithPetName = sop.certifyUserId() .userId("Bobby") @@ -106,6 +110,13 @@ public class CertifyValidateUserIdTest { assertTrue(sop.validateUserId() .userId("Bobby") .authorities(aliceCert) - .subjects(bobWithPetName)); + .subjects(bobWithPetName), + "Alice accepts the pet-name she gave to Bob"); + + assertFalse(sop.validateUserId() + .userId("Bobby") + .authorities(bobWithPetName) + .subjects(bobWithPetName), + "Bob does not accept the pet-name Alice gave him"); } } From 8c077a9c131554671f3e56575e0432c297753beb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:36:28 +0200 Subject: [PATCH 399/444] SOP update-key: Rename --no-new-mechanisms option to --no-added-capabilities --- .../main/kotlin/sop/external/operation/UpdateKeyExternal.kt | 2 +- .../main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt | 6 +++--- sop-java/src/main/kotlin/sop/operation/UpdateKey.kt | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt index 9aa1d29..99df15e 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt @@ -21,7 +21,7 @@ class UpdateKeyExternal(binary: String, environment: Properties) : UpdateKey { override fun signingOnly(): UpdateKey = apply { commandList.add("--signing-only") } - override fun noNewMechanisms(): UpdateKey = apply { commandList.add("--no-new-mechanisms") } + override fun noAddedCapabilities(): UpdateKey = apply { commandList.add("--no-added-capabilities") } override fun withKeyPassword(password: ByteArray): UpdateKey = apply { commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt index 08f9297..931f241 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/commands/UpdateKeyCmd.kt @@ -20,7 +20,7 @@ class UpdateKeyCmd : AbstractSopCmd() { @Option(names = ["--signing-only"]) var signingOnly = false - @Option(names = ["--no-new-mechanisms"]) var noNewMechanisms = false + @Option(names = ["--no-added-capabilities"]) var noAddedCapabilities = false @Option(names = ["--with-key-password"], paramLabel = "PASSWORD") var withKeyPassword: List = listOf() @@ -38,8 +38,8 @@ class UpdateKeyCmd : AbstractSopCmd() { updateKey.signingOnly() } - if (noNewMechanisms) { - updateKey.noNewMechanisms() + if (noAddedCapabilities) { + updateKey.noAddedCapabilities() } for (passwordFileName in withKeyPassword) { diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 6c32b22..f0a7c52 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -21,7 +21,7 @@ interface UpdateKey { @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey - @Throws(SOPGPException.UnsupportedOption::class) fun noNewMechanisms(): UpdateKey + @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: String): UpdateKey = From dea7e905a977154d3a563223cfbcfafd96f551eb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 14:36:49 +0200 Subject: [PATCH 400/444] Document update key --- .../main/kotlin/sop/operation/UpdateKey.kt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index f0a7c52..9a31310 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -19,25 +19,61 @@ interface UpdateKey { */ fun noArmor(): UpdateKey + /** + * Allow key to be used for signing only. + * If this option is not present, the operation may add a new, encryption-capable component key. + */ @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey + /** + * Do not allow adding new capabilities to the key. + * If this option is not present, the operation may add support for new capabilities to the key. + */ @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey + /** + * Provide a passphrase for unlocking the secret key. + * + * @param password password + */ @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) + /** + * Provide a passphrase for unlocking the secret key. + * + * @param password password + */ @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) fun withKeyPassword(password: ByteArray): UpdateKey + /** + * Provide certificates that might contain updated signatures or third-party certifications. + * These certificates will be merged into the key. + * + * @param certs input stream of certificates + */ @Throws( SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: InputStream): UpdateKey + /** + * Provide certificates that might contain updated signatures or third-party certifications. + * These certificates will be merged into the key. + * + * @param certs binary certificates + */ @Throws( SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) + /** + * Provide the OpenPGP key to update. + * + * @param key input stream containing the key + * @return handle to acquire the updated OpenPGP key from + */ @Throws( SOPGPException.BadData::class, IOException::class, @@ -45,6 +81,12 @@ interface UpdateKey { SOPGPException.PrimaryKeyBad::class) fun key(key: InputStream): Ready + /** + * Provide the OpenPGP key to update. + * + * @param key binary OpenPGP key + * @return handle to acquire the updated OpenPGP key from + */ @Throws( SOPGPException.BadData::class, IOException::class, From 091b5f9a5e19e1605e2981f440958d7f4952ef65 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 10 Apr 2025 15:26:00 +0200 Subject: [PATCH 401/444] Add test for certifying with revoked key --- .../operation/CertifyValidateUserIdTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java index 488db9a..e05923c 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -119,4 +119,35 @@ public class CertifyValidateUserIdTest { .subjects(bobWithPetName), "Bob does not accept the pet-name Alice gave him"); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void certifyWithRevokedKey(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .userId("Alice ") + .generate() + .getBytes(); + byte[] aliceRevokedCert = sop.revokeKey() + .keys(aliceKey) + .getBytes(); + byte[] aliceRevokedKey = sop.updateKey() + .mergeCerts(aliceRevokedCert) + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .userId("Bob ") + .generate() + .getBytes(); + byte[] bobCert = sop.extractCert() + .key(bobKey) + .getBytes(); + + assertThrows(SOPGPException.KeyCannotCertify.class, () -> + sop.certifyUserId() + .userId("Bob ") + .keys(aliceRevokedKey) + .certs(bobCert) + .getBytes()); + } } From 138e275bb6ca4241343d3bc50bec6ec55704d030 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 14 Apr 2025 10:58:58 +0200 Subject: [PATCH 402/444] Fix formatting issues --- .../kotlin/sop/external/operation/UpdateKeyExternal.kt | 4 +++- sop-java/src/main/kotlin/sop/operation/UpdateKey.kt | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt index 99df15e..b84f452 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/UpdateKeyExternal.kt @@ -21,7 +21,9 @@ class UpdateKeyExternal(binary: String, environment: Properties) : UpdateKey { override fun signingOnly(): UpdateKey = apply { commandList.add("--signing-only") } - override fun noAddedCapabilities(): UpdateKey = apply { commandList.add("--no-added-capabilities") } + override fun noAddedCapabilities(): UpdateKey = apply { + commandList.add("--no-added-capabilities") + } override fun withKeyPassword(password: ByteArray): UpdateKey = apply { commandList.add("--with-key-password=@ENV:KEY_PASSWORD_$argCount") diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 9a31310..1226ed5 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -20,14 +20,14 @@ interface UpdateKey { fun noArmor(): UpdateKey /** - * Allow key to be used for signing only. - * If this option is not present, the operation may add a new, encryption-capable component key. + * Allow key to be used for signing only. If this option is not present, the operation may add a + * new, encryption-capable component key. */ @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey /** - * Do not allow adding new capabilities to the key. - * If this option is not present, the operation may add support for new capabilities to the key. + * Do not allow adding new capabilities to the key. If this option is not present, the operation + * may add support for new capabilities to the key. */ @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey From be460fabab162a17dde5c4ad131cc1d8f17c12ca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 6 May 2025 12:18:05 +0200 Subject: [PATCH 403/444] Add test for certifying without ASCII armor --- .../operation/CertifyValidateUserIdTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java index e05923c..c97fda7 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -73,6 +73,45 @@ public class CertifyValidateUserIdTest { "Alice accepts Bobs user-id after she certified it"); } + @ParameterizedTest + @MethodSource("provideInstances") + public void certifyUserIdUnarmored(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .noArmor() + .withKeyPassword("sw0rdf1sh") + .userId("Alice ") + .generate() + .getBytes(); + byte[] aliceCert = sop.extractCert() + .noArmor() + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .noArmor() + .userId("Bob ") + .generate() + .getBytes(); + byte[] bobCert = sop.extractCert() + .noArmor() + .key(bobKey) + .getBytes(); + + byte[] bobCertifiedByAlice = sop.certifyUserId() + .noArmor() + .userId("Bob ") + .withKeyPassword("sw0rdf1sh") + .keys(aliceKey) + .certs(bobCert) + .getBytes(); + + assertTrue(sop.validateUserId() + .userId("Bob ") + .authorities(aliceCert) + .subjects(bobCertifiedByAlice), + "Alice accepts Bobs user-id after she certified it"); + } + @ParameterizedTest @MethodSource("provideInstances") public void addPetName(SOP sop) throws IOException { From 38c5a947dd5179b3b77b38bab8d0ae425e61fb9a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 13 May 2025 15:16:30 +0200 Subject: [PATCH 404/444] Add MergeCertsTest --- .../testsuite/operation/MergeCertsTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java new file mode 100644 index 0000000..04a083e --- /dev/null +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.operation; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import sop.SOP; +import sop.util.HexUtil; + +import java.io.IOException; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public class MergeCertsTest extends AbstractSOPTest { + + static Stream provideInstances() { + return provideBackends(); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testMergeWithItself(SOP sop) throws IOException { + byte[] key = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] cert = sop.extractCert() + .noArmor() + .key(key) + .getBytes(); + + byte[] merged = sop.mergeCerts() + .noArmor() + .updates(cert) + .baseCertificates(cert) + .getBytes(); + + System.out.println("cert"); + System.out.println(new String(sop.armor().data(cert).getBytes())); + System.out.println("merged"); + System.out.println(new String(sop.armor().data(merged).getBytes())); + assertArrayEquals(cert, merged); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testApplyBaseToUpdate(SOP sop) throws IOException { + byte[] key = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] cert = sop.extractCert() + .noArmor() + .key(key) + .getBytes(); + + byte[] update = sop.revokeKey() + .noArmor() + .keys(key) + .getBytes(); + + byte[] merged = sop.mergeCerts() + .noArmor() + .updates(cert) + .baseCertificates(update) + .getBytes(); + + assertArrayEquals(update, merged); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testApplyUpdateToBase(SOP sop) throws IOException { + byte[] key = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] cert = sop.extractCert() + .noArmor() + .key(key) + .getBytes(); + + byte[] update = sop.revokeKey() + .noArmor() + .keys(key) + .getBytes(); + + byte[] merged = sop.mergeCerts() + .noArmor() + .updates(update) + .baseCertificates(cert) + .getBytes(); + + assertArrayEquals(update, merged); + } +} From 9360b0e8ce321edc6d274c6473b1b79622033f47 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 13 May 2025 15:36:36 +0200 Subject: [PATCH 405/444] Remove println statements --- .../src/main/java/sop/testsuite/operation/MergeCertsTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java index 04a083e..09387e9 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -41,10 +41,6 @@ public class MergeCertsTest extends AbstractSOPTest { .baseCertificates(cert) .getBytes(); - System.out.println("cert"); - System.out.println(new String(sop.armor().data(cert).getBytes())); - System.out.println("merged"); - System.out.println(new String(sop.armor().data(merged).getBytes())); assertArrayEquals(cert, merged); } From 6d23d3771d310e538e7a49b1ba92a46279c619c9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 13 May 2025 15:37:48 +0200 Subject: [PATCH 406/444] Remove unused import --- .../src/main/java/sop/testsuite/operation/MergeCertsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java index 09387e9..3d40a99 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import sop.SOP; -import sop.util.HexUtil; import java.io.IOException; import java.util.stream.Stream; From 77106942d1d4059b5b772aebbafa322604aa5f16 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 15 May 2025 00:36:16 +0200 Subject: [PATCH 407/444] Add tests for MergeCerts command --- .../testsuite/operation/MergeCertsTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java index 3d40a99..7bc99d1 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -4,6 +4,7 @@ package sop.testsuite.operation; +import kotlin.collections.ArraysKt; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -43,6 +44,52 @@ public class MergeCertsTest extends AbstractSOPTest { assertArrayEquals(cert, merged); } + @ParameterizedTest + @MethodSource("provideInstances") + public void testMergeWithItselfArmored(SOP sop) throws IOException { + byte[] key = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] cert = sop.extractCert() + .key(key) + .getBytes(); + + byte[] merged = sop.mergeCerts() + .updates(cert) + .baseCertificates(cert) + .getBytes(); + + assertArrayEquals(cert, merged); + } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testMergeWithItselfViaBase(SOP sop) throws IOException { + byte[] key = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] cert = sop.extractCert() + .noArmor() + .key(key) + .getBytes(); + + byte[] certs = ArraysKt.plus(cert, cert); + + byte[] merged = sop.mergeCerts() + .noArmor() + .updates(cert) + .baseCertificates(certs) + .getBytes(); + + assertArrayEquals(cert, merged); + } + @ParameterizedTest @MethodSource("provideInstances") public void testApplyBaseToUpdate(SOP sop) throws IOException { @@ -98,4 +145,39 @@ public class MergeCertsTest extends AbstractSOPTest { assertArrayEquals(update, merged); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void testApplyUpdateToMissingBaseDoesNothing(SOP sop) throws IOException { + byte[] aliceKey = sop.generateKey() + .noArmor() + .userId("Alice ") + .generate() + .getBytes(); + + byte[] aliceCert = sop.extractCert() + .noArmor() + .key(aliceKey) + .getBytes(); + + byte[] bobKey = sop.generateKey() + .noArmor() + .userId("Bob ") + .generate() + .getBytes(); + + byte[] bobCert = sop.extractCert() + .noArmor() + .key(bobKey) + .getBytes(); + + byte[] merged = sop.mergeCerts() + .noArmor() + .updates(bobCert) + .baseCertificates(aliceCert) + .getBytes(); + + assertArrayEquals(aliceCert, merged); + } + } From 4ef5444e782cbfba98014311ed97425b83f29764 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 15 May 2025 01:09:36 +0200 Subject: [PATCH 408/444] Test key generation with supported profiles --- .../testsuite/operation/GenerateKeyTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java index 4a5da58..b63b4b8 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import sop.Profile; import sop.SOP; import sop.exception.SOPGPException; import sop.testsuite.JUtils; @@ -16,9 +17,11 @@ import sop.testsuite.TestData; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assumptions.assumeTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") public class GenerateKeyTest extends AbstractSOPTest { @@ -118,4 +121,30 @@ public class GenerateKeyTest extends AbstractSOPTest { sop.encrypt().withCert(signingOnlyCert) .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void generateKeyWithSupportedProfiles(SOP sop) throws IOException { + List profiles = sop.listProfiles() + .generateKey(); + + for (Profile profile : profiles) { + generateKeyWithProfile(sop, profile.getName()); + } + } + + private void generateKeyWithProfile(SOP sop, String profile) throws IOException { + byte[] key; + try { + key = sop.generateKey() + .profile(profile) + .userId("Alice ") + .generate() + .getBytes(); + } catch (SOPGPException.UnsupportedProfile e) { + key = null; + } + assumeTrue(key != null, "'generate-key' does not support profile '" + profile + "'."); + JUtils.assertArrayStartsWith(key, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); + } } From e5cb58468b22cd8945fd8151d74bb4cf00cb922e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 27 May 2025 18:11:54 +0200 Subject: [PATCH 409/444] Add aliases to Profile --- sop-java/src/main/kotlin/sop/Profile.kt | 44 ++++++++++++++++----- sop-java/src/test/java/sop/ProfileTest.java | 21 ++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/Profile.kt b/sop-java/src/main/kotlin/sop/Profile.kt index 2125c57..2208592 100644 --- a/sop-java/src/main/kotlin/sop/Profile.kt +++ b/sop-java/src/main/kotlin/sop/Profile.kt @@ -20,11 +20,15 @@ import sop.util.UTF8Util * in the IETF namespace that begins with the string `draft-` should have semantics that hew as * closely as possible to the referenced Internet Draft. * @param description a free-form description of the profile. - * @see - * SOP Spec - Profile + * @param aliases list of optional profile alias names + * @see + * [SOP Spec - Profile](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html#name-profile) */ -data class Profile(val name: String, val description: Optional) { +data class Profile( + val name: String, + val description: Optional, + val aliases: List = listOf() +) { @JvmOverloads constructor( @@ -50,8 +54,18 @@ data class Profile(val name: String, val description: Optional) { * * @return string */ - override fun toString(): String = - if (description.isEmpty) name else "$name: ${description.get()}" + override fun toString(): String = buildString { + append(name) + if (!description.isEmpty || !aliases.isEmpty()) { + append(":") + } + if (!description.isEmpty) { + append(" ${description.get()}") + } + if (!aliases.isEmpty()) { + append(" (aliases: ${aliases.joinToString(separator = ", ")})") + } + } companion object { @@ -64,9 +78,21 @@ data class Profile(val name: String, val description: Optional) { @JvmStatic fun parse(string: String): Profile { return if (string.contains(": ")) { - Profile( - string.substring(0, string.indexOf(": ")), - string.substring(string.indexOf(": ") + 2).trim()) + val name = string.substring(0, string.indexOf(": ")) + var description = string.substring(string.indexOf(": ") + 2).trim() + if (description.contains("(aliases: ")) { + val aliases = + description.substring( + description.indexOf("(aliases: ") + 10, description.indexOf(")")) + description = description.substring(0, description.indexOf("(aliases: ")).trim() + Profile(name, Optional.of(description), aliases.split(", ").toList()) + } else { + if (description.isNotBlank()) { + Profile(name, Optional.of(description)) + } else { + Profile(name) + } + } } else if (string.endsWith(":")) { Profile(string.substring(0, string.length - 1)) } else { diff --git a/sop-java/src/test/java/sop/ProfileTest.java b/sop-java/src/test/java/sop/ProfileTest.java index 564a6af..770b88b 100644 --- a/sop-java/src/test/java/sop/ProfileTest.java +++ b/sop-java/src/test/java/sop/ProfileTest.java @@ -5,6 +5,9 @@ package sop; import org.junit.jupiter.api.Test; +import sop.util.Optional; + +import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -19,6 +22,24 @@ public class ProfileTest { assertEquals("default: Use the implementers recommendations.", profile.toString()); } + @Test + public void withAliasesToString() { + Profile profile = new Profile( + "Foo", + Optional.of("Something something"), + Arrays.asList("Bar", "Baz")); + assertEquals("Foo: Something something (aliases: Bar, Baz)", profile.toString()); + } + + + @Test + public void parseWithAliases() { + Profile profile = Profile.parse("Foo: Something something (aliases: Bar, Baz)"); + assertEquals("Foo", profile.getName()); + assertEquals("Something something", profile.getDescription().get()); + assertEquals(Arrays.asList("Bar", "Baz"), profile.getAliases()); + } + @Test public void toStringNameOnly() { Profile profile = new Profile("default"); From 9677f1fd0b3a12332c08c3f03a3458f5ea5e14be Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 27 May 2025 18:34:04 +0200 Subject: [PATCH 410/444] Fix profile constructors --- sop-java/src/main/kotlin/sop/Profile.kt | 9 +++++---- sop-java/src/test/java/sop/ProfileTest.java | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/Profile.kt b/sop-java/src/main/kotlin/sop/Profile.kt index 2208592..725bbe5 100644 --- a/sop-java/src/main/kotlin/sop/Profile.kt +++ b/sop-java/src/main/kotlin/sop/Profile.kt @@ -33,8 +33,9 @@ data class Profile( @JvmOverloads constructor( name: String, - description: String? = null - ) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null })) + description: String? = null, + aliases: List = listOf() + ) : this(name, Optional.ofNullable(description?.trim()?.ifBlank { null }), aliases) init { require(name.trim().isNotBlank()) { "Name cannot be empty." } @@ -85,10 +86,10 @@ data class Profile( description.substring( description.indexOf("(aliases: ") + 10, description.indexOf(")")) description = description.substring(0, description.indexOf("(aliases: ")).trim() - Profile(name, Optional.of(description), aliases.split(", ").toList()) + Profile(name, description, aliases.split(", ").toList()) } else { if (description.isNotBlank()) { - Profile(name, Optional.of(description)) + Profile(name, description) } else { Profile(name) } diff --git a/sop-java/src/test/java/sop/ProfileTest.java b/sop-java/src/test/java/sop/ProfileTest.java index 770b88b..41b4a2f 100644 --- a/sop-java/src/test/java/sop/ProfileTest.java +++ b/sop-java/src/test/java/sop/ProfileTest.java @@ -31,7 +31,6 @@ public class ProfileTest { assertEquals("Foo: Something something (aliases: Bar, Baz)", profile.toString()); } - @Test public void parseWithAliases() { Profile profile = Profile.parse("Foo: Something something (aliases: Bar, Baz)"); From e4817174217697bcaf33d227c76f36600d13be6c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 30 May 2025 12:48:24 +0200 Subject: [PATCH 411/444] Add Profile.withAliases() utility function --- sop-java/src/main/kotlin/sop/Profile.kt | 10 ++++++++++ sop-java/src/test/java/sop/ProfileTest.java | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/Profile.kt b/sop-java/src/main/kotlin/sop/Profile.kt index 725bbe5..fd58c63 100644 --- a/sop-java/src/main/kotlin/sop/Profile.kt +++ b/sop-java/src/main/kotlin/sop/Profile.kt @@ -50,6 +50,16 @@ data class Profile( fun hasDescription() = description.isPresent + /** + * Return a copy of this [Profile] with the aliases set to the given strings. + * + * @param alias one or more alias names + * @return profile with aliases + */ + fun withAliases(vararg alias: String): Profile { + return Profile(name, description, alias.toList()) + } + /** * Convert the profile into a String for displaying. * diff --git a/sop-java/src/test/java/sop/ProfileTest.java b/sop-java/src/test/java/sop/ProfileTest.java index 41b4a2f..f418672 100644 --- a/sop-java/src/test/java/sop/ProfileTest.java +++ b/sop-java/src/test/java/sop/ProfileTest.java @@ -39,6 +39,20 @@ public class ProfileTest { assertEquals(Arrays.asList("Bar", "Baz"), profile.getAliases()); } + @Test + public void changeAliasesWithWithAliases() { + Profile p = new Profile("Foo", "Bar any Baz", Arrays.asList("tinitus", "particle")); + p = p.withAliases("fnord", "qbit"); + + assertEquals("Foo", p.getName()); + assertEquals("Bar any Baz", p.getDescription().get()); + + assertTrue(p.getAliases().contains("fnord")); + assertTrue(p.getAliases().contains("qbit")); + assertFalse(p.getAliases().contains("tinitus")); + assertFalse(p.getAliases().contains("particle")); + } + @Test public void toStringNameOnly() { Profile profile = new Profile("default"); From c5d9e57f69073fb5c320d7aff3c6f68c3d99b1fa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 30 May 2025 15:05:44 +0200 Subject: [PATCH 412/444] Add test for encrypt-decrypt using all available generate-key profiles --- .../operation/EncryptDecryptTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java index df824ca..330ca90 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -11,12 +11,15 @@ import org.junit.jupiter.params.provider.MethodSource; import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.EncryptionResult; +import sop.Profile; import sop.SOP; import sop.SessionKey; import sop.Verification; import sop.enums.EncryptAs; import sop.enums.SignatureMode; import sop.exception.SOPGPException; +import sop.operation.Decrypt; +import sop.operation.Encrypt; import sop.testsuite.TestData; import sop.testsuite.assertions.VerificationListAssert; import sop.util.Optional; @@ -25,6 +28,7 @@ import sop.util.UTCUtil; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.stream.Stream; @@ -338,4 +342,55 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes()); } + + @ParameterizedTest + @MethodSource("provideInstances") + public void encryptDecryptWithAllSupportedKeyGenerationProfiles(SOP sop) throws IOException { + List profiles = sop.listProfiles().generateKey(); + + List keys = new ArrayList<>(); + List certs = new ArrayList<>(); + for (Profile p : profiles) { + byte[] k = sop.generateKey() + .profile(p) + .userId(p.getName()) + .generate() + .getBytes(); + keys.add(k); + + byte[] c = sop.extractCert() + .key(k) + .getBytes(); + certs.add(c); + } + + byte[] plaintext = "Hello, World!\n".getBytes(); + + Encrypt encrypt = sop.encrypt(); + for (byte[] c : certs) { + encrypt.withCert(c); + } + for (byte[] k : keys) { + encrypt.signWith(k); + } + + ByteArrayAndResult encRes = encrypt.plaintext(plaintext) + .toByteArrayAndResult(); + EncryptionResult eResult = encRes.getResult(); + byte[] ciphertext = encRes.getBytes(); + + for (byte[] k : keys) { + Decrypt decrypt = sop.decrypt() + .withKey(k); + for (byte[] c : certs) { + decrypt.verifyWithCert(c); + } + ByteArrayAndResult decRes = decrypt.ciphertext(ciphertext) + .toByteArrayAndResult(); + DecryptionResult dResult = decRes.getResult(); + byte[] decPlaintext = decRes.getBytes(); + assertArrayEquals(plaintext, decPlaintext); + assertEquals(certs.size(), dResult.getVerifications().size()); + } + } } From 00a02686c8e44e748faf96ca7d58abbb0c153f6c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:21:07 +0200 Subject: [PATCH 413/444] External-SOP: Fix command names --- .../main/kotlin/sop/external/operation/CertifyUserIdExternal.kt | 2 +- .../main/kotlin/sop/external/operation/MergeCertsExternal.kt | 2 +- .../kotlin/sop/external/operation/ValidateUserIdExternal.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt index abf4d50..c6c08cd 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt @@ -12,7 +12,7 @@ import sop.operation.CertifyUserId class CertifyUserIdExternal(binary: String, environment: Properties) : CertifyUserId { - private val commandList = mutableListOf(binary, "version") + private val commandList = mutableListOf(binary, "certify-userid") private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() private var argCount = 0 diff --git a/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt index 0869fab..b739eb3 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/MergeCertsExternal.kt @@ -12,7 +12,7 @@ import sop.operation.MergeCerts class MergeCertsExternal(binary: String, environment: Properties) : MergeCerts { - private val commandList = mutableListOf(binary, "version") + private val commandList = mutableListOf(binary, "merge-certs") private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() private var argCount = 0 diff --git a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt index 581d6f5..cf4742b 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/ValidateUserIdExternal.kt @@ -12,7 +12,7 @@ import sop.util.UTCUtil class ValidateUserIdExternal(binary: String, environment: Properties) : ValidateUserId { - private val commandList = mutableListOf(binary, "version") + private val commandList = mutableListOf(binary, "validate-userid") private val envList = ExternalSOP.propertiesToEnv(environment).toMutableList() private var argCount = 0 From 0df80470c682fe939727836463892ef98606891f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:21:31 +0200 Subject: [PATCH 414/444] External-SOP: Extend test suite with new test classes --- .../ExternalCertifyValidateUserIdTest.java | 13 +++++++++++++ .../operation/ExternalChangeKeyPasswordTest.java | 13 +++++++++++++ .../external/operation/ExternalMergeCertsTest.java | 13 +++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java create mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java new file mode 100644 index 0000000..bb319ca --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import sop.testsuite.operation.CertifyValidateUserIdTest; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class ExternalCertifyValidateUserIdTest extends CertifyValidateUserIdTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java new file mode 100644 index 0000000..42a9693 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import sop.testsuite.operation.ChangeKeyPasswordTest; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class ExternalChangeKeyPasswordTest extends ChangeKeyPasswordTest { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java new file mode 100644 index 0000000..8b22b37 --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.testsuite.external.operation; + +import org.junit.jupiter.api.condition.EnabledIf; +import sop.testsuite.operation.MergeCertsTest; + +@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") +public class ExternalMergeCertsTest extends MergeCertsTest { + +} From 47a6db8702d382ca2411393e456879deba27162e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:53:24 +0200 Subject: [PATCH 415/444] External-SOP: Fix error message typo --- external-sop/src/main/kotlin/sop/external/ExternalSOP.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index 8ab7737..1291433 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -182,7 +182,7 @@ class ExternalSOP( "External SOP backend reported error NoHardwareKeyFound ($exitCode):\n$errorMessage") HardwareKeyFailure.EXIT_CODE -> throw HardwareKeyFailure( - "External SOP backend reported error HardwareKeyFalure ($exitCode):\n$errorMessage") + "External SOP backend reported error HardwareKeyFailure ($exitCode):\n$errorMessage") PrimaryKeyBad.EXIT_CODE -> throw PrimaryKeyBad( "External SOP backend reported error PrimaryKeyBad ($exitCode):\n$errorMessage") From 589884672a37a3ba7b444bd1d539b73fb55bd6dd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:53:46 +0200 Subject: [PATCH 416/444] External-SOP: Properly map KeyCannotCertify error code --- external-sop/src/main/kotlin/sop/external/ExternalSOP.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index 1291433..b43b786 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -189,6 +189,9 @@ class ExternalSOP( CertUserIdNoMatch.EXIT_CODE -> throw CertUserIdNoMatch( "External SOP backend reported error CertUserIdNoMatch ($exitCode):\n$errorMessage") + KeyCannotCertify.EXIT_CODE -> + throw KeyCannotCertify( + "External SOP backend reported error KeyCannotCertify ($exitCode):\n$errorMessage") // Did you forget to add a case for a new exception type? else -> From 61206dde5310a1cec33a2f8698885f32ccc46b97 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:54:41 +0200 Subject: [PATCH 417/444] GenerateKeyTest: Provoke exception for CertCannotEncrypt test case --- .../main/java/sop/testsuite/operation/GenerateKeyTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java index b63b4b8..0d8b78e 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java @@ -119,7 +119,9 @@ public class GenerateKeyTest extends AbstractSOPTest { assertThrows(SOPGPException.CertCannotEncrypt.class, () -> sop.encrypt().withCert(signingOnlyCert) - .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))); + .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) + .toByteArrayAndResult() + .getBytes()); } @ParameterizedTest From e1d048225b04c272632e076224325367c769d74d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 12:57:11 +0200 Subject: [PATCH 418/444] CertifyValidateUserIdTest: unbound User-IDs do throw exceptions --- .../operation/CertifyValidateUserIdTest.java | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java index c97fda7..7f9f088 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -14,7 +14,6 @@ import sop.exception.SOPGPException; import java.io.IOException; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -47,16 +46,17 @@ public class CertifyValidateUserIdTest { // Alice has her own user-id self-certified assertTrue(sop.validateUserId() - .authorities(aliceCert) - .userId("Alice ") - .subjects(aliceCert), + .authorities(aliceCert) + .userId("Alice ") + .subjects(aliceCert), "Alice accepts her own self-certified user-id"); // Alice has not yet certified Bobs user-id - assertFalse(sop.validateUserId() - .authorities(aliceCert) - .userId("Bob ") - .subjects(bobCert), + assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> + sop.validateUserId() + .authorities(aliceCert) + .userId("Bob ") + .subjects(bobCert), "Alice has not yet certified Bobs user-id"); byte[] bobCertifiedByAlice = sop.certifyUserId() @@ -67,10 +67,10 @@ public class CertifyValidateUserIdTest { .getBytes(); assertTrue(sop.validateUserId() - .userId("Bob ") - .authorities(aliceCert) - .subjects(bobCertifiedByAlice), - "Alice accepts Bobs user-id after she certified it"); + .userId("Bob ") + .authorities(aliceCert) + .subjects(bobCertifiedByAlice), + "Alice accepts Bobs user-id after she certified it"); } @ParameterizedTest @@ -132,11 +132,11 @@ public class CertifyValidateUserIdTest { .getBytes(); assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> - sop.certifyUserId() - .userId("Bobby") - .keys(aliceKey) - .certs(bobCert) - .getBytes(), + sop.certifyUserId() + .userId("Bobby") + .keys(aliceKey) + .certs(bobCert) + .getBytes(), "Alice cannot create a pet-name for Bob without the --no-require-self-sig flag"); byte[] bobWithPetName = sop.certifyUserId() @@ -147,15 +147,16 @@ public class CertifyValidateUserIdTest { .getBytes(); assertTrue(sop.validateUserId() - .userId("Bobby") - .authorities(aliceCert) - .subjects(bobWithPetName), + .userId("Bobby") + .authorities(aliceCert) + .subjects(bobWithPetName), "Alice accepts the pet-name she gave to Bob"); - assertFalse(sop.validateUserId() - .userId("Bobby") - .authorities(bobWithPetName) - .subjects(bobWithPetName), + assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> + sop.validateUserId() + .userId("Bobby") + .authorities(bobWithPetName) + .subjects(bobWithPetName), "Bob does not accept the pet-name Alice gave him"); } From ab13cc1de16a44daa2fde0ad6dde33ac8351d5d7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 13:39:31 +0200 Subject: [PATCH 419/444] CertifyUserIdExternal: add separator before passing keys --- .../main/kotlin/sop/external/operation/CertifyUserIdExternal.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt index c6c08cd..e3661db 100644 --- a/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt +++ b/external-sop/src/main/kotlin/sop/external/operation/CertifyUserIdExternal.kt @@ -44,5 +44,5 @@ class CertifyUserIdExternal(binary: String, environment: Properties) : CertifyUs override fun certs(certs: InputStream): Ready = ExternalSOP.executeTransformingOperation( - Runtime.getRuntime(), commandList.plus(keys), envList, certs) + Runtime.getRuntime(), commandList.plus("--").plus(keys), envList, certs) } From 28d06c330d201815b2662f2b6238f647337e4a97 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 13:47:20 +0200 Subject: [PATCH 420/444] ExternalSOP: Map UnspecificError --- external-sop/src/main/kotlin/sop/external/ExternalSOP.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt index b43b786..48a5af9 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOP.kt @@ -120,6 +120,9 @@ class ExternalSOP( val errorMessage = readString(errIn) when (exitCode) { + UnspecificFailure.EXIT_CODE -> + throw UnspecificFailure( + "External SOP backend reported an unspecific error ($exitCode):\n$errorMessage") NoSignature.EXIT_CODE -> throw NoSignature( "External SOP backend reported error NoSignature ($exitCode):\n$errorMessage") From 79aece6f04059983139615afde3edaf336703760 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 14:13:36 +0200 Subject: [PATCH 421/444] SOP, SOPV: Add --debug option --- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt | 2 +- sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt index 98701fc..07caa03 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -64,7 +64,7 @@ class SopCLI { @JvmField var EXECUTABLE_NAME = "sop" @JvmField - @Option(names = ["--stacktrace"], scope = ScopeType.INHERIT) + @Option(names = ["--stacktrace", "--debug"], scope = ScopeType.INHERIT) var stacktrace = false @JvmStatic diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt index 9a8b4b4..fa9683f 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt @@ -45,7 +45,7 @@ class SopVCLI { @JvmField var EXECUTABLE_NAME = "sopv" @JvmField - @CommandLine.Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) + @CommandLine.Option(names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT) var stacktrace = false @JvmStatic From 5a7a8ae901f58f0bdc6feacc70115d370834bc7f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 2 Jun 2025 14:14:22 +0200 Subject: [PATCH 422/444] MergeCertsTest: do not pass unarmored data This is done to fix external-sop tests, which rely on environment variables, which do not play nicely with binary data --- .../testsuite/operation/MergeCertsTest.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java index 7bc99d1..b577017 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -25,18 +25,15 @@ public class MergeCertsTest extends AbstractSOPTest { @MethodSource("provideInstances") public void testMergeWithItself(SOP sop) throws IOException { byte[] key = sop.generateKey() - .noArmor() .userId("Alice ") .generate() .getBytes(); byte[] cert = sop.extractCert() - .noArmor() .key(key) .getBytes(); byte[] merged = sop.mergeCerts() - .noArmor() .updates(cert) .baseCertificates(cert) .getBytes(); @@ -69,20 +66,17 @@ public class MergeCertsTest extends AbstractSOPTest { @MethodSource("provideInstances") public void testMergeWithItselfViaBase(SOP sop) throws IOException { byte[] key = sop.generateKey() - .noArmor() .userId("Alice ") .generate() .getBytes(); byte[] cert = sop.extractCert() - .noArmor() .key(key) .getBytes(); byte[] certs = ArraysKt.plus(cert, cert); byte[] merged = sop.mergeCerts() - .noArmor() .updates(cert) .baseCertificates(certs) .getBytes(); @@ -94,23 +88,19 @@ public class MergeCertsTest extends AbstractSOPTest { @MethodSource("provideInstances") public void testApplyBaseToUpdate(SOP sop) throws IOException { byte[] key = sop.generateKey() - .noArmor() .userId("Alice ") .generate() .getBytes(); byte[] cert = sop.extractCert() - .noArmor() .key(key) .getBytes(); byte[] update = sop.revokeKey() - .noArmor() .keys(key) .getBytes(); byte[] merged = sop.mergeCerts() - .noArmor() .updates(cert) .baseCertificates(update) .getBytes(); @@ -122,23 +112,19 @@ public class MergeCertsTest extends AbstractSOPTest { @MethodSource("provideInstances") public void testApplyUpdateToBase(SOP sop) throws IOException { byte[] key = sop.generateKey() - .noArmor() .userId("Alice ") .generate() .getBytes(); byte[] cert = sop.extractCert() - .noArmor() .key(key) .getBytes(); byte[] update = sop.revokeKey() - .noArmor() .keys(key) .getBytes(); byte[] merged = sop.mergeCerts() - .noArmor() .updates(update) .baseCertificates(cert) .getBytes(); @@ -150,29 +136,24 @@ public class MergeCertsTest extends AbstractSOPTest { @MethodSource("provideInstances") public void testApplyUpdateToMissingBaseDoesNothing(SOP sop) throws IOException { byte[] aliceKey = sop.generateKey() - .noArmor() .userId("Alice ") .generate() .getBytes(); byte[] aliceCert = sop.extractCert() - .noArmor() .key(aliceKey) .getBytes(); byte[] bobKey = sop.generateKey() - .noArmor() .userId("Bob ") .generate() .getBytes(); byte[] bobCert = sop.extractCert() - .noArmor() .key(bobKey) .getBytes(); byte[] merged = sop.mergeCerts() - .noArmor() .updates(bobCert) .baseCertificates(aliceCert) .getBytes(); From bff4423f93ce0897f52598cc8e6805bd6332301c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Jun 2025 14:30:54 +0200 Subject: [PATCH 423/444] Verification: Rename description to jsonOrDescription --- .../sop/testsuite/assertions/VerificationAssert.java | 4 ++-- sop-java/src/main/kotlin/sop/Verification.kt | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java index 63fd237..5267148 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java @@ -45,12 +45,12 @@ public final class VerificationAssert { } public VerificationAssert hasDescription(String description) { - assertEquals(description, verification.getDescription().get()); + assertEquals(description, verification.getJsonOrDescription().get()); return this; } public VerificationAssert hasDescriptionOrNull(String description) { - if (verification.getDescription().isEmpty()) { + if (verification.getJsonOrDescription().isEmpty()) { return this; } diff --git a/sop-java/src/main/kotlin/sop/Verification.kt b/sop-java/src/main/kotlin/sop/Verification.kt index 20401a9..65a858e 100644 --- a/sop-java/src/main/kotlin/sop/Verification.kt +++ b/sop-java/src/main/kotlin/sop/Verification.kt @@ -15,7 +15,7 @@ data class Verification( val signingKeyFingerprint: String, val signingCertFingerprint: String, val signatureMode: Optional, - val description: Optional + val jsonOrDescription: Optional ) { @JvmOverloads constructor( @@ -31,10 +31,15 @@ data class Verification( Optional.ofNullable(signatureMode), Optional.ofNullable(description?.trim())) + @Deprecated("Replaced by jsonOrDescription", + replaceWith = ReplaceWith("jsonOrDescription") + ) + val description = jsonOrDescription + override fun toString(): String = "${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" + (if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") + - (if (description.isPresent) " ${description.get()}" else "") + (if (jsonOrDescription.isPresent) " ${jsonOrDescription.get()}" else "") companion object { @JvmStatic From ebfde3542228c9e94b6aae20e99dda30fbe2c4d3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Jun 2025 14:31:07 +0200 Subject: [PATCH 424/444] Add support for JSON POJOs --- sop-java/src/main/kotlin/sop/Verification.kt | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/sop-java/src/main/kotlin/sop/Verification.kt b/sop-java/src/main/kotlin/sop/Verification.kt index 65a858e..a8db800 100644 --- a/sop-java/src/main/kotlin/sop/Verification.kt +++ b/sop-java/src/main/kotlin/sop/Verification.kt @@ -17,6 +17,7 @@ data class Verification( val signatureMode: Optional, val jsonOrDescription: Optional ) { + @JvmOverloads constructor( creationTime: Date, @@ -31,11 +32,44 @@ data class Verification( Optional.ofNullable(signatureMode), Optional.ofNullable(description?.trim())) + @JvmOverloads + constructor( + creationTime: Date, + signingKeyFingerprint: String, + signingCertFingerprint: String, + signatureMode: SignatureMode? = null, + json: JSON, + jsonSerializer: JSONSerializer + ) : this( + creationTime, + signingKeyFingerprint, + signingCertFingerprint, + Optional.ofNullable(signatureMode), + Optional.of(jsonSerializer.serialize(json))) + @Deprecated("Replaced by jsonOrDescription", replaceWith = ReplaceWith("jsonOrDescription") ) val description = jsonOrDescription + /** + * Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the result. + * This method returns `null` if parsing fails. + * + * @param parser [JSONParser] implementation + * @return successfully parsed [JSON] POJO or `null`. + */ + fun getJson(parser: JSONParser): JSON? { + return jsonOrDescription.get() + ?.let { + try { + parser.parse(it) + } catch (e: ParseException) { + null + } + } + } + override fun toString(): String = "${UTCUtil.formatUTCDate(creationTime)} $signingKeyFingerprint $signingCertFingerprint" + (if (signatureMode.isPresent) " mode:${signatureMode.get()}" else "") + @@ -78,4 +112,48 @@ data class Verification( } } } + + /** + * POJO data class representing JSON metadata. + * + * @param signers list of supplied CERTS objects that could have issued the signature, identified by + * the name given on the command line. + * @param comment a freeform UTF-8 encoded text describing the verification + * @param ext an extension object containing arbitrary, implementation-specific data + */ + data class JSON( + val signers: List, + val comment: String?, + val ext: Any?) + + /** + * Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings. + */ + fun interface JSONParser { + /** + * Parse a [JSON] POJO from the given single-line [string]. + * If the string does not represent a JSON object matching the [JSON] definition, + * this method throws a [ParseException]. + * + * @param string [String] representation of the [JSON] object. + * @return parsed [JSON] POJO + * @throws ParseException if the [string] is not a JSON string representing the [JSON] object. + */ + @Throws(ParseException::class) + fun parse(string: String): JSON + } + + /** + * Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON strings. + */ + fun interface JSONSerializer { + + /** + * Serialize the given [JSON] object into a single-line JSON string. + * + * @param json JSON POJO + * @return JSON string + */ + fun serialize(json: JSON): String + } } From cdcbae7e5f4ecd448a3cabfbd3367f99d6f694f2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Jun 2025 23:14:23 +0200 Subject: [PATCH 425/444] Add test for JSON data parsing and serializing using a dummy implementation --- .../main/kotlin/sop/cli/picocli/SopVCLI.kt | 3 +- sop-java/src/main/kotlin/sop/Verification.kt | 68 +++++--- .../test/java/sop/VerificationJSONTest.java | 163 ++++++++++++++++++ .../src/test/java/sop/VerificationTest.java | 3 + 4 files changed, 208 insertions(+), 29 deletions(-) create mode 100644 sop-java/src/test/java/sop/VerificationJSONTest.java diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt index fa9683f..311a446 100644 --- a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopVCLI.kt @@ -45,7 +45,8 @@ class SopVCLI { @JvmField var EXECUTABLE_NAME = "sopv" @JvmField - @CommandLine.Option(names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT) + @CommandLine.Option( + names = ["--stacktrace", "--debug"], scope = CommandLine.ScopeType.INHERIT) var stacktrace = false @JvmStatic diff --git a/sop-java/src/main/kotlin/sop/Verification.kt b/sop-java/src/main/kotlin/sop/Verification.kt index a8db800..982e691 100644 --- a/sop-java/src/main/kotlin/sop/Verification.kt +++ b/sop-java/src/main/kotlin/sop/Verification.kt @@ -10,6 +10,15 @@ import sop.enums.SignatureMode import sop.util.Optional import sop.util.UTCUtil +/** + * Metadata about a verified signature. + * + * @param creationTime creation time of the signature + * @param signingKeyFingerprint fingerprint of the (sub-)key that issued the signature + * @param signingCertFingerprint fingerprint of the certificate that contains the signing key + * @param signatureMode optional signature mode (text/binary) + * @param jsonOrDescription arbitrary text or JSON data + */ data class Verification( val creationTime: Date, val signingKeyFingerprint: String, @@ -47,27 +56,28 @@ data class Verification( Optional.ofNullable(signatureMode), Optional.of(jsonSerializer.serialize(json))) - @Deprecated("Replaced by jsonOrDescription", - replaceWith = ReplaceWith("jsonOrDescription") - ) + @Deprecated("Replaced by jsonOrDescription", replaceWith = ReplaceWith("jsonOrDescription")) val description = jsonOrDescription + /** This value is `true` if the [Verification] contains extension JSON. */ + val containsJson: Boolean = + jsonOrDescription.get()?.trim()?.let { it.startsWith("{") && it.endsWith("}") } ?: false + /** - * Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the result. - * This method returns `null` if parsing fails. + * Attempt to parse the [jsonOrDescription] field using the provided [JSONParser] and return the + * result. This method returns `null` if parsing fails. * * @param parser [JSONParser] implementation * @return successfully parsed [JSON] POJO or `null`. */ fun getJson(parser: JSONParser): JSON? { - return jsonOrDescription.get() - ?.let { - try { - parser.parse(it) - } catch (e: ParseException) { - null - } + return jsonOrDescription.get()?.let { + try { + parser.parse(it) + } catch (e: ParseException) { + null } + } } override fun toString(): String = @@ -116,35 +126,37 @@ data class Verification( /** * POJO data class representing JSON metadata. * - * @param signers list of supplied CERTS objects that could have issued the signature, identified by - * the name given on the command line. + * @param signers list of supplied CERTS objects that could have issued the signature, + * identified by the name given on the command line. * @param comment a freeform UTF-8 encoded text describing the verification * @param ext an extension object containing arbitrary, implementation-specific data */ - data class JSON( - val signers: List, - val comment: String?, - val ext: Any?) + data class JSON(val signers: List, val comment: String?, val ext: Any?) { - /** - * Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings. - */ + /** Create a JSON object with only a list of signers. */ + constructor(signers: List) : this(signers, null, null) + + /** Create a JSON object with only a single signer. */ + constructor(signer: String) : this(listOf(signer)) + } + + /** Interface abstracting a JSON parser that parses [JSON] POJOs from single-line strings. */ fun interface JSONParser { /** - * Parse a [JSON] POJO from the given single-line [string]. - * If the string does not represent a JSON object matching the [JSON] definition, - * this method throws a [ParseException]. + * Parse a [JSON] POJO from the given single-line [string]. If the string does not represent + * a JSON object matching the [JSON] definition, this method throws a [ParseException]. * * @param string [String] representation of the [JSON] object. * @return parsed [JSON] POJO - * @throws ParseException if the [string] is not a JSON string representing the [JSON] object. + * @throws ParseException if the [string] is not a JSON string representing the [JSON] + * object. */ - @Throws(ParseException::class) - fun parse(string: String): JSON + @Throws(ParseException::class) fun parse(string: String): JSON } /** - * Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON strings. + * Interface abstracting a JSON serializer that converts [JSON] POJOs into single-line JSON + * strings. */ fun interface JSONSerializer { diff --git a/sop-java/src/test/java/sop/VerificationJSONTest.java b/sop-java/src/test/java/sop/VerificationJSONTest.java new file mode 100644 index 0000000..10253d8 --- /dev/null +++ b/sop-java/src/test/java/sop/VerificationJSONTest.java @@ -0,0 +1,163 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import sop.enums.SignatureMode; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VerificationJSONTest { + + // A hacky self-made "JSON parser" stand-in. + // Only used for testing, do not use in production! + private Verification.JSONParser dummyParser = new Verification.JSONParser() { + @NotNull + @Override + public Verification.JSON parse(@NotNull String string) throws ParseException { + if (!string.startsWith("{")) { + throw new ParseException("Alleged JSON String does not begin with '{'", 0); + } + if (!string.endsWith("}")) { + throw new ParseException("Alleged JSON String does not end with '}'", string.length() - 1); + } + + List signersList = new ArrayList<>(); + Matcher signersMat = Pattern.compile("\"signers\": \\[(.*?)\\]").matcher(string); + if (signersMat.find()) { + String signersCat = signersMat.group(1); + String[] split = signersCat.split(","); + for (String s : split) { + s = s.trim(); + signersList.add(s.substring(1, s.length() - 1)); + } + } + + String comment = null; + Matcher commentMat = Pattern.compile("\"comment\": \"(.*?)\"").matcher(string); + if (commentMat.find()) { + comment = commentMat.group(1); + } + + String ext = null; + Matcher extMat = Pattern.compile("\"ext\": (.*?})}").matcher(string); + if (extMat.find()) { + ext = extMat.group(1); + } + + return new Verification.JSON(signersList, comment, ext); + } + }; + + // A just as hacky "JSON Serializer" lookalike. + // Also don't use in production, for testing only! + private Verification.JSONSerializer dummySerializer = new Verification.JSONSerializer() { + @NotNull + @Override + public String serialize(@NotNull Verification.JSON json) { + if (json.getSigners().isEmpty() && json.getComment() == null && json.getExt() == null) { + return ""; + } + StringBuilder sb = new StringBuilder("{"); + boolean comma = false; + + if (!json.getSigners().isEmpty()) { + comma = true; + sb.append("\"signers\": ["); + for (Iterator iterator = json.getSigners().iterator(); iterator.hasNext(); ) { + String signer = iterator.next(); + sb.append("\"").append(signer).append("\""); + if (iterator.hasNext()) { + sb.append(", "); + } + } + sb.append("]"); + } + + if (json.getComment() != null) { + if (comma) { + sb.append(", "); + } + comma = true; + sb.append("\"comment\": \"").append(json.getComment()).append("\""); + } + + if (json.getExt() != null) { + if (comma) { + sb.append(", "); + } + comma = true; + sb.append("\"ext\": ").append(json.getExt().toString()); + } + return sb.append("}").toString(); + } + }; + + @Test + public void testSimpleSerializeParse() throws ParseException { + String signer = "alice.pub"; + Verification.JSON json = new Verification.JSON(signer); + + String string = dummySerializer.serialize(json); + assertEquals("{\"signers\": [\"alice.pub\"]}", string); + + Verification.JSON parsed = dummyParser.parse(string); + assertEquals(signer, parsed.getSigners().get(0)); + assertEquals(1, parsed.getSigners().size()); + assertNull(parsed.getComment()); + assertNull(parsed.getExt()); + } + + @Test + public void testAdvancedSerializeParse() throws ParseException { + Verification.JSON json = new Verification.JSON( + Arrays.asList("../certs/alice.pub", "/etc/pgp/certs.pgp"), + "This is a comment", + "{\"Foo\": \"Bar\"}"); + + String serialized = dummySerializer.serialize(json); + assertEquals("{\"signers\": [\"../certs/alice.pub\", \"/etc/pgp/certs.pgp\"], \"comment\": \"This is a comment\", \"ext\": {\"Foo\": \"Bar\"}}", + serialized); + + Verification.JSON parsed = dummyParser.parse(serialized); + assertEquals(json.getSigners(), parsed.getSigners()); + assertEquals(json.getComment(), parsed.getComment()); + assertEquals(json.getExt(), parsed.getExt()); + } + + @Test + public void testVerificationWithSimpleJson() { + String string = "2019-10-29T18:36:45Z EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E mode:text {\"signers\": [\"alice.pgp\"]}"; + Verification verification = Verification.fromString(string); + + assertTrue(verification.getContainsJson()); + assertEquals("EB85BB5FA33A75E15E944E63F231550C4F47E38E", verification.getSigningKeyFingerprint()); + assertEquals("EB85BB5FA33A75E15E944E63F231550C4F47E38E", verification.getSigningCertFingerprint()); + assertEquals(SignatureMode.text, verification.getSignatureMode().get()); + + Verification.JSON json = verification.getJson(dummyParser); + assertNotNull(json, "The verification string MUST contain valid extension json"); + + assertEquals(Collections.singletonList("alice.pgp"), json.getSigners()); + assertNull(json.getComment()); + assertNull(json.getExt()); + + verification = new Verification(verification.getCreationTime(), verification.getSigningKeyFingerprint(), verification.getSigningCertFingerprint(), verification.getSignatureMode().get(), json, dummySerializer); + assertEquals(string, verification.toString()); + } +} diff --git a/sop-java/src/test/java/sop/VerificationTest.java b/sop-java/src/test/java/sop/VerificationTest.java index e956435..1e10f61 100644 --- a/sop-java/src/test/java/sop/VerificationTest.java +++ b/sop-java/src/test/java/sop/VerificationTest.java @@ -13,6 +13,7 @@ import java.text.ParseException; import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; public class VerificationTest { @@ -25,6 +26,8 @@ public class VerificationTest { Verification verification = new Verification(signDate, keyFP, certFP); assertEquals("2022-11-07T15:01:24Z F9E6F53F7201C60A87064EAB0B27F2B0760A1209 4E2C78519512C2AE9A8BFE7EB3298EB2FBE5F51B", verification.toString()); + assertFalse(verification.getContainsJson()); + VerificationAssert.assertThatVerification(verification) .issuedBy(certFP) .isBySigningKey(keyFP) From 21766a1f39f5bfc56553d930047fad607f7c29dd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 3 Jun 2025 23:18:34 +0200 Subject: [PATCH 426/444] Delete ProxyOutputStream and test --- .../main/kotlin/sop/util/ProxyOutputStream.kt | 77 ------------------- .../java/sop/util/ProxyOutputStreamTest.java | 40 ---------- 2 files changed, 117 deletions(-) delete mode 100644 sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt delete mode 100644 sop-java/src/test/java/sop/util/ProxyOutputStreamTest.java diff --git a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt b/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt deleted file mode 100644 index 4ba24b8..0000000 --- a/sop-java/src/main/kotlin/sop/util/ProxyOutputStream.kt +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util - -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.OutputStream - -/** - * [OutputStream] that buffers data being written into it, until its underlying output stream is - * being replaced. At that point, first all the buffered data is being written to the underlying - * stream, followed by any successive data that may get written to the [ProxyOutputStream]. This - * class is useful if we need to provide an [OutputStream] at one point in time when the final - * target output stream is not yet known. - */ -@Deprecated("Marked for removal.") -// TODO: Remove in 11.X -class ProxyOutputStream : OutputStream() { - private val buffer = ByteArrayOutputStream() - private var swapped: OutputStream? = null - - @Synchronized - fun replaceOutputStream(underlying: OutputStream) { - this.swapped = underlying - swapped!!.write(buffer.toByteArray()) - } - - @Synchronized - @Throws(IOException::class) - override fun write(b: ByteArray) { - if (swapped == null) { - buffer.write(b) - } else { - swapped!!.write(b) - } - } - - @Synchronized - @Throws(IOException::class) - override fun write(b: ByteArray, off: Int, len: Int) { - if (swapped == null) { - buffer.write(b, off, len) - } else { - swapped!!.write(b, off, len) - } - } - - @Synchronized - @Throws(IOException::class) - override fun flush() { - buffer.flush() - if (swapped != null) { - swapped!!.flush() - } - } - - @Synchronized - @Throws(IOException::class) - override fun close() { - buffer.close() - if (swapped != null) { - swapped!!.close() - } - } - - @Synchronized - @Throws(IOException::class) - override fun write(i: Int) { - if (swapped == null) { - buffer.write(i) - } else { - swapped!!.write(i) - } - } -} diff --git a/sop-java/src/test/java/sop/util/ProxyOutputStreamTest.java b/sop-java/src/test/java/sop/util/ProxyOutputStreamTest.java deleted file mode 100644 index 9d99fd4..0000000 --- a/sop-java/src/test/java/sop/util/ProxyOutputStreamTest.java +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.junit.jupiter.api.Test; - -public class ProxyOutputStreamTest { - - @Test - public void replaceOutputStreamThrowsNPEForNull() { - ProxyOutputStream proxy = new ProxyOutputStream(); - assertThrows(NullPointerException.class, () -> proxy.replaceOutputStream(null)); - } - - @Test - public void testSwappingStreamPreservesWrittenBytes() throws IOException { - byte[] firstSection = "Foo\nBar\n".getBytes(StandardCharsets.UTF_8); - byte[] secondSection = "Baz\n".getBytes(StandardCharsets.UTF_8); - - ProxyOutputStream proxy = new ProxyOutputStream(); - proxy.write(firstSection); - - ByteArrayOutputStream swappedStream = new ByteArrayOutputStream(); - proxy.replaceOutputStream(swappedStream); - - proxy.write(secondSection); - proxy.close(); - - assertEquals("Foo\nBar\nBaz\n", swappedStream.toString()); - } -} From 8a7fd5cb58ee1689ed7be2a00f9e08a55cf09985 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:15:29 +0200 Subject: [PATCH 427/444] Move validate-userid to SOPV --- external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt | 4 ++++ sop-java/src/main/kotlin/sop/SOP.kt | 3 --- sop-java/src/main/kotlin/sop/SOPV.kt | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt b/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt index f22f947..3341055 100644 --- a/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt +++ b/external-sop/src/main/kotlin/sop/external/ExternalSOPV.kt @@ -10,9 +10,11 @@ import sop.SOPV import sop.external.ExternalSOP.TempDirProvider import sop.external.operation.DetachedVerifyExternal import sop.external.operation.InlineVerifyExternal +import sop.external.operation.ValidateUserIdExternal import sop.external.operation.VersionExternal import sop.operation.DetachedVerify import sop.operation.InlineVerify +import sop.operation.ValidateUserId import sop.operation.Version /** @@ -37,6 +39,8 @@ class ExternalSOPV( override fun inlineVerify(): InlineVerify = InlineVerifyExternal(binaryName, properties, tempDirProvider) + override fun validateUserId(): ValidateUserId = ValidateUserIdExternal(binaryName, properties) + companion object { /** diff --git a/sop-java/src/main/kotlin/sop/SOP.kt b/sop-java/src/main/kotlin/sop/SOP.kt index fbd0428..a942c56 100644 --- a/sop-java/src/main/kotlin/sop/SOP.kt +++ b/sop-java/src/main/kotlin/sop/SOP.kt @@ -68,7 +68,4 @@ interface SOP : SOPV { /** Certify OpenPGP Certificate User-IDs. */ fun certifyUserId(): CertifyUserId? - - /** Validate a UserID in an OpenPGP certificate. */ - fun validateUserId(): ValidateUserId? } diff --git a/sop-java/src/main/kotlin/sop/SOPV.kt b/sop-java/src/main/kotlin/sop/SOPV.kt index 58a7f13..27eb6e3 100644 --- a/sop-java/src/main/kotlin/sop/SOPV.kt +++ b/sop-java/src/main/kotlin/sop/SOPV.kt @@ -6,6 +6,7 @@ package sop import sop.operation.DetachedVerify import sop.operation.InlineVerify +import sop.operation.ValidateUserId import sop.operation.Version /** Subset of [SOP] implementing only OpenPGP signature verification. */ @@ -31,4 +32,7 @@ interface SOPV { * a message, use [detachedVerify] instead. */ fun inlineVerify(): InlineVerify? + + /** Validate a UserID in an OpenPGP certificate. */ + fun validateUserId(): ValidateUserId? } From 2a22cea29b52da0abc238a5860c3e251380d3dc2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:28:28 +0200 Subject: [PATCH 428/444] Remove animalsniffer --- build.gradle | 13 ------------- version.gradle | 1 - 2 files changed, 14 deletions(-) diff --git a/build.gradle b/build.gradle index 1bff704..10f2b87 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,6 @@ buildscript { } plugins { - id 'ru.vyarus.animalsniffer' version '2.0.0' id 'org.jetbrains.kotlin.jvm' version "1.9.21" id 'com.diffplug.spotless' version '6.22.0' apply false } @@ -35,18 +34,6 @@ allprojects { apply plugin: 'kotlin-kapt' apply plugin: 'com.diffplug.spotless' - // For non-cli modules enable android api compatibility check - if (it.name.equals('sop-java')) { - // animalsniffer - apply plugin: 'ru.vyarus.animalsniffer' - dependencies { - signature "net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:2.3.3_r2@signature" - } - animalsniffer { - sourceSets = [sourceSets.main] - } - } - // Only generate jar for submodules // https://stackoverflow.com/a/25445035 jar { diff --git a/version.gradle b/version.gradle index c757e1e..4d90e55 100644 --- a/version.gradle +++ b/version.gradle @@ -6,7 +6,6 @@ allprojects { ext { shortVersion = '11.0.0' isSnapshot = true - minAndroidSdk = 10 javaSourceCompatibility = 11 gsonVersion = '2.10.1' jsrVersion = '3.0.2' From ac17000ff16dc14bdf19768685fa27dbb27f52f4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:28:58 +0200 Subject: [PATCH 429/444] Clean up unused version literal --- version.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/version.gradle b/version.gradle index 4d90e55..a58af0b 100644 --- a/version.gradle +++ b/version.gradle @@ -10,7 +10,6 @@ allprojects { gsonVersion = '2.10.1' jsrVersion = '3.0.2' junitVersion = '5.8.2' - junitSysExitVersion = '1.1.2' logbackVersion = '1.2.13' // 1.4+ cause CLI spam mockitoVersion = '4.5.1' picocliVersion = '4.6.3' From e72e5a15c0c0a823072ca22f5a4fc57557a83672 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:29:25 +0200 Subject: [PATCH 430/444] Fix: Pass chars to StringBuilder.append() --- sop-java/src/test/java/sop/VerificationJSONTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sop-java/src/test/java/sop/VerificationJSONTest.java b/sop-java/src/test/java/sop/VerificationJSONTest.java index 10253d8..a80e6fc 100644 --- a/sop-java/src/test/java/sop/VerificationJSONTest.java +++ b/sop-java/src/test/java/sop/VerificationJSONTest.java @@ -81,12 +81,12 @@ public class VerificationJSONTest { sb.append("\"signers\": ["); for (Iterator iterator = json.getSigners().iterator(); iterator.hasNext(); ) { String signer = iterator.next(); - sb.append("\"").append(signer).append("\""); + sb.append('\"').append(signer).append('\"'); if (iterator.hasNext()) { sb.append(", "); } } - sb.append("]"); + sb.append(']'); } if (json.getComment() != null) { @@ -94,7 +94,7 @@ public class VerificationJSONTest { sb.append(", "); } comma = true; - sb.append("\"comment\": \"").append(json.getComment()).append("\""); + sb.append("\"comment\": \"").append(json.getComment()).append('\"'); } if (json.getExt() != null) { @@ -104,7 +104,7 @@ public class VerificationJSONTest { comma = true; sb.append("\"ext\": ").append(json.getExt().toString()); } - return sb.append("}").toString(); + return sb.append('}').toString(); } }; From 86718a26905c16af996dd5d4a15530eac4063ced Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:29:53 +0200 Subject: [PATCH 431/444] Bump logback to 1.5.13 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index a58af0b..864c585 100644 --- a/version.gradle +++ b/version.gradle @@ -10,7 +10,7 @@ allprojects { gsonVersion = '2.10.1' jsrVersion = '3.0.2' junitVersion = '5.8.2' - logbackVersion = '1.2.13' // 1.4+ cause CLI spam + logbackVersion = '1.5.13' mockitoVersion = '4.5.1' picocliVersion = '4.6.3' slf4jVersion = '1.7.36' From 01be696f75c3a48367b4e464a24238dcfa6e6ffd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 11:30:21 +0200 Subject: [PATCH 432/444] Bump version to 14.0.0-SNAPSHOT --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 864c585..cfb972a 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '11.0.0' + shortVersion = '14.0.0' isSnapshot = true javaSourceCompatibility = 11 gsonVersion = '2.10.1' From 04d154f63d5e9e797e5dec8b2a80fdfae734e7b8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 12:12:23 +0200 Subject: [PATCH 433/444] Version: Fix getSopJavaVersion() --- .../src/main/java/sop/testsuite/operation/VersionTest.java | 6 ++++++ sop-java/src/main/kotlin/sop/operation/Version.kt | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java index f836935..47644bf 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java @@ -86,4 +86,10 @@ public class VersionTest extends AbstractSOPTest { throw new TestAbortedException("Implementation does not provide coverage for any sopv interface version."); } } + + @ParameterizedTest + @MethodSource("provideInstances") + public void sopJavaVersionTest(SOP sop) { + assertNotNull(sop.version().getSopJavaVersion()); + } } diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt index a10fe7c..6c8aa95 100644 --- a/sop-java/src/main/kotlin/sop/operation/Version.kt +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -115,12 +115,12 @@ interface Version { fun getSopJavaVersion(): String? { return try { val resourceIn: InputStream = - javaClass.getResourceAsStream("/sop-java-version.properties") + Version::class.java.getResourceAsStream("/sop-java-version.properties") ?: throw IOException("File sop-java-version.properties not found.") val properties = Properties().apply { load(resourceIn) } properties.getProperty("sop-java-version") } catch (e: IOException) { - null + "DEVELOPMENT" } } } From 12835bfb8e17538f1ea87f3d8ee3e1abb77d73e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 12:12:41 +0200 Subject: [PATCH 434/444] Add/fix missing localizations for new SOP commands --- sop-java-picocli/src/main/resources/msg_update-key.properties | 2 +- .../src/main/resources/msg_update-key_de.properties | 2 +- sop-java-picocli/src/main/resources/msg_version.properties | 1 + sop-java-picocli/src/main/resources/msg_version_de.properties | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sop-java-picocli/src/main/resources/msg_update-key.properties b/sop-java-picocli/src/main/resources/msg_update-key.properties index e12fbbc..0b5243e 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key.properties @@ -4,7 +4,7 @@ usage.header=Keep a secret key up-to-date no-armor=ASCII armor the output signing-only=TODO: Document -no-new-mechanisms=Do not add feature support for new mechanisms, which the key did not previously support +no-added-capabilities=Do not add feature support for new mechanisms, which the key did not previously support with-key-password.0=Passphrase to unlock the secret key(s). with-key-password.1=Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). merge-certs.0=Merge additional elements found in the corresponding CERTS objects into the updated secret keys diff --git a/sop-java-picocli/src/main/resources/msg_update-key_de.properties b/sop-java-picocli/src/main/resources/msg_update-key_de.properties index 1b8a84d..91e5532 100644 --- a/sop-java-picocli/src/main/resources/msg_update-key_de.properties +++ b/sop-java-picocli/src/main/resources/msg_update-key_de.properties @@ -4,7 +4,7 @@ usage.header=Halte einen Schlüssel auf dem neusten Stand no-armor=Schütze Ausgabe mit ASCII Armor signing-only=TODO: Dokumentieren -no-new-mechanisms=Füge keine neuen Funktionen hinzu, die der Schlüssel nicht bereits zuvor unterstützt hat +no-added-capabilities=Füge keine neuen Funktionen hinzu, die der Schlüssel nicht bereits zuvor unterstützt hat with-key-password.0=Passwort zum Entsperren der privaten Schlüssel with-key-password.1=Ist INDIREKTER Datentyp (z.B.. Datei, Umgebungsvariable, Dateideskriptor...). merge-certs.0=Führe zusätzliche Elemente aus entsprechenden CERTS Objekten mit dem privaten Schlüssel zusammen diff --git a/sop-java-picocli/src/main/resources/msg_version.properties b/sop-java-picocli/src/main/resources/msg_version.properties index c7d0168..1327a78 100644 --- a/sop-java-picocli/src/main/resources/msg_version.properties +++ b/sop-java-picocli/src/main/resources/msg_version.properties @@ -5,6 +5,7 @@ usage.header=Display version information about the tool extended=Print an extended version string backend=Print information about the cryptographic backend sop-spec=Print the latest revision of the SOP specification targeted by the implementation +sopv=Print the SOPV API version standardOutput=version information diff --git a/sop-java-picocli/src/main/resources/msg_version_de.properties b/sop-java-picocli/src/main/resources/msg_version_de.properties index c317916..c99045c 100644 --- a/sop-java-picocli/src/main/resources/msg_version_de.properties +++ b/sop-java-picocli/src/main/resources/msg_version_de.properties @@ -5,6 +5,7 @@ usage.header=Zeige Versionsinformationen extended=Gebe erweiterte Versionsinformationen aus backend=Gebe Informationen über das kryptografische Backend aus sop-spec=Gebe die neuste Revision der SOP Spezifikation aus, welche von dieser Implementierung umgesetzt wird +sopv=Gebe die SOPV API Version aus standardOutput=Versionsinformationen From 07d0aa69416c65b53fb6cf8d9bbfddd7525e8395 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 12:24:15 +0200 Subject: [PATCH 435/444] Update CHANGELOG --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0500cb..9271f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ SPDX-License-Identifier: Apache-2.0 # Changelog +## 14.0.0-SNAPSHOT +- Implement changes from SOP spec `11`, `12`, `13`, `14` + - Implement `update-key` command + - Implement `merge-certs` command + - Implement `certify-userid` command + - Implement `validate-userid` command + - Add `UnspecificFailure` exception + - Add `KeyCannotCertify` exception + - Add `NoHardwareKeyFound` exception + - Add `HardwareKeyFailure` exception + - Add `PrimaryKeyBad` exception + - Add `CertUserIdNoMatch` exception + - `Verification`: Add support for JSON description extensions +- Remove `animalsniffer` from build dependencies +- Bump `logback` to `1.5.13` + ## 10.1.1 - Prepare jar files for use in native images, e.g. using GraalVM by generating and including configuration files for reflection, resources and dynamic proxies. From 191ec8c07d8cfe7bf3c18e887379d40baae65f82 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 13:22:16 +0200 Subject: [PATCH 436/444] Improve CHANGELOG again --- CHANGELOG.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9271f3d..3f32b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,20 @@ SPDX-License-Identifier: Apache-2.0 # Changelog ## 14.0.0-SNAPSHOT -- Implement changes from SOP spec `11`, `12`, `13`, `14` - - Implement `update-key` command - - Implement `merge-certs` command - - Implement `certify-userid` command - - Implement `validate-userid` command - - Add `UnspecificFailure` exception - - Add `KeyCannotCertify` exception - - Add `NoHardwareKeyFound` exception - - Add `HardwareKeyFailure` exception - - Add `PrimaryKeyBad` exception - - Add `CertUserIdNoMatch` exception +- Update implementation to [SOP Specification revision 14](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-14.html), + including changes from revisions `11`, `12`, `13`, `14`. + - Implement newly introduced operations + - `update-key` 'fixes' everything wrong with a key + - `merge-certs` merges a certificate with other copies + - `certify-userid` create signatures over user-ids on certificates + - `validate-userid` validate signatures over user-ids + - Add new exceptions + - `UnspecificFailure` maps generic application errors + - `KeyCannotCertify` signals that a key cannot be used for third-party certifications + - `NoHardwareKeyFound` signals that a key backed by a hardware device cannot be found + - `HardwareKeyFailure` signals a hardware device failure + - `PrimaryKeyBad` signals an unusable or bad primary key + - `CertUserIdNoMatch` signals that a user-id cannot be found/validated on a certificate - `Verification`: Add support for JSON description extensions - Remove `animalsniffer` from build dependencies - Bump `logback` to `1.5.13` From 9762f1f043546a260f901e16f30b4d0daaa243fb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 13:39:23 +0200 Subject: [PATCH 437/444] SOP-Java 14.0.0 --- CHANGELOG.md | 2 +- README.md | 2 +- version.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f32b28..0447a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # Changelog -## 14.0.0-SNAPSHOT +## 14.0.0 - Update implementation to [SOP Specification revision 14](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-14.html), including changes from revisions `11`, `12`, `13`, `14`. - Implement newly introduced operations diff --git a/README.md b/README.md index f2e207f..35324c4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: Apache-2.0 # SOP for Java [![status-badge](https://ci.codeberg.org/api/badges/PGPainless/sop-java/status.svg)](https://ci.codeberg.org/PGPainless/sop-java) -[![Spec Revision: 10](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/10/) +[![Spec Revision: 14](https://img.shields.io/badge/Spec%20Revision-10-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/14/) [![Coverage Status](https://coveralls.io/repos/github/pgpainless/sop-java/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/sop-java?branch=main) [![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/sop-java)](https://api.reuse.software/info/github.com/pgpainless/sop-java) diff --git a/version.gradle b/version.gradle index cfb972a..cb9b332 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '14.0.0' - isSnapshot = true + isSnapshot = false javaSourceCompatibility = 11 gsonVersion = '2.10.1' jsrVersion = '3.0.2' From b3223372c690aa7f8277ce226c34e6ad314d7d68 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 17 Jun 2025 13:42:42 +0200 Subject: [PATCH 438/444] SOP-Java 14.0.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index cb9b332..bac96da 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '14.0.0' - isSnapshot = false + shortVersion = '14.0.1' + isSnapshot = true javaSourceCompatibility = 11 gsonVersion = '2.10.1' jsrVersion = '3.0.2' From c651adc0b391e764161f09e0eb0869985d468058 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 1 Jul 2025 14:38:13 +0200 Subject: [PATCH 439/444] Add VerificationAssert methods, disable tests for unsupported operations --- .../assertions/VerificationAssert.java | 25 ++++++ .../testsuite/operation/AbstractSOPTest.java | 16 ++++ .../testsuite/operation/ArmorDearmorTest.java | 40 ++++----- .../operation/CertifyValidateUserIdTest.java | 58 ++++++------- .../operation/ChangeKeyPasswordTest.java | 36 ++++---- .../operation/DecryptWithSessionKeyTest.java | 4 +- .../DetachedSignDetachedVerifyTest.java | 48 +++++------ .../operation/EncryptDecryptTest.java | 83 ++++++++++--------- .../testsuite/operation/ExtractCertTest.java | 22 ++--- .../testsuite/operation/GenerateKeyTest.java | 22 ++--- ...ineSignInlineDetachDetachedVerifyTest.java | 14 ++-- .../operation/InlineSignInlineVerifyTest.java | 33 ++++---- .../testsuite/operation/ListProfilesTest.java | 9 +- .../testsuite/operation/MergeCertsTest.java | 44 +++++----- .../testsuite/operation/RevokeKeyTest.java | 46 +++++----- .../sop/testsuite/operation/VersionTest.java | 22 ++--- .../test/java/sop/VerificationJSONTest.java | 3 + 17 files changed, 286 insertions(+), 239 deletions(-) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java index 5267148..dea8717 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/assertions/VerificationAssert.java @@ -8,9 +8,13 @@ import sop.Verification; import sop.enums.SignatureMode; import sop.testsuite.JUtils; +import java.text.ParseException; import java.util.Date; +import java.util.function.Predicate; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public final class VerificationAssert { @@ -57,6 +61,27 @@ public final class VerificationAssert { return hasDescription(description); } + public VerificationAssert hasValidJSONOrNull(Verification.JSONParser parser) + throws ParseException { + if (!verification.getJsonOrDescription().isPresent()) { + // missing description + return this; + } + + return hasJSON(parser, null); + } + + public VerificationAssert hasJSON(Verification.JSONParser parser, Predicate predicate) { + assertTrue(verification.getContainsJson(), "Verification does not appear to contain JSON extension"); + + Verification.JSON json = verification.getJson(parser); + assertNotNull(verification.getJson(parser), "Verification does not appear to contain valid JSON extension."); + if (predicate != null) { + assertTrue(predicate.test(json), "JSON object does not match predicate."); + } + return this; + } + public VerificationAssert hasMode(SignatureMode mode) { assertEquals(mode, verification.getSignatureMode().get()); return this; diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java index 6c163f7..16ae256 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/AbstractSOPTest.java @@ -4,10 +4,13 @@ package sop.testsuite.operation; +import kotlin.jvm.functions.Function0; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.Arguments; import sop.SOP; +import sop.exception.SOPGPException; import sop.testsuite.AbortOnUnsupportedOption; import sop.testsuite.AbortOnUnsupportedOptionExtension; import sop.testsuite.SOPInstanceFactory; @@ -18,6 +21,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + @ExtendWith(AbortOnUnsupportedOptionExtension.class) @AbortOnUnsupportedOption public abstract class AbstractSOPTest { @@ -51,6 +56,17 @@ public abstract class AbstractSOPTest { } } + public T assumeSupported(Function0 f) { + try { + T t = f.invoke(); + assumeTrue(t != null, "Unsupported operation."); + return t; + } catch (SOPGPException.UnsupportedSubcommand e) { + assumeTrue(false, e.getMessage()); + return null; + } + } + public static Stream provideBackends() { return backends.stream(); } diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java index 35959b0..00488e1 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ArmorDearmorTest.java @@ -20,7 +20,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ArmorDearmorTest { +public class ArmorDearmorTest extends AbstractSOPTest { static Stream provideInstances() { return AbstractSOPTest.provideBackends(); @@ -31,13 +31,13 @@ public class ArmorDearmorTest { public void dearmorArmorAliceKey(SOP sop) throws IOException { byte[] aliceKey = TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(aliceKey) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -52,13 +52,13 @@ public class ArmorDearmorTest { public void dearmorArmorAliceCert(SOP sop) throws IOException { byte[] aliceCert = TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(aliceCert) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -73,13 +73,13 @@ public class ArmorDearmorTest { public void dearmorArmorBobKey(SOP sop) throws IOException { byte[] bobKey = TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(bobKey) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -94,13 +94,13 @@ public class ArmorDearmorTest { public void dearmorArmorBobCert(SOP sop) throws IOException { byte[] bobCert = TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(bobCert) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -115,13 +115,13 @@ public class ArmorDearmorTest { public void dearmorArmorCarolKey(SOP sop) throws IOException { byte[] carolKey = TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(carolKey) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -136,13 +136,13 @@ public class ArmorDearmorTest { public void dearmorArmorCarolCert(SOP sop) throws IOException { byte[] carolCert = TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(carolCert) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -163,13 +163,13 @@ public class ArmorDearmorTest { "CePQFpprprnGEzpE3flQLUc=\n" + "=ZiFR\n" + "-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(message) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_MESSAGE)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -191,13 +191,13 @@ public class ArmorDearmorTest { "=GHvQ\n" + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(signature) .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(dearmored, TestData.BEGIN_PGP_SIGNATURE)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(dearmored) .getBytes(); @@ -210,11 +210,11 @@ public class ArmorDearmorTest { @ParameterizedTest @MethodSource("provideInstances") public void testDearmoringTwiceIsIdempotent(SOP sop) throws IOException { - byte[] dearmored = sop.dearmor() + byte[] dearmored = assumeSupported(sop::dearmor) .data(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); - byte[] dearmoredAgain = sop.dearmor() + byte[] dearmoredAgain = assumeSupported(sop::dearmor) .data(dearmored) .getBytes(); @@ -233,7 +233,7 @@ public class ArmorDearmorTest { "=GHvQ\n" + "-----END PGP SIGNATURE-----\n").getBytes(StandardCharsets.UTF_8); - byte[] armoredAgain = sop.armor() + byte[] armoredAgain = assumeSupported(sop::armor) .data(armored) .getBytes(); diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java index 7f9f088..855c23d 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/CertifyValidateUserIdTest.java @@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class CertifyValidateUserIdTest { +public class CertifyValidateUserIdTest extends AbstractSOPTest { static Stream provideInstances() { return AbstractSOPTest.provideBackends(); @@ -27,25 +27,25 @@ public class CertifyValidateUserIdTest { @ParameterizedTest @MethodSource("provideInstances") public void certifyUserId(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() + byte[] aliceKey = assumeSupported(sop::generateKey) .withKeyPassword("sw0rdf1sh") .userId("Alice ") .generate() .getBytes(); - byte[] aliceCert = sop.extractCert() + byte[] aliceCert = assumeSupported(sop::extractCert) .key(aliceKey) .getBytes(); - byte[] bobKey = sop.generateKey() + byte[] bobKey = assumeSupported(sop::generateKey) .userId("Bob ") .generate() .getBytes(); - byte[] bobCert = sop.extractCert() + byte[] bobCert = assumeSupported(sop::extractCert) .key(bobKey) .getBytes(); // Alice has her own user-id self-certified - assertTrue(sop.validateUserId() + assertTrue(assumeSupported(sop::validateUserId) .authorities(aliceCert) .userId("Alice ") .subjects(aliceCert), @@ -53,20 +53,20 @@ public class CertifyValidateUserIdTest { // Alice has not yet certified Bobs user-id assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> - sop.validateUserId() + assumeSupported(sop::validateUserId) .authorities(aliceCert) .userId("Bob ") .subjects(bobCert), "Alice has not yet certified Bobs user-id"); - byte[] bobCertifiedByAlice = sop.certifyUserId() + byte[] bobCertifiedByAlice = assumeSupported(sop::certifyUserId) .userId("Bob ") .withKeyPassword("sw0rdf1sh") .keys(aliceKey) .certs(bobCert) .getBytes(); - assertTrue(sop.validateUserId() + assertTrue(assumeSupported(sop::validateUserId) .userId("Bob ") .authorities(aliceCert) .subjects(bobCertifiedByAlice), @@ -76,28 +76,28 @@ public class CertifyValidateUserIdTest { @ParameterizedTest @MethodSource("provideInstances") public void certifyUserIdUnarmored(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() + byte[] aliceKey = assumeSupported(sop::generateKey) .noArmor() .withKeyPassword("sw0rdf1sh") .userId("Alice ") .generate() .getBytes(); - byte[] aliceCert = sop.extractCert() + byte[] aliceCert = assumeSupported(sop::extractCert) .noArmor() .key(aliceKey) .getBytes(); - byte[] bobKey = sop.generateKey() + byte[] bobKey = assumeSupported(sop::generateKey) .noArmor() .userId("Bob ") .generate() .getBytes(); - byte[] bobCert = sop.extractCert() + byte[] bobCert = assumeSupported(sop::extractCert) .noArmor() .key(bobKey) .getBytes(); - byte[] bobCertifiedByAlice = sop.certifyUserId() + byte[] bobCertifiedByAlice = assumeSupported(sop::certifyUserId) .noArmor() .userId("Bob ") .withKeyPassword("sw0rdf1sh") @@ -105,7 +105,7 @@ public class CertifyValidateUserIdTest { .certs(bobCert) .getBytes(); - assertTrue(sop.validateUserId() + assertTrue(assumeSupported(sop::validateUserId) .userId("Bob ") .authorities(aliceCert) .subjects(bobCertifiedByAlice), @@ -115,45 +115,45 @@ public class CertifyValidateUserIdTest { @ParameterizedTest @MethodSource("provideInstances") public void addPetName(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() + byte[] aliceKey = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] aliceCert = sop.extractCert() + byte[] aliceCert = assumeSupported(sop::extractCert) .key(aliceKey) .getBytes(); - byte[] bobKey = sop.generateKey() + byte[] bobKey = assumeSupported(sop::generateKey) .userId("Bob ") .generate() .getBytes(); - byte[] bobCert = sop.extractCert() + byte[] bobCert = assumeSupported(sop::extractCert) .key(bobKey) .getBytes(); assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> - sop.certifyUserId() + assumeSupported(sop::certifyUserId) .userId("Bobby") .keys(aliceKey) .certs(bobCert) .getBytes(), "Alice cannot create a pet-name for Bob without the --no-require-self-sig flag"); - byte[] bobWithPetName = sop.certifyUserId() + byte[] bobWithPetName = assumeSupported(sop::certifyUserId) .userId("Bobby") .noRequireSelfSig() .keys(aliceKey) .certs(bobCert) .getBytes(); - assertTrue(sop.validateUserId() + assertTrue(assumeSupported(sop::validateUserId) .userId("Bobby") .authorities(aliceCert) .subjects(bobWithPetName), "Alice accepts the pet-name she gave to Bob"); assertThrows(SOPGPException.CertUserIdNoMatch.class, () -> - sop.validateUserId() + assumeSupported(sop::validateUserId) .userId("Bobby") .authorities(bobWithPetName) .subjects(bobWithPetName), @@ -163,28 +163,28 @@ public class CertifyValidateUserIdTest { @ParameterizedTest @MethodSource("provideInstances") public void certifyWithRevokedKey(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() + byte[] aliceKey = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] aliceRevokedCert = sop.revokeKey() + byte[] aliceRevokedCert = assumeSupported(sop::revokeKey) .keys(aliceKey) .getBytes(); - byte[] aliceRevokedKey = sop.updateKey() + byte[] aliceRevokedKey = assumeSupported(sop::updateKey) .mergeCerts(aliceRevokedCert) .key(aliceKey) .getBytes(); - byte[] bobKey = sop.generateKey() + byte[] bobKey = assumeSupported(sop::generateKey) .userId("Bob ") .generate() .getBytes(); - byte[] bobCert = sop.extractCert() + byte[] bobCert = assumeSupported(sop::extractCert) .key(bobKey) .getBytes(); assertThrows(SOPGPException.KeyCannotCertify.class, () -> - sop.certifyUserId() + assumeSupported(sop::certifyUserId) .userId("Bob ") .keys(aliceRevokedKey) .certs(bobCert) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java index 8948dda..a62cbb8 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ChangeKeyPasswordTest.java @@ -32,18 +32,18 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void changePasswordFromUnprotectedToProtected(SOP sop) throws IOException { - byte[] unprotectedKey = sop.generateKey().generate().getBytes(); + byte[] unprotectedKey = assumeSupported(sop::generateKey).generate().getBytes(); byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); - byte[] protectedKey = sop.changeKeyPassword().newKeyPassphrase(password).keys(unprotectedKey).getBytes(); + byte[] protectedKey = assumeSupported(sop::changeKeyPassword).newKeyPassphrase(password).keys(unprotectedKey).getBytes(); - sop.sign().withKeyPassword(password).key(protectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); + assumeSupported(sop::sign).withKeyPassword(password).key(protectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); } @ParameterizedTest @MethodSource("provideInstances") public void changePasswordFromUnprotectedToUnprotected(SOP sop) throws IOException { - byte[] unprotectedKey = sop.generateKey().noArmor().generate().getBytes(); - byte[] stillUnprotectedKey = sop.changeKeyPassword().noArmor().keys(unprotectedKey).getBytes(); + byte[] unprotectedKey = assumeSupported(sop::generateKey).noArmor().generate().getBytes(); + byte[] stillUnprotectedKey = assumeSupported(sop::changeKeyPassword).noArmor().keys(unprotectedKey).getBytes(); assertArrayEquals(unprotectedKey, stillUnprotectedKey); } @@ -52,12 +52,12 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { @MethodSource("provideInstances") public void changePasswordFromProtectedToUnprotected(SOP sop) throws IOException { byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); - byte[] protectedKey = sop.generateKey().withKeyPassword(password).generate().getBytes(); - byte[] unprotectedKey = sop.changeKeyPassword() + byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(password).generate().getBytes(); + byte[] unprotectedKey = assumeSupported(sop::changeKeyPassword) .oldKeyPassphrase(password) .keys(protectedKey).getBytes(); - sop.sign().key(unprotectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); + assumeSupported(sop::sign).key(unprotectedKey).data("Test123".getBytes(StandardCharsets.UTF_8)); } @ParameterizedTest @@ -65,13 +65,13 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { public void changePasswordFromProtectedToDifferentProtected(SOP sop) throws IOException { byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); - byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); - byte[] reprotectedKey = sop.changeKeyPassword() + byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes(); + byte[] reprotectedKey = assumeSupported(sop::changeKeyPassword) .oldKeyPassphrase(oldPassword) .newKeyPassphrase(newPassword) .keys(protectedKey).getBytes(); - sop.sign().key(reprotectedKey).withKeyPassword(newPassword).data("Test123".getBytes(StandardCharsets.UTF_8)); + assumeSupported(sop::sign).key(reprotectedKey).withKeyPassword(newPassword).data("Test123".getBytes(StandardCharsets.UTF_8)); } @@ -82,8 +82,8 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { byte[] newPassword = "monkey123".getBytes(UTF8Util.UTF8); byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); - byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); - assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.changeKeyPassword() + byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes(); + assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::changeKeyPassword) .oldKeyPassphrase(wrongPassword) .newKeyPassphrase(newPassword) .keys(protectedKey).getBytes()); @@ -93,9 +93,9 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { @MethodSource("provideInstances") public void nonUtf8PasswordsFail(SOP sop) { assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> - sop.changeKeyPassword().oldKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); + assumeSupported(sop::changeKeyPassword).oldKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); assertThrows(SOPGPException.PasswordNotHumanReadable.class, () -> - sop.changeKeyPassword().newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); + assumeSupported(sop::changeKeyPassword).newKeyPassphrase(new byte[] {(byte) 0xff, (byte) 0xfe})); } @@ -104,16 +104,16 @@ public class ChangeKeyPasswordTest extends AbstractSOPTest { public void testNoArmor(SOP sop) throws IOException { byte[] oldPassword = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] newPassword = "0r4ng3".getBytes(UTF8Util.UTF8); - byte[] protectedKey = sop.generateKey().withKeyPassword(oldPassword).generate().getBytes(); + byte[] protectedKey = assumeSupported(sop::generateKey).withKeyPassword(oldPassword).generate().getBytes(); - byte[] armored = sop.changeKeyPassword() + byte[] armored = assumeSupported(sop::changeKeyPassword) .oldKeyPassphrase(oldPassword) .newKeyPassphrase(newPassword) .keys(protectedKey) .getBytes(); JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_PRIVATE_KEY_BLOCK); - byte[] unarmored = sop.changeKeyPassword() + byte[] unarmored = assumeSupported(sop::changeKeyPassword) .noArmor() .oldKeyPassphrase(oldPassword) .newKeyPassphrase(newPassword) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java index 65ec4a5..8fd201a 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DecryptWithSessionKeyTest.java @@ -41,7 +41,7 @@ public class DecryptWithSessionKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testDecryptAndExtractSessionKey(SOP sop) throws IOException { - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult(); @@ -54,7 +54,7 @@ public class DecryptWithSessionKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testDecryptWithSessionKey(SOP sop) throws IOException { - byte[] decrypted = sop.decrypt() + byte[] decrypted = assumeSupported(sop::decrypt) .withSessionKey(SessionKey.fromString(SESSION_KEY)) .ciphertext(CIPHERTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java index e404599..415b9db 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/DetachedSignDetachedVerifyTest.java @@ -37,13 +37,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signVerifyWithAliceKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -60,14 +60,14 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signVerifyTextModeWithAliceKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(SignAs.text) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -85,7 +85,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -101,13 +101,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signVerifyWithBobKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -123,13 +123,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signVerifyWithCarolKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -145,7 +145,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signVerifyWithEncryptedKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword(TestData.PASSWORD) .data(message) @@ -154,7 +154,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { JUtils.assertArrayStartsWith(signature, TestData.BEGIN_PGP_SIGNATURE); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -170,18 +170,18 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { public void signArmorVerifyWithBobKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.detachedSign() + byte[] signature = assumeSupported(sop::detachedSign) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) .toByteArrayAndResult() .getBytes(); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(signature) .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(message); @@ -199,7 +199,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date beforeSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() - 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notAfter(beforeSignature) .signatures(signature) @@ -213,7 +213,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] signature = TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date afterSignature = new Date(TestData.ALICE_DETACHED_SIGNED_MESSAGE_DATE.getTime() + 1000); // 1 sec after sig - assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .notBefore(afterSignature) .signatures(signature) @@ -224,13 +224,13 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { @MethodSource("provideInstances") public void signWithAliceVerifyWithBobThrowsNoSignature(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signatures = sop.detachedSign() + byte[] signatures = assumeSupported(sop::detachedSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - assertThrows(SOPGPException.NoSignature.class, () -> sop.detachedVerify() + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::detachedVerify) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(message)); @@ -240,7 +240,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { @MethodSource("provideInstances") public void signVerifyWithEncryptedKeyWithoutPassphraseFails(SOP sop) { assertThrows(SOPGPException.KeyIsProtected.class, () -> - sop.detachedSign() + assumeSupported(sop::detachedSign) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .data(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() @@ -253,7 +253,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signature = sop.sign() + byte[] signature = assumeSupported(sop::sign) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .withKeyPassword("wrong") .withKeyPassword(TestData.PASSWORD) // correct @@ -262,7 +262,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes(); - List verificationList = sop.verify() + List verificationList = assumeSupported(sop::verify) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signature) .data(message); @@ -279,7 +279,7 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); assertThrows(SOPGPException.MissingArg.class, () -> - sop.verify() + assumeSupported(sop::verify) .signatures(TestData.ALICE_DETACHED_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8)) .data(message)); } @@ -288,14 +288,14 @@ public class DetachedSignDetachedVerifyTest extends AbstractSOPTest { @MethodSource("provideInstances") public void signVerifyWithMultipleKeys(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signatures = sop.detachedSign() + byte[] signatures = assumeSupported(sop::detachedSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult() .getBytes(); - List verificationList = sop.detachedVerify() + List verificationList = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java index 330ca90..937b5b7 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java @@ -49,7 +49,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - ByteArrayAndResult encResult = sop.encrypt() + ByteArrayAndResult encResult = assumeSupported(sop::encrypt) .withPassword("sw0rdf1sh") .plaintext(message) .toByteArrayAndResult(); @@ -57,7 +57,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = encResult.getBytes(); Optional encSessionKey = encResult.getResult().getSessionKey(); - ByteArrayAndResult decResult = sop.decrypt() + ByteArrayAndResult decResult = assumeSupported(sop::decrypt) .withPassword("sw0rdf1sh") .ciphertext(ciphertext) .toByteArrayAndResult(); @@ -65,9 +65,10 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] plaintext = decResult.getBytes(); Optional decSessionKey = decResult.getResult().getSessionKey(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); if (encSessionKey.isPresent() && decSessionKey.isPresent()) { - assertEquals(encSessionKey.get(), decSessionKey.get()); + assertEquals(encSessionKey.get(), decSessionKey.get(), + "Extracted Session Key mismatch."); } } @@ -75,91 +76,93 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .toByteArrayAndResult() .getBytes(); - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); byte[] plaintext = bytesAndResult.getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); DecryptionResult result = bytesAndResult.getResult(); - assertNotNull(result.getSessionKey().get()); + if (result.getSessionKey().isPresent()) { + assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null."); + } } @ParameterizedTest @MethodSource("provideInstances") public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .toByteArrayAndResult() .getBytes(); - byte[] plaintext = sop.decrypt() + byte[] plaintext = assumeSupported(sop::decrypt) .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); } @ParameterizedTest @MethodSource("provideInstances") public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .plaintext(message) .toByteArrayAndResult() .getBytes(); - byte[] plaintext = sop.decrypt() + byte[] plaintext = assumeSupported(sop::decrypt) .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult() .getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); } @ParameterizedTest @MethodSource("provideInstances") public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .noArmor() .plaintext(message) .toByteArrayAndResult() .getBytes(); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(ciphertext) .getBytes(); - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .ciphertext(armored) .toByteArrayAndResult(); byte[] plaintext = bytesAndResult.getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); } @ParameterizedTest @MethodSource("provideInstances") public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.binary) @@ -167,17 +170,19 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes(); - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); byte[] plaintext = bytesAndResult.getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); DecryptionResult result = bytesAndResult.getResult(); - assertNotNull(result.getSessionKey().get()); + if (result.getSessionKey().isPresent()) { + assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null."); + } List verificationList = result.getVerifications(); VerificationListAssert.assertThatVerificationList(verificationList) @@ -191,7 +196,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(EncryptAs.text) @@ -199,14 +204,14 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes(); - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .ciphertext(ciphertext) .toByteArrayAndResult(); byte[] plaintext = bytesAndResult.getBytes(); - assertArrayEquals(message, plaintext); + assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext."); DecryptionResult result = bytesAndResult.getResult(); assertNotNull(result.getSessionKey().get()); @@ -222,17 +227,17 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException { byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8); - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .withKeyPassword(keyPassword) .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] ciphertext = sop.encrypt() + byte[] ciphertext = assumeSupported(sop::encrypt) .withCert(cert) .signWith(key) .withKeyPassword(keyPassword) @@ -240,7 +245,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult() .getBytes(); - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(key) .withKeyPassword(keyPassword) .verifyWithCert(cert) @@ -273,7 +278,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date assertThrows(SOPGPException.NoSignature.class, () -> { - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotAfter(beforeSignature) @@ -307,7 +312,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date assertThrows(SOPGPException.NoSignature.class, () -> { - ByteArrayAndResult bytesAndResult = sop.decrypt() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::decrypt) .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .verifyNotBefore(afterSignature) @@ -326,7 +331,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { public void missingArgsTest(SOP sop) { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - assertThrows(SOPGPException.MissingArg.class, () -> sop.encrypt() + assertThrows(SOPGPException.MissingArg.class, () -> assumeSupported(sop::encrypt) .plaintext(message) .toByteArrayAndResult() .getBytes()); @@ -336,7 +341,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { @MethodSource("provideInstances") public void passingSecretKeysForPublicKeysFails(SOP sop) { assertThrows(SOPGPException.BadData.class, () -> - sop.encrypt() + assumeSupported(sop::encrypt) .withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() @@ -346,19 +351,19 @@ public class EncryptDecryptTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void encryptDecryptWithAllSupportedKeyGenerationProfiles(SOP sop) throws IOException { - List profiles = sop.listProfiles().generateKey(); + List profiles = assumeSupported(sop::listProfiles).generateKey(); List keys = new ArrayList<>(); List certs = new ArrayList<>(); for (Profile p : profiles) { - byte[] k = sop.generateKey() + byte[] k = assumeSupported(sop::generateKey) .profile(p) .userId(p.getName()) .generate() .getBytes(); keys.add(k); - byte[] c = sop.extractCert() + byte[] c = assumeSupported(sop::extractCert) .key(k) .getBytes(); certs.add(c); @@ -366,7 +371,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] plaintext = "Hello, World!\n".getBytes(); - Encrypt encrypt = sop.encrypt(); + Encrypt encrypt = assumeSupported(sop::encrypt); for (byte[] c : certs) { encrypt.withCert(c); } @@ -380,7 +385,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { byte[] ciphertext = encRes.getBytes(); for (byte[] k : keys) { - Decrypt decrypt = sop.decrypt() + Decrypt decrypt = assumeSupported(sop::decrypt) .withKey(k); for (byte[] c : certs) { decrypt.verifyWithCert(c); @@ -389,7 +394,7 @@ public class EncryptDecryptTest extends AbstractSOPTest { .toByteArrayAndResult(); DecryptionResult dResult = decRes.getResult(); byte[] decPlaintext = decRes.getBytes(); - assertArrayEquals(plaintext, decPlaintext); + assertArrayEquals(plaintext, decPlaintext, "Decrypted plaintext does not match original plaintext."); assertEquals(certs.size(), dResult.getVerifications().size()); } } diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java index 99acf81..94d9927 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ExtractCertTest.java @@ -28,12 +28,12 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractArmoredCertFromArmoredKeyTest(SOP sop) throws IOException { - InputStream keyIn = sop.generateKey() + InputStream keyIn = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getInputStream(); - byte[] cert = sop.extractCert().key(keyIn).getBytes(); + byte[] cert = assumeSupported(sop::extractCert).key(keyIn).getBytes(); JUtils.assertArrayStartsWith(cert, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK); JUtils.assertArrayEndsWithIgnoreNewlines(cert, TestData.END_PGP_PUBLIC_KEY_BLOCK); } @@ -41,7 +41,7 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractAliceCertFromAliceKeyTest(SOP sop) throws IOException { - byte[] armoredCert = sop.extractCert() + byte[] armoredCert = assumeSupported(sop::extractCert) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); JUtils.assertAsciiArmorEquals(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); @@ -50,7 +50,7 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractBobsCertFromBobsKeyTest(SOP sop) throws IOException { - byte[] armoredCert = sop.extractCert() + byte[] armoredCert = assumeSupported(sop::extractCert) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); JUtils.assertAsciiArmorEquals(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); @@ -59,7 +59,7 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractCarolsCertFromCarolsKeyTest(SOP sop) throws IOException { - byte[] armoredCert = sop.extractCert() + byte[] armoredCert = assumeSupported(sop::extractCert) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .getBytes(); JUtils.assertAsciiArmorEquals(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8), armoredCert); @@ -68,12 +68,12 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractUnarmoredCertFromArmoredKeyTest(SOP sop) throws IOException { - InputStream keyIn = sop.generateKey() + InputStream keyIn = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getInputStream(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .noArmor() .key(keyIn) .getBytes(); @@ -84,13 +84,13 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractArmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { - InputStream keyIn = sop.generateKey() + InputStream keyIn = assumeSupported(sop::generateKey) .userId("Alice ") .noArmor() .generate() .getInputStream(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(keyIn) .getBytes(); @@ -101,13 +101,13 @@ public class ExtractCertTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void extractUnarmoredCertFromUnarmoredKeyTest(SOP sop) throws IOException { - InputStream keyIn = sop.generateKey() + InputStream keyIn = assumeSupported(sop::generateKey) .noArmor() .userId("Alice ") .generate() .getInputStream(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .noArmor() .key(keyIn) .getBytes(); diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java index 0d8b78e..787cf62 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/GenerateKeyTest.java @@ -33,7 +33,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyTest(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); @@ -45,7 +45,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyNoArmor(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .noArmor() .generate() @@ -57,7 +57,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyWithMultipleUserIdsTest(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .userId("Bob ") .generate() @@ -70,7 +70,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyWithoutUserIdTest(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .generate() .getBytes(); @@ -81,7 +81,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyWithPasswordTest(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .withKeyPassword("sw0rdf1sh") .generate() @@ -94,7 +94,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyWithMultipleUserIdsAndPassword(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .userId("Bob ") .withKeyPassword("sw0rdf1sh") @@ -108,17 +108,17 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateSigningOnlyKey(SOP sop) throws IOException { - byte[] signingOnlyKey = sop.generateKey() + byte[] signingOnlyKey = assumeSupported(sop::generateKey) .signingOnly() .userId("Alice ") .generate() .getBytes(); - byte[] signingOnlyCert = sop.extractCert() + byte[] signingOnlyCert = assumeSupported(sop::extractCert) .key(signingOnlyKey) .getBytes(); assertThrows(SOPGPException.CertCannotEncrypt.class, () -> - sop.encrypt().withCert(signingOnlyCert) + assumeSupported(sop::encrypt).withCert(signingOnlyCert) .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8)) .toByteArrayAndResult() .getBytes()); @@ -127,7 +127,7 @@ public class GenerateKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void generateKeyWithSupportedProfiles(SOP sop) throws IOException { - List profiles = sop.listProfiles() + List profiles = assumeSupported(sop::listProfiles) .generateKey(); for (Profile profile : profiles) { @@ -138,7 +138,7 @@ public class GenerateKeyTest extends AbstractSOPTest { private void generateKeyWithProfile(SOP sop, String profile) throws IOException { byte[] key; try { - key = sop.generateKey() + key = assumeSupported(sop::generateKey) .profile(profile) .userId("Alice ") .generate() diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java index 3e20a09..ac043b3 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineDetachDetachedVerifyTest.java @@ -36,12 +36,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { public void inlineSignThenDetachThenDetachedVerifyTest(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - ByteArrayAndResult bytesAndResult = sop.inlineDetach() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::inlineDetach) .message(inlineSigned) .toByteArrayAndResult(); @@ -51,7 +51,7 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { byte[] signatures = bytesAndResult.getResult() .getBytes(); - List verifications = sop.detachedVerify() + List verifications = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(signatures) .data(plaintext); @@ -64,12 +64,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { public void inlineSignThenDetachNoArmorThenArmorThenDetachedVerifyTest(SOP sop) throws IOException { byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); - ByteArrayAndResult bytesAndResult = sop.inlineDetach() + ByteArrayAndResult bytesAndResult = assumeSupported(sop::inlineDetach) .noArmor() .message(inlineSigned) .toByteArrayAndResult(); @@ -81,12 +81,12 @@ public class InlineSignInlineDetachDetachedVerifyTest extends AbstractSOPTest { .getBytes(); Assertions.assertFalse(JUtils.arrayStartsWith(signatures, TestData.BEGIN_PGP_SIGNATURE)); - byte[] armored = sop.armor() + byte[] armored = assumeSupported(sop::armor) .data(signatures) .getBytes(); JUtils.assertArrayStartsWith(armored, TestData.BEGIN_PGP_SIGNATURE); - List verifications = sop.detachedVerify() + List verifications = assumeSupported(sop::detachedVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .signatures(armored) .data(plaintext); diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java index 39a26c6..d751ee8 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/InlineSignInlineVerifyTest.java @@ -40,14 +40,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void inlineSignVerifyAlice(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -66,7 +66,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void inlineSignVerifyAliceNoArmor(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .noArmor() .data(message) @@ -74,7 +74,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { Assertions.assertFalse(JUtils.arrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE)); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -93,7 +93,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void clearsignVerifyAlice(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] clearsigned = sop.inlineSign() + byte[] clearsigned = assumeSupported(sop::inlineSign) .key(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.clearsigned) .data(message) @@ -101,12 +101,13 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { JUtils.assertArrayStartsWith(clearsigned, TestData.BEGIN_PGP_SIGNED_MESSAGE); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(clearsigned) .toByteArrayAndResult(); - assertArrayEquals(message, bytesAndResult.getBytes()); + assertArrayEquals(message, bytesAndResult.getBytes(), + "ASCII armored message does not appear to start with the 'BEGIN PGP SIGNED MESSAGE' header."); List verificationList = bytesAndResult.getResult(); VerificationListAssert.assertThatVerificationList(verificationList) @@ -121,7 +122,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { byte[] message = TestData.ALICE_INLINE_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8); Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) .toByteArrayAndResult(); @@ -141,7 +142,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify) .notBefore(afterSignature) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) @@ -155,7 +156,7 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { Date signatureDate = TestData.ALICE_INLINE_SIGNED_MESSAGE_DATE; Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before sig - assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify() + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify) .notAfter(beforeSignature) .cert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8)) .data(message) @@ -167,14 +168,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void inlineSignVerifyBob(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -193,14 +194,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void inlineSignVerifyCarol(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .key(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8)) .data(message) .getBytes(); JUtils.assertArrayStartsWith(inlineSigned, TestData.BEGIN_PGP_MESSAGE); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); @@ -219,14 +220,14 @@ public class InlineSignInlineVerifyTest extends AbstractSOPTest { public void inlineSignVerifyProtectedKey(SOP sop) throws IOException { byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] inlineSigned = sop.inlineSign() + byte[] inlineSigned = assumeSupported(sop::inlineSign) .withKeyPassword(TestData.PASSWORD) .key(TestData.PASSWORD_PROTECTED_KEY.getBytes(StandardCharsets.UTF_8)) .mode(InlineSignAs.binary) .data(message) .getBytes(); - ByteArrayAndResult> bytesAndResult = sop.inlineVerify() + ByteArrayAndResult> bytesAndResult = assumeSupported(sop::inlineVerify) .cert(TestData.PASSWORD_PROTECTED_CERT.getBytes(StandardCharsets.UTF_8)) .data(inlineSigned) .toByteArrayAndResult(); diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java index 6d3c4c4..4faa1b3 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/ListProfilesTest.java @@ -26,8 +26,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void listGenerateKeyProfiles(SOP sop) { - List profiles = sop - .listProfiles() + List profiles = assumeSupported(sop::listProfiles) .generateKey(); assertFalse(profiles.isEmpty()); @@ -36,8 +35,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void listEncryptProfiles(SOP sop) { - List profiles = sop - .listProfiles() + List profiles = assumeSupported(sop::listProfiles) .encrypt(); assertFalse(profiles.isEmpty()); @@ -46,8 +44,7 @@ public class ListProfilesTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void listUnsupportedProfiles(SOP sop) { - assertThrows(SOPGPException.UnsupportedProfile.class, () -> sop - .listProfiles() + assertThrows(SOPGPException.UnsupportedProfile.class, () -> assumeSupported(sop::listProfiles) .subcommand("invalid")); } } diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java index b577017..501f53c 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/MergeCertsTest.java @@ -24,16 +24,16 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testMergeWithItself(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(cert) .baseCertificates(cert) .getBytes(); @@ -44,17 +44,17 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testMergeWithItselfArmored(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .noArmor() .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(cert) .baseCertificates(cert) .getBytes(); @@ -65,18 +65,18 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testMergeWithItselfViaBase(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); byte[] certs = ArraysKt.plus(cert, cert); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(cert) .baseCertificates(certs) .getBytes(); @@ -87,20 +87,20 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testApplyBaseToUpdate(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); - byte[] update = sop.revokeKey() + byte[] update = assumeSupported(sop::revokeKey) .keys(key) .getBytes(); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(cert) .baseCertificates(update) .getBytes(); @@ -111,20 +111,20 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testApplyUpdateToBase(SOP sop) throws IOException { - byte[] key = sop.generateKey() + byte[] key = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] cert = sop.extractCert() + byte[] cert = assumeSupported(sop::extractCert) .key(key) .getBytes(); - byte[] update = sop.revokeKey() + byte[] update = assumeSupported(sop::revokeKey) .keys(key) .getBytes(); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(update) .baseCertificates(cert) .getBytes(); @@ -135,25 +135,25 @@ public class MergeCertsTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void testApplyUpdateToMissingBaseDoesNothing(SOP sop) throws IOException { - byte[] aliceKey = sop.generateKey() + byte[] aliceKey = assumeSupported(sop::generateKey) .userId("Alice ") .generate() .getBytes(); - byte[] aliceCert = sop.extractCert() + byte[] aliceCert = assumeSupported(sop::extractCert) .key(aliceKey) .getBytes(); - byte[] bobKey = sop.generateKey() + byte[] bobKey = assumeSupported(sop::generateKey) .userId("Bob ") .generate() .getBytes(); - byte[] bobCert = sop.extractCert() + byte[] bobCert = assumeSupported(sop::extractCert) .key(bobKey) .getBytes(); - byte[] merged = sop.mergeCerts() + byte[] merged = assumeSupported(sop::mergeCerts) .updates(bobCert) .baseCertificates(aliceCert) .getBytes(); diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java index cb51332..1880d58 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/RevokeKeyTest.java @@ -36,8 +36,8 @@ public class RevokeKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void revokeUnprotectedKey(SOP sop) throws IOException { - byte[] secretKey = sop.generateKey().userId("Alice ").generate().getBytes(); - byte[] revocation = sop.revokeKey().keys(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice ").generate().getBytes(); + byte[] revocation = assumeSupported(sop::revokeKey).keys(secretKey).getBytes(); assertTrue(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertFalse(Arrays.equals(secretKey, revocation)); @@ -46,8 +46,8 @@ public class RevokeKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void revokeUnprotectedKeyNoArmor(SOP sop) throws IOException { - byte[] secretKey = sop.generateKey().userId("Alice ").generate().getBytes(); - byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice ").generate().getBytes(); + byte[] revocation = assumeSupported(sop::revokeKey).noArmor().keys(secretKey).getBytes(); assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); } @@ -55,8 +55,8 @@ public class RevokeKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void revokeUnprotectedKeyUnarmored(SOP sop) throws IOException { - byte[] secretKey = sop.generateKey().userId("Alice ").noArmor().generate().getBytes(); - byte[] revocation = sop.revokeKey().noArmor().keys(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).userId("Alice ").noArmor().generate().getBytes(); + byte[] revocation = assumeSupported(sop::revokeKey).noArmor().keys(secretKey).getBytes(); assertFalse(JUtils.arrayStartsWith(revocation, TestData.BEGIN_PGP_PUBLIC_KEY_BLOCK)); assertFalse(Arrays.equals(secretKey, revocation)); @@ -65,18 +65,18 @@ public class RevokeKeyTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void revokeCertificateFails(SOP sop) throws IOException { - byte[] secretKey = sop.generateKey().generate().getBytes(); - byte[] certificate = sop.extractCert().key(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).generate().getBytes(); + byte[] certificate = assumeSupported(sop::extractCert).key(secretKey).getBytes(); - assertThrows(SOPGPException.BadData.class, () -> sop.revokeKey().keys(certificate).getBytes()); + assertThrows(SOPGPException.BadData.class, () -> assumeSupported(sop::revokeKey).keys(certificate).getBytes()); } @ParameterizedTest @MethodSource("provideInstances") public void revokeProtectedKey(SOP sop) throws IOException { byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); - byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); - byte[] revocation = sop.revokeKey().withKeyPassword(password).keys(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] revocation = assumeSupported(sop::revokeKey).withKeyPassword(password).keys(secretKey).getBytes(); assertFalse(Arrays.equals(secretKey, revocation)); } @@ -86,8 +86,8 @@ public class RevokeKeyTest extends AbstractSOPTest { public void revokeProtectedKeyWithMultiplePasswordOptions(SOP sop) throws IOException { byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); byte[] wrongPassword = "0r4ng3".getBytes(UTF8Util.UTF8); - byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); - byte[] revocation = sop.revokeKey().withKeyPassword(wrongPassword).withKeyPassword(password).keys(secretKey).getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] revocation = assumeSupported(sop::revokeKey).withKeyPassword(wrongPassword).withKeyPassword(password).keys(secretKey).getBytes(); assertFalse(Arrays.equals(secretKey, revocation)); } @@ -96,9 +96,9 @@ public class RevokeKeyTest extends AbstractSOPTest { @MethodSource("provideInstances") public void revokeProtectedKeyWithMissingPassphraseFails(SOP sop) throws IOException { byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); - byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice ").generate().getBytes(); - assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().keys(secretKey).getBytes()); + assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::revokeKey).keys(secretKey).getBytes()); } @ParameterizedTest @@ -106,27 +106,27 @@ public class RevokeKeyTest extends AbstractSOPTest { public void revokeProtectedKeyWithWrongPassphraseFails(SOP sop) throws IOException { byte[] password = "sw0rdf1sh".getBytes(UTF8Util.UTF8); String wrongPassword = "or4ng3"; - byte[] secretKey = sop.generateKey().withKeyPassword(password).userId("Alice ").generate().getBytes(); + byte[] secretKey = assumeSupported(sop::generateKey).withKeyPassword(password).userId("Alice ").generate().getBytes(); - assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.revokeKey().withKeyPassword(wrongPassword).keys(secretKey).getBytes()); + assertThrows(SOPGPException.KeyIsProtected.class, () -> assumeSupported(sop::revokeKey).withKeyPassword(wrongPassword).keys(secretKey).getBytes()); } @ParameterizedTest @MethodSource("provideInstances") public void revokeKeyIsNowHardRevoked(SOP sop) throws IOException { - byte[] key = sop.generateKey().generate().getBytes(); - byte[] cert = sop.extractCert().key(key).getBytes(); + byte[] key = assumeSupported(sop::generateKey).generate().getBytes(); + byte[] cert = assumeSupported(sop::extractCert).key(key).getBytes(); // Sign a message with the key byte[] msg = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8); - byte[] signedMsg = sop.inlineSign().key(key).data(msg).getBytes(); + byte[] signedMsg = assumeSupported(sop::inlineSign).key(key).data(msg).getBytes(); // Verifying the message with the valid cert works - List result = sop.inlineVerify().cert(cert).data(signedMsg).toByteArrayAndResult().getResult(); + List result = assumeSupported(sop::inlineVerify).cert(cert).data(signedMsg).toByteArrayAndResult().getResult(); VerificationListAssert.assertThatVerificationList(result).hasSingleItem(); // Now hard revoke the key and re-check signature, expecting no valid certification - byte[] revokedCert = sop.revokeKey().keys(key).getBytes(); - assertThrows(SOPGPException.NoSignature.class, () -> sop.inlineVerify().cert(revokedCert).data(signedMsg).toByteArrayAndResult()); + byte[] revokedCert = assumeSupported(sop::revokeKey).keys(key).getBytes(); + assertThrows(SOPGPException.NoSignature.class, () -> assumeSupported(sop::inlineVerify).cert(revokedCert).data(signedMsg).toByteArrayAndResult()); } } diff --git a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java index 47644bf..71f7efd 100644 --- a/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java +++ b/sop-java-testfixtures/src/main/java/sop/testsuite/operation/VersionTest.java @@ -28,7 +28,7 @@ public class VersionTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void versionNameTest(SOP sop) { - String name = sop.version().getName(); + String name = assumeSupported(sop::version).getName(); assertNotNull(name); assertFalse(name.isEmpty()); } @@ -36,21 +36,21 @@ public class VersionTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void versionVersionTest(SOP sop) { - String version = sop.version().getVersion(); + String version = assumeSupported(sop::version).getVersion(); assertFalse(version.isEmpty()); } @ParameterizedTest @MethodSource("provideInstances") public void backendVersionTest(SOP sop) { - String backend = sop.version().getBackendVersion(); + String backend = assumeSupported(sop::version).getBackendVersion(); assertFalse(backend.isEmpty()); } @ParameterizedTest @MethodSource("provideInstances") public void extendedVersionTest(SOP sop) { - String extended = sop.version().getExtendedVersion(); + String extended = assumeSupported(sop::version).getExtendedVersion(); assertFalse(extended.isEmpty()); } @@ -58,27 +58,27 @@ public class VersionTest extends AbstractSOPTest { @MethodSource("provideInstances") public void sopSpecVersionTest(SOP sop) { try { - sop.version().getSopSpecVersion(); + assumeSupported(sop::version).getSopSpecVersion(); } catch (RuntimeException e) { throw new TestAbortedException("SOP backend does not support 'version --sop-spec' yet."); } - String sopSpec = sop.version().getSopSpecVersion(); - if (sop.version().isSopSpecImplementationIncomplete()) { + String sopSpec = assumeSupported(sop::version).getSopSpecVersion(); + if (assumeSupported(sop::version).isSopSpecImplementationIncomplete()) { assertTrue(sopSpec.startsWith("~draft-dkg-openpgp-stateless-cli-")); } else { assertTrue(sopSpec.startsWith("draft-dkg-openpgp-stateless-cli-")); } - int sopRevision = sop.version().getSopSpecRevisionNumber(); - assertTrue(sop.version().getSopSpecRevisionName().endsWith("" + sopRevision)); + int sopRevision = assumeSupported(sop::version).getSopSpecRevisionNumber(); + assertTrue(assumeSupported(sop::version).getSopSpecRevisionName().endsWith("" + sopRevision)); } @ParameterizedTest @MethodSource("provideInstances") public void sopVVersionTest(SOP sop) { try { - sop.version().getSopVVersion(); + assumeSupported(sop::version).getSopVVersion(); } catch (SOPGPException.UnsupportedOption e) { throw new TestAbortedException( "Implementation does (gracefully) not provide coverage for any sopv interface version."); @@ -90,6 +90,6 @@ public class VersionTest extends AbstractSOPTest { @ParameterizedTest @MethodSource("provideInstances") public void sopJavaVersionTest(SOP sop) { - assertNotNull(sop.version().getSopJavaVersion()); + assertNotNull(assumeSupported(sop::version).getSopJavaVersion()); } } diff --git a/sop-java/src/test/java/sop/VerificationJSONTest.java b/sop-java/src/test/java/sop/VerificationJSONTest.java index a80e6fc..a6e5d96 100644 --- a/sop-java/src/test/java/sop/VerificationJSONTest.java +++ b/sop-java/src/test/java/sop/VerificationJSONTest.java @@ -7,6 +7,7 @@ package sop; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import sop.enums.SignatureMode; +import sop.testsuite.assertions.VerificationAssert; import java.text.ParseException; import java.util.ArrayList; @@ -158,6 +159,8 @@ public class VerificationJSONTest { assertNull(json.getExt()); verification = new Verification(verification.getCreationTime(), verification.getSigningKeyFingerprint(), verification.getSigningCertFingerprint(), verification.getSignatureMode().get(), json, dummySerializer); + VerificationAssert.assertThatVerification(verification) + .hasJSON(dummyParser, j -> j.getSigners().contains("alice.pgp")); assertEquals(string, verification.toString()); } } From d32d9b54d75586c834d73b08a6b2c48d7b9d3ddc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 1 Jul 2025 15:06:50 +0200 Subject: [PATCH 440/444] Depend on junit-platform-suite to avoid needing to inherit test suite for external-sop tests --- external-sop/build.gradle | 4 +++- .../sop/testsuite/external/ExternalTestSuite.java | 14 ++++++++++++++ .../operation/ExternalArmorDearmorTest.java | 13 ------------- .../ExternalCertifyValidateUserIdTest.java | 13 ------------- .../operation/ExternalChangeKeyPasswordTest.java | 13 ------------- .../ExternalDecryptWithSessionKeyTest.java | 13 ------------- .../ExternalDetachedSignDetachedVerifyTest.java | 12 ------------ .../operation/ExternalEncryptDecryptTest.java | 13 ------------- .../operation/ExternalExtractCertTest.java | 13 ------------- .../operation/ExternalGenerateKeyTest.java | 13 ------------- ...alInlineSignInlineDetachDetachedVerifyTest.java | 14 -------------- .../ExternalInlineSignInlineVerifyTest.java | 13 ------------- .../operation/ExternalListProfilesTest.java | 13 ------------- .../external/operation/ExternalMergeCertsTest.java | 13 ------------- .../external/operation/ExternalRevokeKeyTest.java | 13 ------------- .../external/operation/ExternalVersionTest.java | 13 ------------- 16 files changed, 17 insertions(+), 183 deletions(-) create mode 100644 external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java delete mode 100644 external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java diff --git a/external-sop/build.gradle b/external-sop/build.gradle index d1a7ffb..2dfbf7e 100644 --- a/external-sop/build.gradle +++ b/external-sop/build.gradle @@ -15,7 +15,9 @@ repositories { dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + testImplementation "org.junit.platform:junit-platform-suite-api:1.13.2" + testRuntimeOnly 'org.junit.platform:junit-platform-suite:1.13.2' api project(":sop-java") api "org.slf4j:slf4j-api:$slf4jVersion" diff --git a/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java b/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java new file mode 100644 index 0000000..6e991cf --- /dev/null +++ b/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java @@ -0,0 +1,14 @@ +package sop.testsuite.external; + +import org.junit.platform.suite.api.IncludeClassNamePatterns; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +@Suite +@SuiteDisplayName("External SOP Tests") +@SelectPackages("sop.testsuite.operation") +@IncludeClassNamePatterns(".*Test") +public class ExternalTestSuite { + +} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java deleted file mode 100644 index 1d8ff2b..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalArmorDearmorTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.ArmorDearmorTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalArmorDearmorTest extends ArmorDearmorTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java deleted file mode 100644 index bb319ca..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalCertifyValidateUserIdTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.CertifyValidateUserIdTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalCertifyValidateUserIdTest extends CertifyValidateUserIdTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java deleted file mode 100644 index 42a9693..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalChangeKeyPasswordTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.ChangeKeyPasswordTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalChangeKeyPasswordTest extends ChangeKeyPasswordTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java deleted file mode 100644 index 0ac03a4..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDecryptWithSessionKeyTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.DecryptWithSessionKeyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalDecryptWithSessionKeyTest extends DecryptWithSessionKeyTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java deleted file mode 100644 index 13959df..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalDetachedSignDetachedVerifyTest.java +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.DetachedSignDetachedVerifyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalDetachedSignDetachedVerifyTest extends DetachedSignDetachedVerifyTest { -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java deleted file mode 100644 index b83ca46..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalEncryptDecryptTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.EncryptDecryptTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalEncryptDecryptTest extends EncryptDecryptTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java deleted file mode 100644 index f47656c..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalExtractCertTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.ExtractCertTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalExtractCertTest extends ExtractCertTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java deleted file mode 100644 index 7ac971b..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalGenerateKeyTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.GenerateKeyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalGenerateKeyTest extends GenerateKeyTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java deleted file mode 100644 index 2dd3396..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineDetachDetachedVerifyTest.java +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.InlineSignInlineDetachDetachedVerifyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalInlineSignInlineDetachDetachedVerifyTest - extends InlineSignInlineDetachDetachedVerifyTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java deleted file mode 100644 index 24e30aa..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalInlineSignInlineVerifyTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.InlineSignInlineVerifyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalInlineSignInlineVerifyTest extends InlineSignInlineVerifyTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java deleted file mode 100644 index 18da883..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalListProfilesTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.ListProfilesTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalListProfilesTest extends ListProfilesTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java deleted file mode 100644 index 8b22b37..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalMergeCertsTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.MergeCertsTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalMergeCertsTest extends MergeCertsTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java deleted file mode 100644 index e2efe03..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalRevokeKeyTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.RevokeKeyTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalRevokeKeyTest extends RevokeKeyTest { - -} diff --git a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java b/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java deleted file mode 100644 index ee63f09..0000000 --- a/external-sop/src/test/java/sop/testsuite/external/operation/ExternalVersionTest.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.testsuite.external.operation; - -import org.junit.jupiter.api.condition.EnabledIf; -import sop.testsuite.operation.VersionTest; - -@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends") -public class ExternalVersionTest extends VersionTest { - -} From d4e8c14b08ccb5d36c1ebc98bfa06d7f80061dd0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 24 Jul 2025 12:53:27 +0200 Subject: [PATCH 441/444] Update documentation, add withKeyPassphrase(CharArray) methods --- .../main/kotlin/sop/operation/AbstractSign.kt | 13 ++- .../src/main/kotlin/sop/operation/Armor.kt | 1 + .../kotlin/sop/operation/CertifyUserId.kt | 102 +++++++++++++++--- .../kotlin/sop/operation/ChangeKeyPassword.kt | 51 ++++++--- .../src/main/kotlin/sop/operation/Dearmor.kt | 1 + .../src/main/kotlin/sop/operation/Decrypt.kt | 1 + .../main/kotlin/sop/operation/DetachedSign.kt | 1 + .../kotlin/sop/operation/DetachedVerify.kt | 1 + .../src/main/kotlin/sop/operation/Encrypt.kt | 1 + .../main/kotlin/sop/operation/ExtractCert.kt | 1 + .../main/kotlin/sop/operation/GenerateKey.kt | 1 + .../main/kotlin/sop/operation/InlineDetach.kt | 1 + .../main/kotlin/sop/operation/InlineSign.kt | 1 + .../main/kotlin/sop/operation/InlineVerify.kt | 2 +- .../main/kotlin/sop/operation/ListProfiles.kt | 2 +- .../main/kotlin/sop/operation/MergeCerts.kt | 53 +++++++-- .../main/kotlin/sop/operation/RevokeKey.kt | 38 ++++++- .../main/kotlin/sop/operation/UpdateKey.kt | 66 ++++++++---- .../kotlin/sop/operation/ValidateUserId.kt | 51 +++++---- .../kotlin/sop/operation/VerifySignatures.kt | 1 + .../src/main/kotlin/sop/operation/Version.kt | 1 + 21 files changed, 308 insertions(+), 82 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt index 0258432..72b8f72 100644 --- a/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt +++ b/sop-java/src/main/kotlin/sop/operation/AbstractSign.kt @@ -61,9 +61,18 @@ interface AbstractSign { * @param password password * @return builder instance * @throws UnsupportedOption if key passwords are not supported - * @throws PasswordNotHumanReadable if the provided passphrase is not human-readable */ - @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + @Throws(UnsupportedOption::class) + fun withKeyPassword(password: CharArray): T = withKeyPassword(password.concatToString()) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + */ + @Throws(UnsupportedOption::class) fun withKeyPassword(password: String): T = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) /** diff --git a/sop-java/src/main/kotlin/sop/operation/Armor.kt b/sop-java/src/main/kotlin/sop/operation/Armor.kt index be7f1a3..b54aed7 100644 --- a/sop-java/src/main/kotlin/sop/operation/Armor.kt +++ b/sop-java/src/main/kotlin/sop/operation/Armor.kt @@ -9,6 +9,7 @@ import java.io.InputStream import sop.Ready import sop.exception.SOPGPException.BadData +/** Interface for armoring binary OpenPGP data. */ interface Armor { /** diff --git a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt index 642966b..d59f9f0 100644 --- a/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/CertifyUserId.kt @@ -7,35 +7,111 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.Ready -import sop.exception.SOPGPException +import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for issuing certifications over UserIDs on certificates. */ interface CertifyUserId { - @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): CertifyUserId + /** Disable ASCII armor for the output. */ + @Throws(UnsupportedOption::class) fun noArmor(): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) fun userId(userId: String): CertifyUserId + /** + * Add a user-id that shall be certified on the certificates. + * + * @param userId user-id + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun userId(userId: String): CertifyUserId - @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + */ + @Throws(UnsupportedOption::class) + fun withKeyPassword(password: CharArray): CertifyUserId = + withKeyPassword(password.concatToString()) + + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + */ + @Throws(UnsupportedOption::class) fun withKeyPassword(password: String): CertifyUserId = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) - @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + /** + * Provide the password for the secret key used for signing. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if key passwords are not supported + * @throws PasswordNotHumanReadable if the provided password is not human-readable + */ + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) fun withKeyPassword(password: ByteArray): CertifyUserId - @Throws(SOPGPException.UnsupportedOption::class) fun noRequireSelfSig(): CertifyUserId + /** + * If this option is provided, it is possible to certify user-ids on certificates, which do not + * have a self-certification for the user-id. You can use this option to add pet-name + * certifications to certificates, e.g. "Mom". + * + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun noRequireSelfSig(): CertifyUserId - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) - fun keys(keys: InputStream): CertifyUserId + /** + * Provide signing keys for issuing the certifications. + * + * @param keys input stream containing one or more signing key + * @return builder instance + * @throws BadData if the keys cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun keys(keys: InputStream): CertifyUserId - @Throws(SOPGPException.BadData::class, IOException::class, SOPGPException.KeyIsProtected::class) + /** + * Provide signing keys for issuing the certifications. + * + * @param keys byte array containing one or more signing key + * @return builder instance + * @throws BadData if the keys cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun keys(keys: ByteArray): CertifyUserId = keys(keys.inputStream()) - @Throws( - SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + /** + * Provide the certificates that you want to create certifications for. + * + * @param certs input stream containing the certificates + * @return object to require the certified certificates from + * @throws BadData if the certificates cannot be read + * @throws IOException if an IO error occurs + * @throws KeyIsProtected if one or more signing keys are passphrase protected and cannot be + * unlocked + */ + @Throws(BadData::class, IOException::class, CertUserIdNoMatch::class, KeyIsProtected::class) fun certs(certs: InputStream): Ready - @Throws( - SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + /** + * Provide the certificates that you want to create certifications for. + * + * @param certs byte array containing the certificates + * @return object to require the certified certificates from + * @throws BadData if the certificates cannot be read + * @throws IOException if an IO error occurs + * @throws KeyIsProtected if one or more signing keys are passphrase protected and cannot be + * unlocked + */ + @Throws(BadData::class, IOException::class, CertUserIdNoMatch::class, KeyIsProtected::class) fun certs(certs: ByteArray): Ready = certs(certs.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt index 224e0f4..fe9b8c9 100644 --- a/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt +++ b/sop-java/src/main/kotlin/sop/operation/ChangeKeyPassword.kt @@ -11,6 +11,7 @@ import sop.exception.SOPGPException.KeyIsProtected import sop.exception.SOPGPException.PasswordNotHumanReadable import sop.util.UTF8Util +/** Interface for changing key passwords. */ interface ChangeKeyPassword { /** @@ -28,13 +29,8 @@ interface ChangeKeyPassword { * @param oldPassphrase old passphrase * @return builder instance */ - @Throws(PasswordNotHumanReadable::class) - fun oldKeyPassphrase(oldPassphrase: ByteArray): ChangeKeyPassword = - try { - oldKeyPassphrase(UTF8Util.decodeUTF8(oldPassphrase)) - } catch (e: CharacterCodingException) { - throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") - } + fun oldKeyPassphrase(oldPassphrase: CharArray): ChangeKeyPassword = + oldKeyPassphrase(oldPassphrase.concatToString()) /** * Provide a passphrase to unlock the secret key. This method can be provided multiple times to @@ -47,21 +43,33 @@ interface ChangeKeyPassword { fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword /** - * Provide a passphrase to re-lock the secret key with. This method can only be used once, and - * all key material encountered will be encrypted with the given passphrase. If this method is - * not called, the key material will not be protected. + * Provide a passphrase to unlock the secret key. This method can be provided multiple times to + * provide separate passphrases that are tried as a means to unlock any secret key material + * encountered. * - * @param newPassphrase new passphrase + * @param oldPassphrase old passphrase * @return builder instance + * @throws PasswordNotHumanReadable if the old key passphrase is not human-readable */ @Throws(PasswordNotHumanReadable::class) - fun newKeyPassphrase(newPassphrase: ByteArray): ChangeKeyPassword = + fun oldKeyPassphrase(oldPassphrase: ByteArray): ChangeKeyPassword = try { - newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)) + oldKeyPassphrase(UTF8Util.decodeUTF8(oldPassphrase)) } catch (e: CharacterCodingException) { throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") } + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + */ + fun newKeyPassphrase(newPassphrase: CharArray): ChangeKeyPassword = + newKeyPassphrase(newPassphrase.concatToString()) + /** * Provide a passphrase to re-lock the secret key with. This method can only be used once, and * all key material encountered will be encrypted with the given passphrase. If this method is @@ -72,6 +80,23 @@ interface ChangeKeyPassword { */ fun newKeyPassphrase(newPassphrase: String): ChangeKeyPassword + /** + * Provide a passphrase to re-lock the secret key with. This method can only be used once, and + * all key material encountered will be encrypted with the given passphrase. If this method is + * not called, the key material will not be protected. + * + * @param newPassphrase new passphrase + * @return builder instance + * @throws PasswordNotHumanReadable if the passphrase is not human-readable + */ + @Throws(PasswordNotHumanReadable::class) + fun newKeyPassphrase(newPassphrase: ByteArray): ChangeKeyPassword = + try { + newKeyPassphrase(UTF8Util.decodeUTF8(newPassphrase)) + } catch (e: CharacterCodingException) { + throw PasswordNotHumanReadable("Password MUST be a valid UTF8 string.") + } + /** * Provide the key material. * diff --git a/sop-java/src/main/kotlin/sop/operation/Dearmor.kt b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt index cc5e98d..2984f27 100644 --- a/sop-java/src/main/kotlin/sop/operation/Dearmor.kt +++ b/sop-java/src/main/kotlin/sop/operation/Dearmor.kt @@ -10,6 +10,7 @@ import sop.Ready import sop.exception.SOPGPException.BadData import sop.util.UTF8Util +/** Interface for removing ASCII armor from OpenPGP data. */ interface Dearmor { /** diff --git a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt index ae228e9..4d009f9 100644 --- a/sop-java/src/main/kotlin/sop/operation/Decrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Decrypt.kt @@ -13,6 +13,7 @@ import sop.SessionKey import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for decrypting encrypted OpenPGP messages. */ interface Decrypt { /** diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt index c0e62dd..4aaadc1 100644 --- a/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt +++ b/sop-java/src/main/kotlin/sop/operation/DetachedSign.kt @@ -11,6 +11,7 @@ import sop.SigningResult import sop.enums.SignAs import sop.exception.SOPGPException.* +/** Interface for creating detached signatures over plaintext messages. */ interface DetachedSign : AbstractSign { /** diff --git a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt index d899b54..319658d 100644 --- a/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt +++ b/sop-java/src/main/kotlin/sop/operation/DetachedVerify.kt @@ -8,6 +8,7 @@ import java.io.IOException import java.io.InputStream import sop.exception.SOPGPException.BadData +/** Interface for verifying detached OpenPGP signatures over plaintext messages. */ interface DetachedVerify : AbstractVerify, VerifySignatures { /** diff --git a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt index 71c04cb..02c7f97 100644 --- a/sop-java/src/main/kotlin/sop/operation/Encrypt.kt +++ b/sop-java/src/main/kotlin/sop/operation/Encrypt.kt @@ -13,6 +13,7 @@ import sop.enums.EncryptAs import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for creating encrypted OpenPGP messages. */ interface Encrypt { /** diff --git a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt index e2ce1cc..6485bc2 100644 --- a/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt +++ b/sop-java/src/main/kotlin/sop/operation/ExtractCert.kt @@ -9,6 +9,7 @@ import java.io.InputStream import sop.Ready import sop.exception.SOPGPException.BadData +/** Interface for extracting certificates from OpenPGP keys. */ interface ExtractCert { /** diff --git a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt index 13de39a..bccd372 100644 --- a/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/GenerateKey.kt @@ -10,6 +10,7 @@ import sop.Ready import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for generating OpenPGP keys. */ interface GenerateKey { /** diff --git a/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt index 941a9bf..1cc64ce 100644 --- a/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt +++ b/sop-java/src/main/kotlin/sop/operation/InlineDetach.kt @@ -10,6 +10,7 @@ import sop.ReadyWithResult import sop.Signatures import sop.exception.SOPGPException.BadData +/** Interface for detaching inline signatures from OpenPGP messages. */ interface InlineDetach { /** diff --git a/sop-java/src/main/kotlin/sop/operation/InlineSign.kt b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt index 11b5668..6855a61 100644 --- a/sop-java/src/main/kotlin/sop/operation/InlineSign.kt +++ b/sop-java/src/main/kotlin/sop/operation/InlineSign.kt @@ -10,6 +10,7 @@ import sop.Ready import sop.enums.InlineSignAs import sop.exception.SOPGPException.* +/** Interface for creating inline-signed OpenPGP messages. */ interface InlineSign : AbstractSign { /** diff --git a/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt index c16b269..a944957 100644 --- a/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt +++ b/sop-java/src/main/kotlin/sop/operation/InlineVerify.kt @@ -11,7 +11,7 @@ import sop.Verification import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.NoSignature -/** API for verification of inline-signed messages. */ +/** Interface for verification of inline-signed messages. */ interface InlineVerify : AbstractVerify { /** diff --git a/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt index 315faf2..0bed1f8 100644 --- a/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt +++ b/sop-java/src/main/kotlin/sop/operation/ListProfiles.kt @@ -6,7 +6,7 @@ package sop.operation import sop.Profile -/** Subcommand to list supported profiles of other subcommands. */ +/** Interface to list supported profiles of other subcommands. */ interface ListProfiles { /** diff --git a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt index f922490..20469cb 100644 --- a/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt +++ b/sop-java/src/main/kotlin/sop/operation/MergeCerts.kt @@ -7,21 +7,58 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.Ready -import sop.exception.SOPGPException +import sop.exception.SOPGPException.* +/** Interface for merging multiple copies of the same certificate into one. */ interface MergeCerts { - @Throws(SOPGPException.UnsupportedOption::class) fun noArmor(): MergeCerts + /** + * Disable ASCII armor for the output certificate. + * + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun noArmor(): MergeCerts - @Throws(SOPGPException.BadData::class, IOException::class) - fun updates(updateCerts: InputStream): MergeCerts + /** + * Provide updated copies of the base certificate. + * + * @param updateCerts input stream containing an updated copy of the base cert + * @return builder instance + * @throws BadData if the update cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun updates(updateCerts: InputStream): MergeCerts - @Throws(SOPGPException.BadData::class, IOException::class) + /** + * Provide updated copies of the base certificate. + * + * @param updateCerts byte array containing an updated copy of the base cert + * @return builder instance + * @throws BadData if the update cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun updates(updateCerts: ByteArray): MergeCerts = updates(updateCerts.inputStream()) - @Throws(SOPGPException.BadData::class, IOException::class) - fun baseCertificates(certs: InputStream): Ready + /** + * Provide the base certificate into which updates shall be merged. + * + * @param certs input stream containing the base OpenPGP certificate + * @return object to require the merged certificate from + * @throws BadData if the base certificate cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun baseCertificates(certs: InputStream): Ready - @Throws(SOPGPException.BadData::class, IOException::class) + /** + * Provide the base certificate into which updates shall be merged. + * + * @param certs byte array containing the base OpenPGP certificate + * @return object to require the merged certificate from + * @throws BadData if the base certificate cannot be read + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, IOException::class) fun baseCertificates(certs: ByteArray): Ready = baseCertificates(certs.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt index f3cbe5c..13c6712 100644 --- a/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/RevokeKey.kt @@ -4,12 +4,13 @@ package sop.operation +import java.io.IOException import java.io.InputStream import sop.Ready -import sop.exception.SOPGPException.PasswordNotHumanReadable -import sop.exception.SOPGPException.UnsupportedOption +import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for creating certificate revocations. */ interface RevokeKey { /** @@ -25,9 +26,18 @@ interface RevokeKey { * @param password password * @return builder instance * @throws UnsupportedOption if the implementation does not support key passwords - * @throws PasswordNotHumanReadable if the password is not human-readable */ - @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) + @Throws(UnsupportedOption::class) + fun withKeyPassword(password: CharArray): RevokeKey = withKeyPassword(password.concatToString()) + + /** + * Provide the decryption password for the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if the implementation does not support key passwords + */ + @Throws(UnsupportedOption::class) fun withKeyPassword(password: String): RevokeKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @@ -42,7 +52,27 @@ interface RevokeKey { @Throws(UnsupportedOption::class, PasswordNotHumanReadable::class) fun withKeyPassword(password: ByteArray): RevokeKey + /** + * Provide the key that you want to revoke. + * + * @param bytes byte array containing the OpenPGP key + * @return object to require the revocation certificate from + * @throws BadData if the key cannot be read + * @throws KeyIsProtected if the key is protected and cannot be unlocked + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, KeyIsProtected::class, IOException::class) fun keys(bytes: ByteArray): Ready = keys(bytes.inputStream()) + /** + * Provide the key that you want to revoke. + * + * @param keys input stream containing the OpenPGP key + * @return object to require the revocation certificate from + * @throws BadData if the key cannot be read + * @throws KeyIsProtected if the key is protected and cannot be unlocked + * @throws IOException if an IO error occurs + */ + @Throws(BadData::class, KeyIsProtected::class, IOException::class) fun keys(keys: InputStream): Ready } diff --git a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt index 1226ed5..13a4bdc 100644 --- a/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt +++ b/sop-java/src/main/kotlin/sop/operation/UpdateKey.kt @@ -7,9 +7,10 @@ package sop.operation import java.io.IOException import java.io.InputStream import sop.Ready -import sop.exception.SOPGPException +import sop.exception.SOPGPException.* import sop.util.UTF8Util +/** Interface for bringing an OpenPGP key up to date. */ interface UpdateKey { /** @@ -22,21 +23,39 @@ interface UpdateKey { /** * Allow key to be used for signing only. If this option is not present, the operation may add a * new, encryption-capable component key. + * + * @return builder instance + * @throws UnsupportedOption if this option is not supported */ - @Throws(SOPGPException.UnsupportedOption::class) fun signingOnly(): UpdateKey + @Throws(UnsupportedOption::class) fun signingOnly(): UpdateKey /** * Do not allow adding new capabilities to the key. If this option is not present, the operation * may add support for new capabilities to the key. + * + * @return builder instance + * @throws UnsupportedOption if this option is not supported */ - @Throws(SOPGPException.UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey + @Throws(UnsupportedOption::class) fun noAddedCapabilities(): UpdateKey /** * Provide a passphrase for unlocking the secret key. * * @param password password + * @return builder instance + * @throws UnsupportedOption if this option is not supported */ - @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + @Throws(UnsupportedOption::class) + fun withKeyPassword(password: CharArray): UpdateKey = withKeyPassword(password.concatToString()) + + /** + * Provide a passphrase for unlocking the secret key. + * + * @param password password + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun withKeyPassword(password: String): UpdateKey = withKeyPassword(password.toByteArray(UTF8Util.UTF8)) @@ -44,8 +63,11 @@ interface UpdateKey { * Provide a passphrase for unlocking the secret key. * * @param password password + * @return builder instance + * @throws PasswordNotHumanReadable if the password is not human-readable + * @throws UnsupportedOption if this option is not supported */ - @Throws(SOPGPException.PasswordNotHumanReadable::class, SOPGPException.UnsupportedOption::class) + @Throws(PasswordNotHumanReadable::class, UnsupportedOption::class) fun withKeyPassword(password: ByteArray): UpdateKey /** @@ -53,9 +75,12 @@ interface UpdateKey { * These certificates will be merged into the key. * * @param certs input stream of certificates + * @return builder instance + * @throws UnsupportedOption if this option is not supported + * @throws BadData if the certificate cannot be read + * @throws IOException if an IO error occurs */ - @Throws( - SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws(UnsupportedOption::class, BadData::class, IOException::class) fun mergeCerts(certs: InputStream): UpdateKey /** @@ -63,9 +88,12 @@ interface UpdateKey { * These certificates will be merged into the key. * * @param certs binary certificates + * @return builder instance + * @throws UnsupportedOption if this option is not supported + * @throws BadData if the certificate cannot be read + * @throws IOException if an IO error occurs */ - @Throws( - SOPGPException.UnsupportedOption::class, SOPGPException.BadData::class, IOException::class) + @Throws(UnsupportedOption::class, BadData::class, IOException::class) fun mergeCerts(certs: ByteArray): UpdateKey = mergeCerts(certs.inputStream()) /** @@ -73,12 +101,12 @@ interface UpdateKey { * * @param key input stream containing the key * @return handle to acquire the updated OpenPGP key from + * @throws BadData if the key cannot be read + * @throws IOException if an IO error occurs + * @throws KeyIsProtected if the key is passphrase protected and cannot be unlocked + * @throws PrimaryKeyBad if the primary key is bad (e.g. expired, too weak) */ - @Throws( - SOPGPException.BadData::class, - IOException::class, - SOPGPException.KeyIsProtected::class, - SOPGPException.PrimaryKeyBad::class) + @Throws(BadData::class, IOException::class, KeyIsProtected::class, PrimaryKeyBad::class) fun key(key: InputStream): Ready /** @@ -86,11 +114,11 @@ interface UpdateKey { * * @param key binary OpenPGP key * @return handle to acquire the updated OpenPGP key from + * @throws BadData if the key cannot be read + * @throws IOException if an IO error occurs + * @throws KeyIsProtected if the key is passphrase protected and cannot be unlocked + * @throws PrimaryKeyBad if the primary key is bad (e.g. expired, too weak) */ - @Throws( - SOPGPException.BadData::class, - IOException::class, - SOPGPException.KeyIsProtected::class, - SOPGPException.PrimaryKeyBad::class) + @Throws(BadData::class, IOException::class, KeyIsProtected::class, PrimaryKeyBad::class) fun key(key: ByteArray): Ready = key(key.inputStream()) } diff --git a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt index fe20fd4..971de25 100644 --- a/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt +++ b/sop-java/src/main/kotlin/sop/operation/ValidateUserId.kt @@ -7,9 +7,9 @@ package sop.operation import java.io.IOException import java.io.InputStream import java.util.* -import sop.exception.SOPGPException +import sop.exception.SOPGPException.* -/** Subcommand to validate UserIDs on certificates. */ +/** Interface to validate UserIDs on certificates. */ interface ValidateUserId { /** @@ -17,15 +17,16 @@ interface ValidateUserId { * e-mail address part of each correctly bound User ID. The rest of each correctly bound User ID * is ignored. * - * @return this + * @return builder instance + * @throws UnsupportedOption if this option is not supported */ - @Throws(SOPGPException.UnsupportedOption::class) fun addrSpecOnly(): ValidateUserId + @Throws(UnsupportedOption::class) fun addrSpecOnly(): ValidateUserId /** * Set the UserID to validate. To match only the email address, call [addrSpecOnly]. * * @param userId UserID or email address - * @return this + * @return builder instance */ fun userId(userId: String): ValidateUserId @@ -34,19 +35,22 @@ interface ValidateUserId { * if it was bound by an authoritative certificate. * * @param certs authoritative certificates - * @return this + * @return builder instance + * @throws BadData if the authority certificates cannot be read + * @throws IOException if an IO error occurs */ - @Throws(SOPGPException.BadData::class, IOException::class) - fun authorities(certs: InputStream): ValidateUserId + @Throws(BadData::class, IOException::class) fun authorities(certs: InputStream): ValidateUserId /** * Add certificates, which act as authorities. The [userId] is only considered correctly bound, * if it was bound by an authoritative certificate. * * @param certs authoritative certificates - * @return this + * @return builder instance + * @throws BadData if the authority certificates cannot be read + * @throws IOException if an IO error occurs */ - @Throws(SOPGPException.BadData::class, IOException::class) + @Throws(BadData::class, IOException::class) fun authorities(certs: ByteArray): ValidateUserId = authorities(certs.inputStream()) /** @@ -54,13 +58,12 @@ interface ValidateUserId { * * @param certs subject certificates * @return true if all subject certificates have a correct binding to the UserID. - * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws BadData if the subject certificates are malformed * @throws IOException if a parser exception happens - * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly - * bound UserID that matches [userId]. + * @throws CertUserIdNoMatch if any subject certificate does not have a correctly bound UserID + * that matches [userId]. */ - @Throws( - SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws(BadData::class, IOException::class, CertUserIdNoMatch::class) fun subjects(certs: InputStream): Boolean /** @@ -68,14 +71,20 @@ interface ValidateUserId { * * @param certs subject certificates * @return true if all subject certificates have a correct binding to the UserID. - * @throws SOPGPException.BadData if the subject certificates are malformed + * @throws BadData if the subject certificates are malformed * @throws IOException if a parser exception happens - * @throws SOPGPException.CertUserIdNoMatch if any subject certificate does not have a correctly - * bound UserID that matches [userId]. + * @throws CertUserIdNoMatch if any subject certificate does not have a correctly bound UserID + * that matches [userId]. */ - @Throws( - SOPGPException.BadData::class, IOException::class, SOPGPException.CertUserIdNoMatch::class) + @Throws(BadData::class, IOException::class, CertUserIdNoMatch::class) fun subjects(certs: ByteArray): Boolean = subjects(certs.inputStream()) - fun validateAt(date: Date): ValidateUserId + /** + * Provide a reference time for user-id validation. + * + * @param date reference time + * @return builder instance + * @throws UnsupportedOption if this option is not supported + */ + @Throws(UnsupportedOption::class) fun validateAt(date: Date): ValidateUserId } diff --git a/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt index b75e4a5..00a64aa 100644 --- a/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt +++ b/sop-java/src/main/kotlin/sop/operation/VerifySignatures.kt @@ -10,6 +10,7 @@ import sop.Verification import sop.exception.SOPGPException.BadData import sop.exception.SOPGPException.NoSignature +/** API handle for verifying signatures. */ interface VerifySignatures { /** diff --git a/sop-java/src/main/kotlin/sop/operation/Version.kt b/sop-java/src/main/kotlin/sop/operation/Version.kt index 6c8aa95..8a4c808 100644 --- a/sop-java/src/main/kotlin/sop/operation/Version.kt +++ b/sop-java/src/main/kotlin/sop/operation/Version.kt @@ -10,6 +10,7 @@ import java.util.* import kotlin.jvm.Throws import sop.exception.SOPGPException +/** Interface for acquiring version information about the SOP implementation. */ interface Version { /** From e680f3450a392aab2569fd97e6e993ba65fd63af Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Jul 2025 12:21:06 +0200 Subject: [PATCH 442/444] SOPV: Document since when operations are available --- sop-java/src/main/kotlin/sop/SOPV.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/sop-java/src/main/kotlin/sop/SOPV.kt b/sop-java/src/main/kotlin/sop/SOPV.kt index 27eb6e3..d483194 100644 --- a/sop-java/src/main/kotlin/sop/SOPV.kt +++ b/sop-java/src/main/kotlin/sop/SOPV.kt @@ -12,27 +12,41 @@ import sop.operation.Version /** Subset of [SOP] implementing only OpenPGP signature verification. */ interface SOPV { - /** Get information about the implementations name and version. */ + /** + * Get information about the implementations name and version. + * + * @since sopv 1.0 + */ fun version(): Version? /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. + * + * @since sopv 1.0 */ fun verify(): DetachedVerify? = detachedVerify() /** * Verify detached signatures. If you need to verify an inline-signed message, use * [inlineVerify] instead. + * + * @since sopv 1.0 */ fun detachedVerify(): DetachedVerify? /** * Verify signatures of an inline-signed message. If you need to verify detached signatures over * a message, use [detachedVerify] instead. + * + * @since sopv 1.0 */ fun inlineVerify(): InlineVerify? - /** Validate a UserID in an OpenPGP certificate. */ + /** + * Validate a UserID in an OpenPGP certificate. + * + * @since sopv 1.2 + */ fun validateUserId(): ValidateUserId? } From cc08b76b68a66bc8ef094dcaccbee5c561bfabe0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Jul 2025 14:09:06 +0200 Subject: [PATCH 443/444] Introduce sop-java-json-gson module --- settings.gradle | 3 +- sop-java-json-gson/README.md | 13 +++ sop-java-json-gson/build.gradle | 28 ++++++ .../src/main/kotlin/sop/GsonParser.kt | 23 +++++ .../src/main/kotlin/sop/GsonSerializer.kt | 16 ++++ .../kotlin/sop/GsonSerializerAndParserTest.kt | 96 +++++++++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 sop-java-json-gson/README.md create mode 100644 sop-java-json-gson/build.gradle create mode 100644 sop-java-json-gson/src/main/kotlin/sop/GsonParser.kt create mode 100644 sop-java-json-gson/src/main/kotlin/sop/GsonSerializer.kt create mode 100644 sop-java-json-gson/src/test/kotlin/sop/GsonSerializerAndParserTest.kt diff --git a/settings.gradle b/settings.gradle index 1cb66be..84dc381 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,5 +7,6 @@ rootProject.name = 'SOP-Java' include 'sop-java', 'sop-java-picocli', 'sop-java-testfixtures', - 'external-sop' + 'external-sop', + 'sop-java-json-gson' diff --git a/sop-java-json-gson/README.md b/sop-java-json-gson/README.md new file mode 100644 index 0000000..9feb8ff --- /dev/null +++ b/sop-java-json-gson/README.md @@ -0,0 +1,13 @@ + + +# SOP-Java-JSON-GSON + +## JSON Parsing VERIFICATION extension JSON using Gson + +Since revision 11, the SOP specification defines VERIFICATIONS extension JSON. + +This module implements the `JSONParser` and `JSONSerializer` interfaces using Googles Gson library. diff --git a/sop-java-json-gson/build.gradle b/sop-java-json-gson/build.gradle new file mode 100644 index 0000000..4105902 --- /dev/null +++ b/sop-java-json-gson/build.gradle @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +plugins { + id 'java-library' +} + +group 'org.pgpainless' + +repositories { + mavenCentral() +} + +dependencies { + implementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + implementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + runtimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + + implementation project(":sop-java") + api "org.slf4j:slf4j-api:$slf4jVersion" + testImplementation "ch.qos.logback:logback-classic:$logbackVersion" + + // @Nonnull, @Nullable... + implementation "com.google.code.findbugs:jsr305:$jsrVersion" + + api "com.google.code.gson:gson:$gsonVersion" +} diff --git a/sop-java-json-gson/src/main/kotlin/sop/GsonParser.kt b/sop-java-json-gson/src/main/kotlin/sop/GsonParser.kt new file mode 100644 index 0000000..06adecb --- /dev/null +++ b/sop-java-json-gson/src/main/kotlin/sop/GsonParser.kt @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import com.google.gson.reflect.TypeToken +import java.text.ParseException + +class GsonParser( + private val gson: Gson = Gson() +) : Verification.JSONParser { + + override fun parse(string: String): Verification.JSON { + try { + return gson.fromJson(string, object : TypeToken(){}.type) + } catch (e: JsonSyntaxException) { + throw ParseException(e.message, 0) + } + } +} \ No newline at end of file diff --git a/sop-java-json-gson/src/main/kotlin/sop/GsonSerializer.kt b/sop-java-json-gson/src/main/kotlin/sop/GsonSerializer.kt new file mode 100644 index 0000000..410fe49 --- /dev/null +++ b/sop-java-json-gson/src/main/kotlin/sop/GsonSerializer.kt @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import com.google.gson.Gson + +class GsonSerializer( + private val gson: Gson = Gson() +) : Verification.JSONSerializer { + + override fun serialize(json: Verification.JSON): String { + return gson.toJson(json) + } +} \ No newline at end of file diff --git a/sop-java-json-gson/src/test/kotlin/sop/GsonSerializerAndParserTest.kt b/sop-java-json-gson/src/test/kotlin/sop/GsonSerializerAndParserTest.kt new file mode 100644 index 0000000..9bbef14 --- /dev/null +++ b/sop-java-json-gson/src/test/kotlin/sop/GsonSerializerAndParserTest.kt @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.text.ParseException + +class GsonSerializerAndParserTest { + + private val serializer: GsonSerializer = GsonSerializer() + private val parser: GsonParser = GsonParser() + + @Test + fun simpleSingleTest() { + val before = Verification.JSON("/tmp/alice.pgp") + + val json = serializer.serialize(before) + assertEquals("{\"signers\":[\"/tmp/alice.pgp\"]}", json) + + val after = parser.parse(json) + + assertEquals(before, after) + } + + @Test + fun simpleListTest() { + val before = Verification.JSON(listOf("/tmp/alice.pgp", "/tmp/bob.asc")) + + val json = serializer.serialize(before) + assertEquals("{\"signers\":[\"/tmp/alice.pgp\",\"/tmp/bob.asc\"]}", json) + + val after = parser.parse(json) + + assertEquals(before, after) + } + + @Test + fun withCommentTest() { + val before = Verification.JSON( + listOf("/tmp/alice.pgp"), + "This is a comment.", + null) + + val json = serializer.serialize(before) + assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\"}", json) + + val after = parser.parse(json) + + assertEquals(before, after) + } + + @Test + fun withExtStringTest() { + val before = Verification.JSON( + listOf("/tmp/alice.pgp"), + "This is a comment.", + "This is an ext object string.") + + val json = serializer.serialize(before) + assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\",\"ext\":\"This is an ext object string.\"}", json) + + val after = parser.parse(json) + + assertEquals(before, after) + } + + @Test + fun withExtListTest() { + val before = Verification.JSON( + listOf("/tmp/alice.pgp"), + "This is a comment.", + listOf(1.0,2.0,3.0)) + + val json = serializer.serialize(before) + assertEquals("{\"signers\":[\"/tmp/alice.pgp\"],\"comment\":\"This is a comment.\",\"ext\":[1.0,2.0,3.0]}", json) + + val after = parser.parse(json) + + assertEquals(before, after) + } + + @Test + fun parseInvalidJSON() { + assertThrows { parser.parse("Invalid") } + } + + @Test + fun parseMalformedJSON() { + // Missing '}' + assertThrows { parser.parse("{\"signers\":[\"Alice\"]") } + } +} \ No newline at end of file From b28e234f2124f36a549eab238b1070390ac55dfc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 25 Jul 2025 14:09:57 +0200 Subject: [PATCH 444/444] ExternalTestSuite: Add license header --- .../test/java/sop/testsuite/external/ExternalTestSuite.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java b/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java index 6e991cf..aa0ae82 100644 --- a/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java +++ b/external-sop/src/test/java/sop/testsuite/external/ExternalTestSuite.java @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package sop.testsuite.external; import org.junit.platform.suite.api.IncludeClassNamePatterns;