diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/DateParser.java b/sop-java-picocli/src/main/java/sop/cli/picocli/DateParser.java deleted file mode 100644 index d2e2188..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/DateParser.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import java.util.Date; - -import sop.util.UTCUtil; - -public class DateParser { - - public static final Date BEGINNING_OF_TIME = new Date(0); - public static final Date END_OF_TIME = new Date(8640000000000000L); - - public static Date parseNotAfter(String notAfter) { - Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter); - if (date == null) { - Print.errln("Invalid date string supplied as value of --not-after."); - System.exit(1); - } - return date; - } - - public static Date parseNotBefore(String notBefore) { - Date date = notBefore.equals("now") ? new Date() : notBefore.equals("-") ? BEGINNING_OF_TIME : UTCUtil.parseUTCDate(notBefore); - if (date == null) { - Print.errln("Invalid date string supplied as value of --not-before."); - System.exit(1); - } - return date; - } -} diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java b/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java deleted file mode 100644 index 340eed7..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/FileUtil.java +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.IOException; - -import sop.exception.SOPGPException; -import sop.util.UTF8Util; - -public class FileUtil { - - private static final String ERROR_AMBIGUOUS = "File name '%s' is ambiguous. File with the same name exists on the filesystem."; - private static final String ERROR_ENV_FOUND = "Environment variable '%s' not set."; - private static final String ERROR_OUTPUT_EXISTS = "Output file '%s' already exists."; - private static final String ERROR_INPUT_NOT_EXIST = "File '%s' does not exist."; - private static final String ERROR_CANNOT_CREATE_FILE = "Output file '%s' cannot be created: %s"; - - public static final String PRFX_ENV = "@ENV:"; - public static final String PRFX_FD = "@FD:"; - - private static EnvironmentVariableResolver envResolver = System::getenv; - - public static void setEnvironmentVariableResolver(EnvironmentVariableResolver envResolver) { - if (envResolver == null) { - throw new NullPointerException("Variable envResolver cannot be null."); - } - FileUtil.envResolver = envResolver; - } - - 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 File getFile(String fileName) { - if (fileName == null) { - throw new NullPointerException("File name cannot be null."); - } - - if (fileName.startsWith(PRFX_ENV)) { - - if (new File(fileName).exists()) { - throw new SOPGPException.AmbiguousInput(String.format(ERROR_AMBIGUOUS, fileName)); - } - - String envName = fileName.substring(PRFX_ENV.length()); - String envValue = envResolver.resolveEnvironmentVariable(envName); - if (envValue == null) { - throw new IllegalArgumentException(String.format(ERROR_ENV_FOUND, envName)); - } - return new File(envValue); - } else if (fileName.startsWith(PRFX_FD)) { - - if (new File(fileName).exists()) { - throw new SOPGPException.AmbiguousInput(String.format(ERROR_AMBIGUOUS, fileName)); - } - - throw new IllegalArgumentException("File descriptors not supported."); - } - - return new File(fileName); - } - - public static FileInputStream getFileInputStream(String fileName) { - File file = getFile(fileName); - try { - FileInputStream inputStream = new FileInputStream(file); - return inputStream; - } catch (FileNotFoundException e) { - throw new SOPGPException.MissingInput(String.format(ERROR_INPUT_NOT_EXIST, fileName), e); - } - } - - public static File createNewFileOrThrow(File file) throws IOException { - if (file == null) { - throw new NullPointerException("File cannot be null."); - } - - try { - if (!file.createNewFile()) { - throw new SOPGPException.OutputExists(String.format(ERROR_OUTPUT_EXISTS, file.getAbsolutePath())); - } - } catch (IOException e) { - throw new IOException(String.format(ERROR_CANNOT_CREATE_FILE, file.getAbsolutePath(), e.getMessage())); - } - return file; - } - - 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(); - } - } -} 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 bbd8b97..c0cabea 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 @@ -10,9 +10,11 @@ public class SOPExecutionExceptionHandler implements CommandLine.IExecutionExcep @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) { 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 1193edf..750cfa8 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 @@ -20,9 +20,13 @@ import sop.cli.picocli.commands.SignCmd; import sop.cli.picocli.commands.VerifyCmd; import sop.cli.picocli.commands.VersionCmd; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + @CommandLine.Command( name = "sop", - description = "Stateless OpenPGP Protocol", + resourceBundle = "sop", exitCodeOnInvalidInput = 69, subcommands = { CommandLine.HelpCommand.class, @@ -39,33 +43,12 @@ import sop.cli.picocli.commands.VersionCmd; InlineVerifyCmd.class, VersionCmd.class, AutoComplete.GenerateCompletion.class - }, - exitCodeListHeading = "Exit Codes:%n", - exitCodeList = { - " 0:Successful program execution", - " 1:Generic program error", - " 3:Verification requested but no verifiable signature found", - "13:Unsupported asymmetric algorithm", - "17:Certificate is not encryption capable", - "19:Usage error: Missing argument", - "23:Incomplete verification instructions", - "29:Unable to decrypt", - "31:Password is not human-readable", - "37:Unsupported Option", - "41:Invalid data or data of wrong type encountered", - "53:Non-text input received where text was expected", - "59:Output file already exists", - "61:Input file does not exist", - "67:Key is password protected", - "69:Unsupported subcommand", - "71:Unsupported special prefix (e.g. \"@env/@fd\") of indirect parameter", - "73:Ambiguous input (a filename matching the designator already exists)", - "79:Key is not signing capable" } ) public class SopCLI { // Singleton static SOP SOP_INSTANCE; + static ResourceBundle cliMsg = ResourceBundle.getBundle("sop"); public static String EXECUTABLE_NAME = "sop"; @@ -77,6 +60,13 @@ public class SopCLI { } public static int execute(String[] args) { + + // Set locale + new CommandLine(new InitLocale()).parseArgs(args); + + cliMsg = ResourceBundle.getBundle("sop"); + + // Prepare CLI CommandLine cmd = new CommandLine(SopCLI.class); // Hide generate-completion command CommandLine gen = cmd.getSubcommands().get("generate-completion"); @@ -92,7 +82,8 @@ public class SopCLI { public static SOP getSop() { if (SOP_INSTANCE == null) { - throw new IllegalStateException("No SOP backend set."); + String errorMsg = cliMsg.getString("sop.error.runtime.no_backend_set"); + throw new IllegalStateException(errorMsg); } return SOP_INSTANCE; } @@ -101,3 +92,18 @@ public class SopCLI { 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/commands/AbstractSopCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/AbstractSopCmd.java index afaf521..ea73fc5 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 @@ -5,43 +5,225 @@ 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.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Collection; +import java.util.Date; +import java.util.Locale; +import java.util.ResourceBundle; public abstract class AbstractSopCmd implements Runnable { - static final String ERROR_UNSUPPORTED_OPTION = "Option '%s' is not supported."; - static final String ERROR_FILE_NOT_EXIST = "File '%s' does not exist."; - static final String ERROR_OUTPUT_OF_OPTION_EXISTS = "Target %s of option %s already exists."; + 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); + } - void throwIfOutputExists(File outputFile, String optionName) { - if (outputFile == null) { + 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); + + protected final ResourceBundle messages; + protected EnvironmentVariableResolver envResolver = System::getenv; + + public AbstractSopCmd() { + this(Locale.getDefault()); + } + + public AbstractSopCmd(@Nonnull Locale locale) { + messages = ResourceBundle.getBundle("sop", locale); + } + + void throwIfOutputExists(String output) { + if (output == null) { return; } + File outputFile = new File(output); if (outputFile.exists()) { - throw new SOPGPException.OutputExists(String.format(ERROR_OUTPUT_OF_OPTION_EXISTS, outputFile.getAbsolutePath(), optionName)); + 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) { - throw new SOPGPException.MissingArg(argName + " is required."); + String errorMsg = getMsg("sop.error.usage.argument_required", argName); + throw new SOPGPException.MissingArg(errorMsg); } } void throwIfEmptyParameters(Collection arg, String parmName) { if (arg.isEmpty()) { - throw new SOPGPException.MissingArg("Parameter '" + parmName + "' is required."); + String errorMsg = getMsg("sop.error.usage.parameter_required", parmName); + throw new SOPGPException.MissingArg(errorMsg); } } T throwIfUnsupportedSubcommand(T subcommand, String subcommandName) { if (subcommand == null) { - throw new SOPGPException.UnsupportedSubcommand("Command '" + subcommandName + "' is not supported."); + 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); + } + + String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); + throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); + + } 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); + } + + if (trimmed.startsWith(PRFX_FD)) { + String errorMsg = getMsg("sop.error.indirect_data_type.designator_fd_not_supported"); + throw new SOPGPException.UnsupportedSpecialPrefix(errorMsg); + } + + 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 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) { + Date date = notAfter.equals("now") ? new Date() : notAfter.equals("-") ? END_OF_TIME : UTCUtil.parseUTCDate(notAfter); + if (date == null) { + 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) { + String errorMsg = getMsg("sop.error.input.malformed_not_before"); + throw new IllegalArgumentException(errorMsg); + } + return date; + } + } 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 7340b5c..891b974 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 @@ -4,22 +4,23 @@ package sop.cli.picocli.commands; -import java.io.IOException; - import picocli.CommandLine; import sop.Ready; -import sop.cli.picocli.Print; 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", - description = "Add ASCII Armor to standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class ArmorCmd extends AbstractSopCmd { - @CommandLine.Option(names = {"--label"}, description = "Label to be used in the header and tail of the armoring.", paramLabel = "{auto|sig|key|cert|message}") + @CommandLine.Option(names = {"--label"}, + descriptionKey = "sop.armor.usage.option.label", + paramLabel = "{auto|sig|key|cert|message}") ArmorLabel label; @Override @@ -32,8 +33,8 @@ public class ArmorCmd extends AbstractSopCmd { try { armor.label(label); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Armor labels not supported."); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--label"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } @@ -41,13 +42,10 @@ public class ArmorCmd extends AbstractSopCmd { Ready ready = armor.data(System.in); ready.writeTo(System.out); } catch (SOPGPException.BadData badData) { - Print.errln("Bad data."); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); + throw new SOPGPException.BadData(errorMsg, badData); } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); + throw new RuntimeException(e); } } } 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 36d6ced..1bca4d7 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 @@ -4,16 +4,15 @@ package sop.cli.picocli.commands; -import java.io.IOException; - import picocli.CommandLine; -import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; import sop.operation.Dearmor; +import java.io.IOException; + @CommandLine.Command(name = "dearmor", - description = "Remove ASCII Armor from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DearmorCmd extends AbstractSopCmd { @@ -26,13 +25,10 @@ public class DearmorCmd extends AbstractSopCmd { dearmor.data(System.in) .writeTo(System.out); } catch (SOPGPException.BadData e) { - Print.errln("Bad data."); - Print.trace(e); - System.exit(e.getExitCode()); + String errorMsg = getMsg("sop.error.input.stdin_not_openpgp_data"); + throw new SOPGPException.BadData(errorMsg, e); } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); + throw new RuntimeException(e); } } } 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 968988d..5a9b4f5 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 @@ -4,31 +4,27 @@ package sop.cli.picocli.commands; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; +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 sop.util.HexUtil; + 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; import java.util.regex.Pattern; -import picocli.CommandLine; -import sop.DecryptionResult; -import sop.ReadyWithResult; -import sop.SessionKey; -import sop.Verification; -import sop.cli.picocli.DateParser; -import sop.cli.picocli.FileUtil; -import sop.cli.picocli.SopCLI; -import sop.exception.SOPGPException; -import sop.operation.Decrypt; -import sop.util.HexUtil; - @CommandLine.Command(name = "decrypt", - description = "Decrypt a message from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class DecryptCmd extends AbstractSopCmd { @@ -44,54 +40,49 @@ public class DecryptCmd extends AbstractSopCmd { @CommandLine.Option( names = {OPT_SESSION_KEY_OUT}, - description = "Can be used to learn the session key on successful decryption", + descriptionKey = "sop.decrypt.usage.option.session_key_out", paramLabel = "SESSIONKEY") - File sessionKeyOut; + String sessionKeyOut; @CommandLine.Option( names = {OPT_WITH_SESSION_KEY}, - description = "Provide a session key file. Enables decryption of the \"CIPHERTEXT\" using the session key directly against the \"SEIPD\" packet", + descriptionKey = "sop.decrypt.usage.option.with_session_key", paramLabel = "SESSIONKEY") List withSessionKey = new ArrayList<>(); @CommandLine.Option( names = {OPT_WITH_PASSWORD}, - description = "Provide a password file. Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"", + descriptionKey = "sop.decrypt.usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = {OPT_VERIFY_OUT}, - description = "Produces signature verification status to the designated file", + descriptionKey = "sop.decrypt.usage.option.verify_out", paramLabel = "VERIFICATIONS") - File verifyOut; + String verifyOut; @CommandLine.Option(names = {OPT_VERIFY_WITH}, - description = "Certificates whose signatures would be acceptable for signatures over this message", + descriptionKey = "sop.decrypt.usage.option.certs", paramLabel = "CERT") - List certs = new ArrayList<>(); + List certs = new ArrayList<>(); @CommandLine.Option(names = {OPT_NOT_BEFORE}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to beginning of time (\"-\").", + descriptionKey = "sop.decrypt.usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {OPT_NOT_AFTER}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to current system time (\"now\").\n" + - "Accepts special value \"-\" for end of time.", + descriptionKey = "sop.decrypt.usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @CommandLine.Parameters(index = "0..*", - description = "Secret keys to attempt decryption with", + descriptionKey = "sop.decrypt.usage.param.keys", paramLabel = "KEY") - List keys = new ArrayList<>(); + List keys = new ArrayList<>(); @CommandLine.Option(names = {OPT_WITH_KEY_PASSWORD}, - description = "Provide indirect file type pointing at passphrase(s) for secret key(s)", + descriptionKey = "sop.decrypt.usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); @@ -100,8 +91,8 @@ public class DecryptCmd extends AbstractSopCmd { Decrypt decrypt = throwIfUnsupportedSubcommand( SopCLI.getSop().decrypt(), "decrypt"); - throwIfOutputExists(verifyOut, OPT_VERIFY_OUT); - throwIfOutputExists(sessionKeyOut, OPT_SESSION_KEY_OUT); + throwIfOutputExists(verifyOut); + throwIfOutputExists(sessionKeyOut); setNotAfter(notAfter, decrypt); setNotBefore(notBefore, decrypt); @@ -112,8 +103,8 @@ public class DecryptCmd extends AbstractSopCmd { setDecryptWith(keys, decrypt); if (verifyOut != null && certs.isEmpty()) { - String errorMessage = "Option %s is requested, but no option %s was provided."; - throw new SOPGPException.IncompleteVerification(String.format(errorMessage, OPT_VERIFY_OUT, OPT_VERIFY_WITH)); + String errorMsg = getMsg("sop.error.usage.option_requires_other_option", OPT_VERIFY_OUT, OPT_VERIFY_WITH); + throw new SOPGPException.IncompleteVerification(errorMsg); } try { @@ -122,7 +113,8 @@ public class DecryptCmd extends AbstractSopCmd { writeSessionKeyOut(result); writeVerifyOut(result); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("No valid OpenPGP message found on Standard Input.", badData); + String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); + throw new SOPGPException.BadData(errorMsg, badData); } catch (IOException ioException) { throw new RuntimeException(ioException); } @@ -130,9 +122,8 @@ public class DecryptCmd extends AbstractSopCmd { private void writeVerifyOut(DecryptionResult result) throws IOException { if (verifyOut != null) { - FileUtil.createNewFileOrThrow(verifyOut); - try (FileOutputStream outputStream = new FileOutputStream(verifyOut)) { - PrintWriter writer = new PrintWriter(outputStream); + try (OutputStream fileOut = getOutput(verifyOut)) { + PrintWriter writer = new PrintWriter(fileOut); for (Verification verification : result.getVerifications()) { // CHECKSTYLE:OFF writer.println(verification.toString()); @@ -145,11 +136,9 @@ public class DecryptCmd extends AbstractSopCmd { private void writeSessionKeyOut(DecryptionResult result) throws IOException { if (sessionKeyOut != null) { - FileUtil.createNewFileOrThrow(sessionKeyOut); - - try (FileOutputStream outputStream = new FileOutputStream(sessionKeyOut)) { + try (OutputStream outputStream = getOutput(sessionKeyOut)) { if (!result.getSessionKey().isPresent()) { - String errorMsg = "Session key not extracted. Possibly the feature %s is not supported."; + 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(); @@ -160,30 +149,29 @@ public class DecryptCmd extends AbstractSopCmd { } } - private void setDecryptWith(List keys, Decrypt decrypt) { - for (File key : keys) { - try (FileInputStream keyIn = new FileInputStream(key)) { + private void setDecryptWith(List keys, Decrypt decrypt) { + for (String key : keys) { + try (InputStream keyIn = getInput(key)) { decrypt.withKey(keyIn); } catch (SOPGPException.KeyIsProtected keyIsProtected) { - throw new SOPGPException.KeyIsProtected("Key in file " + key.getAbsolutePath() + " is password protected.", keyIsProtected); + String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", key); + throw new SOPGPException.KeyIsProtected(errorMsg, keyIsProtected); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("File " + key.getAbsolutePath() + " does not contain a private key.", badData); - } catch (FileNotFoundException e) { - throw new SOPGPException.MissingInput(String.format(ERROR_FILE_NOT_EXIST, key.getAbsolutePath()), e); + 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 (File cert : certs) { - try (FileInputStream certIn = new FileInputStream(cert)) { + private void setVerifyWith(List certs, Decrypt decrypt) { + for (String cert : certs) { + try (InputStream certIn = getInput(cert)) { decrypt.verifyWithCert(certIn); - } catch (FileNotFoundException e) { - throw new SOPGPException.MissingInput(String.format(ERROR_FILE_NOT_EXIST, cert.getAbsolutePath()), e); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("File " + cert.getAbsolutePath() + " does not contain a valid certificate.", badData); + String errorMsg = getMsg("sop.error.input.not_a_certificate", cert); + throw new SOPGPException.BadData(errorMsg, badData); } catch (IOException ioException) { throw new RuntimeException(ioException); } @@ -195,12 +183,13 @@ public class DecryptCmd extends AbstractSopCmd { for (String sessionKeyFile : withSessionKey) { String sessionKey; try { - sessionKey = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(sessionKeyFile)); + sessionKey = stringFromInputStream(getInput(sessionKeyFile)); } catch (IOException e) { throw new RuntimeException(e); } if (!sessionKeyPattern.matcher(sessionKey).matches()) { - throw new IllegalArgumentException("Session keys are expected in the format 'ALGONUM:HEXKEY'."); + String errorMsg = getMsg("sop.error.input.malformed_session_key"); + throw new IllegalArgumentException(errorMsg); } String[] split = sessionKey.split(":"); byte algorithm = (byte) Integer.parseInt(split[0]); @@ -209,7 +198,8 @@ public class DecryptCmd extends AbstractSopCmd { try { decrypt.withSessionKey(new SessionKey(algorithm, key)); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_SESSION_KEY), unsupportedOption); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", OPT_WITH_SESSION_KEY); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } } @@ -217,10 +207,12 @@ public class DecryptCmd extends AbstractSopCmd { private void setWithPasswords(List withPassword, Decrypt decrypt) { for (String passwordFile : withPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile)); + String password = stringFromInputStream(getInput(passwordFile)); decrypt.withPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_PASSWORD), 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); } @@ -230,10 +222,12 @@ public class DecryptCmd extends AbstractSopCmd { private void setWithKeyPassword(List withKeyPassword, Decrypt decrypt) { for (String passwordFile : withKeyPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile)); + String password = stringFromInputStream(getInput(passwordFile)); decrypt.withKeyPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_KEY_PASSWORD), 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); } @@ -241,20 +235,22 @@ public class DecryptCmd extends AbstractSopCmd { } private void setNotAfter(String notAfter, Decrypt decrypt) { - Date notAfterDate = DateParser.parseNotAfter(notAfter); + Date notAfterDate = parseNotAfter(notAfter); try { decrypt.verifyNotAfter(notAfterDate); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_NOT_AFTER), 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 = DateParser.parseNotBefore(notBefore); + Date notBeforeDate = parseNotBefore(notBefore); try { decrypt.verifyNotBefore(notBeforeDate); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_NOT_BEFORE), 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/java/sop/cli/picocli/commands/EncryptCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/EncryptCmd.java index 1535f99..3af1bb1 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 @@ -4,55 +4,52 @@ package sop.cli.picocli.commands; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import picocli.CommandLine; import sop.Ready; -import sop.cli.picocli.FileUtil; 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", - description = "Encrypt a message from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class EncryptCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.encrypt.usage.option.armor", negatable = true) boolean armor = true; @CommandLine.Option(names = {"--as"}, - description = "Type of the input data. Defaults to 'binary'", + descriptionKey = "sop.encrypt.usage.option.type", paramLabel = "{binary|text}") EncryptAs type; @CommandLine.Option(names = "--with-password", - description = "Encrypt the message with a password provided by the given password file", + descriptionKey = "sop.encrypt.usage.option.with_password", paramLabel = "PASSWORD") List withPassword = new ArrayList<>(); @CommandLine.Option(names = "--sign-with", - description = "Sign the output with a private key", + descriptionKey = "sop.encrypt.usage.option.sign_with", paramLabel = "KEY") - List signWith = new ArrayList<>(); + List signWith = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - description = "Provide indirect file type pointing at passphrase(s) for secret key(s)", + descriptionKey = "sop.encrypt.usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); - @CommandLine.Parameters(description = "Certificates the message gets encrypted to", + @CommandLine.Parameters(descriptionKey = "sop.encrypt.usage.param.certs", index = "0..*", paramLabel = "CERTS") - List certs = new ArrayList<>(); + List certs = new ArrayList<>(); @Override public void run() { @@ -63,20 +60,24 @@ public class EncryptCmd extends AbstractSopCmd { try { encrypt.mode(type); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption("Unsupported option '--as'.", unsupportedOption); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } if (withPassword.isEmpty() && certs.isEmpty()) { - throw new SOPGPException.MissingArg("At least one password file or cert file required for encryption."); + String errorMsg = getMsg("sop.error.usage.password_or_cert_required"); + throw new SOPGPException.MissingArg(errorMsg); } for (String passwordFileName : withPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFileName)); + String password = stringFromInputStream(getInput(passwordFileName)); encrypt.withPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption("Unsupported option '--with-password'.", 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); } @@ -84,46 +85,51 @@ public class EncryptCmd extends AbstractSopCmd { for (String passwordFileName : withKeyPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFileName)); + String password = stringFromInputStream(getInput(passwordFileName)); encrypt.withKeyPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption("Unsupported option '--with-key-password'.", 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 (File keyFile : signWith) { - try (FileInputStream keyIn = new FileInputStream(keyFile)) { + for (String keyInput : signWith) { + try (InputStream keyIn = getInput(keyInput)) { encrypt.signWith(keyIn); - } catch (FileNotFoundException e) { - throw new SOPGPException.MissingInput("Key file " + keyFile.getAbsolutePath() + " not found.", e); } catch (IOException e) { throw new RuntimeException(e); } catch (SOPGPException.KeyIsProtected keyIsProtected) { - throw new SOPGPException.KeyIsProtected("Key from " + keyFile.getAbsolutePath() + " is password protected.", keyIsProtected); + String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput); + throw new SOPGPException.KeyIsProtected(errorMsg, keyIsProtected); } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - throw new SOPGPException.UnsupportedAsymmetricAlgo("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo); + String errorMsg = getMsg("sop.error.runtime.key_uses_unsupported_asymmetric_algorithm", keyInput); + throw new SOPGPException.UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo); } catch (SOPGPException.KeyCannotSign keyCannotSign) { - throw new SOPGPException.KeyCannotSign("Key from " + keyFile.getAbsolutePath() + " cannot sign.", keyCannotSign); + String errorMsg = getMsg("sop.error.runtime.key_cannot_sign", keyInput); + throw new SOPGPException.KeyCannotSign(errorMsg, keyCannotSign); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.", badData); + String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); + throw new SOPGPException.BadData(errorMsg, badData); } } - for (File certFile : certs) { - try (FileInputStream certIn = new FileInputStream(certFile)) { + for (String certInput : certs) { + try (InputStream certIn = getInput(certInput)) { encrypt.withCert(certIn); - } catch (FileNotFoundException e) { - throw new SOPGPException.MissingInput("Certificate file " + certFile.getAbsolutePath() + " not found.", e); } catch (IOException e) { throw new RuntimeException(e); } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - throw new SOPGPException.UnsupportedAsymmetricAlgo("Certificate from " + certFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo); + String errorMsg = getMsg("sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm", certInput); + throw new SOPGPException.UnsupportedAsymmetricAlgo(errorMsg, unsupportedAsymmetricAlgo); } catch (SOPGPException.CertCannotEncrypt certCannotEncrypt) { - throw new SOPGPException.CertCannotEncrypt("Certificate from " + certFile.getAbsolutePath() + " is not capable of encryption.", certCannotEncrypt); + String errorMsg = getMsg("sop.error.runtime.cert_cannot_encrypt", certInput); + throw new SOPGPException.CertCannotEncrypt(errorMsg, certCannotEncrypt); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("Certificate file " + certFile.getAbsolutePath() + " does not contain a valid OpenPGP certificate.", badData); + String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); + throw new SOPGPException.BadData(errorMsg, badData); } } 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 8502d19..960a263 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", - description = "Extract a public key certificate from a secret key from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class ExtractCertCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.extract-cert.usage.option.armor", negatable = true) boolean armor = true; @@ -37,7 +37,8 @@ public class ExtractCertCmd extends AbstractSopCmd { } catch (IOException e) { throw new RuntimeException(e); } catch (SOPGPException.BadData badData) { - throw new SOPGPException.BadData("Standard Input does not contain valid OpenPGP private key material.", 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/java/sop/cli/picocli/commands/GenerateKeyCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/GenerateKeyCmd.java index 06df864..fbf415c 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 @@ -4,33 +4,31 @@ package sop.cli.picocli.commands; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import picocli.CommandLine; import sop.Ready; -import sop.cli.picocli.FileUtil; -import sop.cli.picocli.Print; 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", - description = "Generate a secret key", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class GenerateKeyCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.generate-key.usage.option.armor", negatable = true) boolean armor = true; - @CommandLine.Parameters(description = "User-ID, eg. \"Alice \"") + @CommandLine.Parameters(descriptionKey = "sop.generate-key.usage.option.user_id") List userId = new ArrayList<>(); @CommandLine.Option(names = "--with-key-password", - description = "Indirect file type pointing to file containing password to protect the key", + descriptionKey = "sop.generate-key.usage.option.with_key_password", paramLabel = "PASSWORD") String withKeyPassword; @@ -49,10 +47,11 @@ public class GenerateKeyCmd extends AbstractSopCmd { if (withKeyPassword != null) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(withKeyPassword)); + String password = stringFromInputStream(getInput(withKeyPassword)); generateKey.withKeyPassword(password); } catch (SOPGPException.UnsupportedOption e) { - throw new SOPGPException.UnsupportedOption("Option '--with-key-password' is not supported."); + 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); } @@ -61,18 +60,8 @@ public class GenerateKeyCmd extends AbstractSopCmd { try { Ready ready = generateKey.generate(); ready.writeTo(System.out); - } catch (SOPGPException.MissingArg missingArg) { - Print.errln("Missing argument."); - Print.trace(missingArg); - System.exit(missingArg.getExitCode()); - } catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) { - Print.errln("Unsupported asymmetric algorithm."); - Print.trace(unsupportedAsymmetricAlgo); - System.exit(unsupportedAsymmetricAlgo.getExitCode()); } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); + throw new RuntimeException(e); } } } 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 be7b85c..bcd269d 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 @@ -4,29 +4,28 @@ package sop.cli.picocli.commands; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - 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", - description = "Split a clearsigned message", + resourceBundle = "sop", exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE) public class InlineDetachCmd extends AbstractSopCmd { @CommandLine.Option( names = {"--signatures-out"}, - description = "Destination to which a detached signatures block will be written", + descriptionKey = "sop.inline-detach.usage.option.signatures_out", paramLabel = "SIGNATURES") - File signaturesOut; + String signaturesOut; @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.inline-detach.usage.option.armor", negatable = true) boolean armor = true; @@ -35,18 +34,17 @@ public class InlineDetachCmd extends AbstractSopCmd { InlineDetach inlineDetach = throwIfUnsupportedSubcommand( SopCLI.getSop().inlineDetach(), "inline-detach"); - throwIfOutputExists(signaturesOut, "--signatures-out"); + throwIfOutputExists(signaturesOut); throwIfMissingArg(signaturesOut, "--signatures-out"); if (!armor) { inlineDetach.noArmor(); } - try { + try (OutputStream outputStream = getOutput(signaturesOut)) { Signatures signatures = inlineDetach .message(System.in).writeTo(System.out); - signaturesOut.createNewFile(); - signatures.writeTo(new FileOutputStream(signaturesOut)); + signatures.writeTo(outputStream); } catch (IOException e) { throw new RuntimeException(e); } 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 b7fafe6..739bbf6 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 @@ -8,99 +8,90 @@ import picocli.CommandLine; import sop.MicAlg; import sop.ReadyWithResult; import sop.SigningResult; -import sop.cli.picocli.FileUtil; -import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; import sop.operation.InlineSign; -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.util.ArrayList; import java.util.List; @CommandLine.Command(name = "inline-sign", - description = "Create an inline-signed message from data on standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class InlineSignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.inline-sign.usage.option.armor", negatable = true) boolean armor = true; - @CommandLine.Option(names = "--as", description = "Defaults to 'binary'. If '--as=text' and the input data is not valid UTF-8, inline-sign fails with return code 53.", + @CommandLine.Option(names = "--as", + descriptionKey = "sop.inline-sign.usage.option.as", paramLabel = "{binary|text|cleartextsigned}") InlineSignAs type; - @CommandLine.Parameters(description = "Secret keys used for signing", + @CommandLine.Parameters(descriptionKey = "sop.inline-sign.usage.parameter.keys", paramLabel = "KEYS") - List secretKeyFile = new ArrayList<>(); + List secretKeyFile = new ArrayList<>(); - @CommandLine.Option(names = "--with-key-password", description = "Password(s) to unlock the secret key(s) with", + @CommandLine.Option(names = "--with-key-password", + descriptionKey = "sop.inline-sign.usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); - @CommandLine.Option(names = "--micalg-out", description = "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)", + @CommandLine.Option(names = "--micalg-out", + descriptionKey = "sop.inline-sign.usage.option.micalg", paramLabel = "MICALG") - File micAlgOut; + String micAlgOut; @Override public void run() { InlineSign inlineSign = throwIfUnsupportedSubcommand( SopCLI.getSop().inlineSign(), "inline-sign"); - throwIfOutputExists(micAlgOut, "--micalg-out"); + throwIfOutputExists(micAlgOut); if (type != null) { try { inlineSign.mode(type); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--as'"); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } if (secretKeyFile.isEmpty()) { - Print.errln("Missing required parameter 'KEYS'."); - System.exit(19); + String errorMsg = getMsg("sop.error.usage.parameter_required", "KEYS"); + throw new SOPGPException.MissingArg(errorMsg); } for (String passwordFile : withKeyPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile)); + String password = stringFromInputStream(getInput(passwordFile)); inlineSign.withKeyPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-key-password"), 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 (File keyFile : secretKeyFile) { - try (FileInputStream keyIn = new FileInputStream(keyFile)) { + for (String keyInput : secretKeyFile) { + try (InputStream keyIn = getInput(keyInput)) { inlineSign.key(keyIn); - } catch (FileNotFoundException e) { - Print.errln("File " + keyFile.getAbsolutePath() + " does not exist."); - Print.trace(e); - System.exit(1); } catch (IOException e) { - Print.errln("Cannot access file " + keyFile.getAbsolutePath()); - Print.trace(e); - System.exit(1); + throw new RuntimeException(e); } catch (SOPGPException.KeyIsProtected e) { - Print.errln("Key " + keyFile.getName() + " is password protected."); - Print.trace(e); - System.exit(1); + String errorMsg = getMsg("sop.error.runtime.cannot_unlock_key", keyInput); + throw new SOPGPException.KeyIsProtected(errorMsg, e); } catch (SOPGPException.BadData badData) { - Print.errln("Bad data in key file " + keyFile.getAbsolutePath() + ":"); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); + throw new SOPGPException.BadData(errorMsg, badData); } } @@ -115,19 +106,12 @@ public class InlineSignCmd extends AbstractSopCmd { MicAlg micAlg = result.getMicAlg(); if (micAlgOut != null) { // Write micalg out - micAlgOut.createNewFile(); - FileOutputStream micAlgOutStream = new FileOutputStream(micAlgOut); + OutputStream micAlgOutStream = getOutput(micAlgOut); micAlg.writeTo(micAlgOutStream); micAlgOutStream.close(); } } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); - } catch (SOPGPException.ExpectedText expectedText) { - Print.errln("Expected text input, but got binary data."); - Print.trace(expectedText); - System.exit(expectedText.getExitCode()); + throw new RuntimeException(e); } } } 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 4d22dbf..249d8a1 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 @@ -7,91 +7,76 @@ package sop.cli.picocli.commands; import picocli.CommandLine; import sop.ReadyWithResult; import sop.Verification; -import sop.cli.picocli.DateParser; -import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; import sop.operation.InlineVerify; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; 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", - description = "Verify inline-signed data from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class InlineVerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(arity = "1..*", - description = "Public key certificates", + descriptionKey = "sop.inline-verify.usage.parameter.certs", paramLabel = "CERT") - List certificates = new ArrayList<>(); + List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to beginning of time (\"-\").", + descriptionKey = "sop.inline-verify.usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to current system time (\"now\").\n" + - "Accepts special value \"-\" for end of time.", + descriptionKey = "sop.inline-verify.usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @CommandLine.Option(names = "--verifications-out", - description = "File to write details over successful verifications to") - File verificationsOut; + descriptionKey = "sop.inline-verify.usage.option.verifications_out") + String verificationsOut; @Override public void run() { InlineVerify inlineVerify = throwIfUnsupportedSubcommand( SopCLI.getSop().inlineVerify(), "inline-verify"); - throwIfOutputExists(verificationsOut, "--verifications-out"); + throwIfOutputExists(verificationsOut); if (notAfter != null) { try { - inlineVerify.notAfter(DateParser.parseNotAfter(notAfter)); + inlineVerify.notAfter(parseNotAfter(notAfter)); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--not-after'."); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } if (notBefore != null) { try { - inlineVerify.notBefore(DateParser.parseNotBefore(notBefore)); + inlineVerify.notBefore(parseNotBefore(notBefore)); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--not-before'."); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } - for (File certFile : certificates) { - try (FileInputStream certIn = new FileInputStream(certFile)) { + for (String certInput : certificates) { + try (InputStream certIn = getInput(certInput)) { inlineVerify.cert(certIn); - } catch (FileNotFoundException fileNotFoundException) { - Print.errln("Certificate file " + certFile.getAbsolutePath() + " not found."); - - Print.trace(fileNotFoundException); - System.exit(1); } catch (IOException ioException) { - Print.errln("IO Error."); - Print.trace(ioException); - System.exit(1); + 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) { - Print.errln("Certificate file " + certFile.getAbsolutePath() + " appears to not contain a valid OpenPGP certificate."); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); + throw new SOPGPException.BadData(errorMsg, badData); } } @@ -100,23 +85,18 @@ public class InlineVerifyCmd extends AbstractSopCmd { ReadyWithResult> ready = inlineVerify.data(System.in); verifications = ready.writeTo(System.out); } catch (SOPGPException.NoSignature e) { - Print.errln("No verifiable signature found."); - Print.trace(e); - System.exit(e.getExitCode()); + String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); + throw new SOPGPException.NoSignature(errorMsg, e); } catch (IOException ioException) { - Print.errln("IO Error."); - Print.trace(ioException); - System.exit(1); + throw new RuntimeException(ioException); } catch (SOPGPException.BadData badData) { - Print.errln("Standard Input appears not to contain a valid OpenPGP message."); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.stdin_not_a_message"); + throw new SOPGPException.BadData(errorMsg, badData); } if (verificationsOut != null) { - try { - verificationsOut.createNewFile(); - PrintWriter pw = new PrintWriter(verificationsOut); + try (OutputStream outputStream = getOutput(verificationsOut)) { + PrintWriter pw = new PrintWriter(outputStream); for (Verification verification : verifications) { // CHECKSTYLE:OFF pw.println(verification); 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 3843d13..b6661af 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 @@ -4,126 +4,110 @@ package sop.cli.picocli.commands; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import picocli.CommandLine; import sop.MicAlg; import sop.ReadyWithResult; import sop.SigningResult; -import sop.cli.picocli.FileUtil; -import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.enums.SignAs; import sop.exception.SOPGPException; -import sop.operation.Sign; +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", - description = "Create a detached signature on the data from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class SignCmd extends AbstractSopCmd { @CommandLine.Option(names = "--no-armor", - description = "ASCII armor the output", + descriptionKey = "sop.sign.usage.option.armor", negatable = true) boolean armor = true; - @CommandLine.Option(names = "--as", description = "Defaults to 'binary'. If '--as=text' and the input data is not valid UTF-8, sign fails with return code 53.", + @CommandLine.Option(names = "--as", + descriptionKey = "sop.sign.usage.option.as", paramLabel = "{binary|text}") SignAs type; - @CommandLine.Parameters(description = "Secret keys used for signing", + @CommandLine.Parameters(descriptionKey = "sop.sign.usage.parameter.keys", paramLabel = "KEYS") - List secretKeyFile = new ArrayList<>(); + List secretKeyFile = new ArrayList<>(); - @CommandLine.Option(names = "--with-key-password", description = "Password(s) to unlock the secret key(s) with", + @CommandLine.Option(names = "--with-key-password", + descriptionKey = "sop.sign.usage.option.with_key_password", paramLabel = "PASSWORD") List withKeyPassword = new ArrayList<>(); - @CommandLine.Option(names = "--micalg-out", description = "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)", + @CommandLine.Option(names = "--micalg-out", + descriptionKey = "sop.sign.usage.option.micalg_out", paramLabel = "MICALG") - File micAlgOut; + String micAlgOut; @Override public void run() { - Sign sign = throwIfUnsupportedSubcommand( - SopCLI.getSop().sign(), "sign"); + DetachedSign detachedSign = throwIfUnsupportedSubcommand( + SopCLI.getSop().detachedSign(), "sign"); - throwIfOutputExists(micAlgOut, "--micalg-out"); + throwIfOutputExists(micAlgOut); throwIfEmptyParameters(secretKeyFile, "KEYS"); if (type != null) { try { - sign.mode(type); + detachedSign.mode(type); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--as'"); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--as"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } for (String passwordFile : withKeyPassword) { try { - String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile)); - sign.withKeyPassword(password); + String password = stringFromInputStream(getInput(passwordFile)); + detachedSign.withKeyPassword(password); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-key-password"), 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 (File keyFile : secretKeyFile) { - try (FileInputStream keyIn = new FileInputStream(keyFile)) { - sign.key(keyIn); - } catch (FileNotFoundException e) { - Print.errln("File " + keyFile.getAbsolutePath() + " does not exist."); - Print.trace(e); - System.exit(1); + for (String keyInput : secretKeyFile) { + try (InputStream keyIn = getInput(keyInput)) { + detachedSign.key(keyIn); } catch (IOException e) { - Print.errln("Cannot access file " + keyFile.getAbsolutePath()); - Print.trace(e); - System.exit(1); - } catch (SOPGPException.KeyIsProtected e) { - Print.errln("Key " + keyFile.getName() + " is password protected."); - Print.trace(e); - System.exit(1); + 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) { - Print.errln("Bad data in key file " + keyFile.getAbsolutePath() + ":"); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.not_a_private_key", keyInput); + throw new SOPGPException.BadData(errorMsg, badData); } } if (!armor) { - sign.noArmor(); + detachedSign.noArmor(); } try { - ReadyWithResult ready = sign.data(System.in); + ReadyWithResult ready = detachedSign.data(System.in); SigningResult result = ready.writeTo(System.out); MicAlg micAlg = result.getMicAlg(); if (micAlgOut != null) { // Write micalg out - micAlgOut.createNewFile(); - FileOutputStream micAlgOutStream = new FileOutputStream(micAlgOut); - micAlg.writeTo(micAlgOutStream); - micAlgOutStream.close(); + OutputStream outputStream = getOutput(micAlgOut); + micAlg.writeTo(outputStream); + outputStream.close(); } } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); - } catch (SOPGPException.ExpectedText expectedText) { - Print.errln("Expected text input, but got binary data."); - Print.trace(expectedText); - System.exit(expectedText.getExitCode()); + throw new RuntimeException(e); } } } 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 0d4b596..e0953b3 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 @@ -4,129 +4,101 @@ package sop.cli.picocli.commands; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import picocli.CommandLine; import sop.Verification; -import sop.cli.picocli.DateParser; import sop.cli.picocli.Print; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; -import sop.operation.Verify; +import sop.operation.DetachedVerify; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; @CommandLine.Command(name = "verify", - description = "Verify a detached signature over the data from standard input", + resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class VerifyCmd extends AbstractSopCmd { @CommandLine.Parameters(index = "0", - description = "Detached signature", + descriptionKey = "sop.verify.usage.parameter.signature", paramLabel = "SIGNATURE") - File signature; + String signature; @CommandLine.Parameters(index = "0..*", arity = "1..*", - description = "Public key certificates", + descriptionKey = "sop.verify.usage.parameter.certs", paramLabel = "CERT") - List certificates = new ArrayList<>(); + List certificates = new ArrayList<>(); @CommandLine.Option(names = {"--not-before"}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to beginning of time (\"-\").", + descriptionKey = "sop.verify.usage.option.not_before", paramLabel = "DATE") String notBefore = "-"; @CommandLine.Option(names = {"--not-after"}, - description = "ISO-8601 formatted UTC date (eg. '2020-11-23T16:35Z)\n" + - "Reject signatures with a creation date not in range.\n" + - "Defaults to current system time (\"now\").\n" + - "Accepts special value \"-\" for end of time.", + descriptionKey = "sop.verify.usage.option.not_after", paramLabel = "DATE") String notAfter = "now"; @Override public void run() { - Verify verify = throwIfUnsupportedSubcommand( - SopCLI.getSop().verify(), "verify"); + DetachedVerify detachedVerify = throwIfUnsupportedSubcommand( + SopCLI.getSop().detachedVerify(), "verify"); if (notAfter != null) { try { - verify.notAfter(DateParser.parseNotAfter(notAfter)); + detachedVerify.notAfter(parseNotAfter(notAfter)); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--not-after'."); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-after"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } if (notBefore != null) { try { - verify.notBefore(DateParser.parseNotBefore(notBefore)); + detachedVerify.notBefore(parseNotBefore(notBefore)); } catch (SOPGPException.UnsupportedOption unsupportedOption) { - Print.errln("Unsupported option '--not-before'."); - Print.trace(unsupportedOption); - System.exit(unsupportedOption.getExitCode()); + String errorMsg = getMsg("sop.error.feature_support.option_not_supported", "--not-before"); + throw new SOPGPException.UnsupportedOption(errorMsg, unsupportedOption); } } - for (File certFile : certificates) { - try (FileInputStream certIn = new FileInputStream(certFile)) { - verify.cert(certIn); - } catch (FileNotFoundException fileNotFoundException) { - Print.errln("Certificate file " + certFile.getAbsolutePath() + " not found."); - - Print.trace(fileNotFoundException); - System.exit(1); + for (String certInput : certificates) { + try (InputStream certIn = getInput(certInput)) { + detachedVerify.cert(certIn); } catch (IOException ioException) { - Print.errln("IO Error."); - Print.trace(ioException); - System.exit(1); + throw new RuntimeException(ioException); } catch (SOPGPException.BadData badData) { - Print.errln("Certificate file " + certFile.getAbsolutePath() + " appears to not contain a valid OpenPGP certificate."); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.not_a_certificate", certInput); + throw new SOPGPException.BadData(errorMsg, badData); } } if (signature != null) { - try (FileInputStream sigIn = new FileInputStream(signature)) { - verify.signatures(sigIn); - } catch (FileNotFoundException e) { - Print.errln("Signature file " + signature.getAbsolutePath() + " does not exist."); - Print.trace(e); - System.exit(1); + try (InputStream sigIn = getInput(signature)) { + detachedVerify.signatures(sigIn); } catch (IOException e) { - Print.errln("IO Error."); - Print.trace(e); - System.exit(1); + throw new RuntimeException(e); } catch (SOPGPException.BadData badData) { - Print.errln("File " + signature.getAbsolutePath() + " does not contain a valid OpenPGP signature."); - Print.trace(badData); - System.exit(badData.getExitCode()); + String errorMsg = getMsg("sop.error.input.not_a_signature", signature); + throw new SOPGPException.BadData(errorMsg, badData); } } - List verifications = null; + List verifications; try { - verifications = verify.data(System.in); + verifications = detachedVerify.data(System.in); } catch (SOPGPException.NoSignature e) { - Print.errln("No verifiable signature found."); - Print.trace(e); - System.exit(e.getExitCode()); + String errorMsg = getMsg("sop.error.runtime.no_verifiable_signature_found"); + throw new SOPGPException.NoSignature(errorMsg, e); } catch (IOException ioException) { - Print.errln("IO Error."); - Print.trace(ioException); - System.exit(1); + throw new RuntimeException(ioException); } catch (SOPGPException.BadData badData) { - Print.errln("Standard Input appears not to contain a valid OpenPGP message."); - Print.trace(badData); - System.exit(badData.getExitCode()); + 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/java/sop/cli/picocli/commands/VersionCmd.java b/sop-java-picocli/src/main/java/sop/cli/picocli/commands/VersionCmd.java index 64d69ca..2dc08db 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", description = "Display version information about the tool", +@CommandLine.Command(name = "version", resourceBundle = "sop", exitCodeOnInvalidInput = 37) public class VersionCmd extends AbstractSopCmd { @@ -17,10 +17,12 @@ public class VersionCmd extends AbstractSopCmd { Exclusive exclusive; static class Exclusive { - @CommandLine.Option(names = "--extended", description = "Print an extended version string.") + @CommandLine.Option(names = "--extended", + descriptionKey = "sop.version.usage.option.extended") boolean extended; - @CommandLine.Option(names = "--backend", description = "Print information about the cryptographic backend.") + @CommandLine.Option(names = "--backend", + descriptionKey = "sop.version.usage.option.backend") boolean backend; } diff --git a/sop-java-picocli/src/main/resources/sop.properties b/sop-java-picocli/src/main/resources/sop.properties new file mode 100644 index 0000000..8a7ef20 --- /dev/null +++ b/sop-java-picocli/src/main/resources/sop.properties @@ -0,0 +1,157 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +sop.name=sop +usage.header=Stateless OpenPGP Protocol + +usage.footerHeading=Powered by picocli%n +sop.locale=Locale for description texts +# Generic +usage.synopsisHeading=Usage:\u0020 +usage.commandListHeading = %nCommands:%n +# Exit Codes +usage.exitCodeListHeading=%nExit Codes:%n +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 +usage.exitCodeList.4=17:Certificate is not encryption capable +usage.exitCodeList.5=19:Usage error: Missing argument +usage.exitCodeList.6=23:Incomplete verification instructions +usage.exitCodeList.7=29:Unable to decrypt +usage.exitCodeList.8=31:Password is not human-readable +usage.exitCodeList.9=37:Unsupported Option +usage.exitCodeList.10=41:Invalid data or data of wrong type encountered +usage.exitCodeList.11=53:Non-text input received where text was expected +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.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 (eg. 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 (eg. 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 (eg. '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 (eg. '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 (eg. 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 (eg. 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 (eg. 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, eg. "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 (eg. 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 (eg. 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 (eg. '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 (eg. '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 (eg. 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 (eg. '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 (eg. '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 +## 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 '{0}' does not contain an OpenPGP private key. +sop.error.input.not_a_certificate=Input '{0}' does not contain an OpenPGP certificate. +sop.error.input.not_a_signature=Input '{0}' does not contain an OpenPGP signature. +sop.error.input.malformed_not_after=Invalid date string supplied as value of '--not-after'. +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 +## Indirect Data Types +sop.error.indirect_data_type.ambiguous_filename=File name '{0}' is ambiguous. File with the same name exists on the filesystem. +sop.error.indirect_data_type.environment_variable_not_set=Environment variable '{0}' not set. +sop.error.indirect_data_type.environment_variable_empty=Environment variable '{0}' is empty. +sop.error.indirect_data_type.input_file_does_not_exist=Input file '{0}' does not exist. +sop.error.indirect_data_type.input_not_a_file=Input file '{0}' is not a file. +sop.error.indirect_data_type.output_file_already_exists=Output file '{0}' already exists. +sop.error.indirect_data_type.output_file_cannot_be_created=Output file '{0}' 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.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 +sop.error.runtime.no_backend_set=No SOP backend set. +sop.error.runtime.cannot_unlock_key=Cannot unlock password-protected secret key from input '{0}'. +sop.error.runtime.key_uses_unsupported_asymmetric_algorithm=Secret key from input '{0}' uses an unsupported asymmetric algorithm. +sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm=Certificate from input '{0}' uses an unsupported asymmetric algorithm. +sop.error.runtime.key_cannot_sign=Secret key from input '{0}' cannot sign. +sop.error.runtime.cert_cannot_encrypt=Certificate from input '{0}' 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. +## 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 '{0}' is required. +sop.error.usage.parameter_required=Parameter '{0}' is required. +sop.error.usage.option_requires_other_option=Option '{0}' is requested, but no option {1} was provided. +# Feature Support +sop.error.feature_support.subcommand_not_supported=Subcommand '{0}' is not supported. +sop.error.feature_support.option_not_supported=Option '{0}' not supported. \ No newline at end of file diff --git a/sop-java-picocli/src/main/resources/sop_de.properties b/sop-java-picocli/src/main/resources/sop_de.properties new file mode 100644 index 0000000..532a8d1 --- /dev/null +++ b/sop-java-picocli/src/main/resources/sop_de.properties @@ -0,0 +1,156 @@ +# SPDX-FileCopyrightText: 2022 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 +sop.name=sop +usage.header=Stateless OpenPGP Protocol +usage.footerHeading=Powered by Picocli%n +sop.locale=Gebietsschema für Beschreibungstexte +# Generic +usage.synopsisHeading=Aufruf:\u0020 +usage.commandListHeading=%nBefehle:%n +# Exit Codes +usage.exitCodeListHeading=%nExit Codes:%n +usage.exitCodeList.0=\u00200:Erfolgreiche Programmausführung +usage.exitCodeList.1=\u00201:Generischer Programmfehler +usage.exitCodeList.2=\u00203:Signaturverifikation gefordert, aber keine gültige Signatur gefunden +usage.exitCodeList.3=13:Nicht unterstützter asymmetrischer Algorithmus +usage.exitCodeList.4=17:Zertifikat ist nicht fähig zu verschlüsseln +usage.exitCodeList.5=19:Nutzungsfehler: Fehlendes Argument +usage.exitCodeList.6=23:Unvollständige Verifikationsanweisungen +usage.exitCodeList.7=29:Entschlüsselung nicht möglich +usage.exitCodeList.8=31:Passwort ist nicht für Menschen lesbar +usage.exitCodeList.9=37:Nicht unterstützte Option +usage.exitCodeList.10=41:Ungültige Daten oder Daten des falschen Typs gefunden +usage.exitCodeList.11=53:Nicht-Text-Eingabe erhalten, wo Text erwartet wurde +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 (zB. "@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 (zb. 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 (zb. 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 (zB. '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 (zB. '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 (zb. 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 (zB. 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 (zb. 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, zB. "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 (zB. 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 (zB. 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 (zB. '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 (zB. '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 (zB. 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 (zB. '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 (zB. '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 +## 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 '{0}' enthält keinen privaten OpenPGP Schlüssel. +sop.error.input.not_a_certificate=Eingabe '{0}' enthält kein OpenPGP Zertifikat. +sop.error.input.not_a_signature=Eingabe '{0}' enthält keine OpenPGP Signatur. +sop.error.input.malformed_not_after=Ungültige Datumszeichenfolge als Wert von '--not-after'. +sop.error.input.malformed_not_before=Ungültige Datumszeichenfolge als Wert von '--not-before'. +sop.error.input.stdin_not_a_message=Standard-Eingabe enthält scheinbar keine OpenPGP Nachricht. +sop.error.input.stdin_not_a_private_key=Standard-Eingabe enthält scheinbar keinen privaten OpenPGP Schlüssel. +sop.error.input.stdin_not_openpgp_data=Standard-Eingabe enthält scheinbar keine gültigen OpenPGP Daten. +## Indirect Data Types +sop.error.indirect_data_type.ambiguous_filename=Dateiname '{0}' ist mehrdeutig. Datei mit dem selben Namen existiert im Dateisystem. +sop.error.indirect_data_type.environment_variable_not_set=Umgebungsvariable '{0}' nicht gesetzt. +sop.error.indirect_data_type.environment_variable_empty=Umgebungsvariable '{0}' ist leer. +sop.error.indirect_data_type.input_file_does_not_exist=Quelldatei '{0}' existiert nicht. +sop.error.indirect_data_type.input_not_a_file=Quelldatei '{0}' ist keine Datei. +sop.error.indirect_data_type.output_file_already_exists=Zieldatei '{0}' existiert bereits. +sop.error.indirect_data_type.output_file_cannot_be_created=Zieldatei '{0}' 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.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 +sop.error.runtime.no_backend_set=Kein SOP Backend gesetzt. +sop.error.runtime.cannot_unlock_key=Gesperrter Schlüssel aus Eingabe '{0}' kann nicht entsperrt werden. +sop.error.runtime.key_uses_unsupported_asymmetric_algorithm=Privater Schlüssel aus Eingabe '{0}' nutzt nicht unterstütztem asymmetrischen Algorithmus. +sop.error.runtime.cert_uses_unsupported_asymmetric_algorithm=Zertifikat aus Eingabe '{0}' nutzt nicht unterstütztem asymmetrischen Algorithmus. +sop.error.runtime.key_cannot_sign=Privater Schlüssel aus Eingabe '{0}' kann nicht signieren. +sop.error.runtime.cert_cannot_encrypt=Zertifikat aus Eingabe '{0}' 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. +## 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 '{0}' ist erforderlich. +sop.error.usage.parameter_required=Parameter '{0}' ist erforderlich. +sop.error.usage.option_requires_other_option=Option '{0}' wurde angegeben, jedoch kein Wert für {1}. +# Feature Support +sop.error.feature_support.subcommand_not_supported=Unterbefehl '{0}' wird nicht unterstützt. +sop.error.feature_support.option_not_supported=Option '{0}' wird nicht unterstützt. \ No newline at end of file diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/DateParserTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java similarity index 58% rename from sop-java-picocli/src/test/java/sop/cli/picocli/DateParserTest.java rename to sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java index 5c7def5..1fff1d5 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/DateParserTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/DateParsingTest.java @@ -9,41 +9,44 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Date; import org.junit.jupiter.api.Test; +import sop.cli.picocli.commands.AbstractSopCmd; +import sop.cli.picocli.commands.ArmorCmd; import sop.util.UTCUtil; -public class DateParserTest { +public class DateParsingTest { + private AbstractSopCmd cmd = new ArmorCmd(); // we use ArmorCmd as a concrete implementation. @Test public void parseNotAfterDashReturnsEndOfTime() { - assertEquals(DateParser.END_OF_TIME, DateParser.parseNotAfter("-")); + assertEquals(AbstractSopCmd.END_OF_TIME, cmd.parseNotAfter("-")); } @Test public void parseNotBeforeDashReturnsBeginningOfTime() { - assertEquals(DateParser.BEGINNING_OF_TIME, DateParser.parseNotBefore("-")); + assertEquals(AbstractSopCmd.BEGINNING_OF_TIME, cmd.parseNotBefore("-")); } @Test public void parseNotAfterNowReturnsNow() { - assertEquals(new Date().getTime(), DateParser.parseNotAfter("now").getTime(), 1000); + assertEquals(new Date().getTime(), cmd.parseNotAfter("now").getTime(), 1000); } @Test public void parseNotBeforeNowReturnsNow() { - assertEquals(new Date().getTime(), DateParser.parseNotBefore("now").getTime(), 1000); + assertEquals(new Date().getTime(), cmd.parseNotBefore("now").getTime(), 1000); } @Test public void parseNotAfterTimestamp() { String timestamp = "2019-10-24T23:48:29Z"; - Date date = DateParser.parseNotAfter(timestamp); + Date date = cmd.parseNotAfter(timestamp); assertEquals(timestamp, UTCUtil.formatUTCDate(date)); } @Test public void parseNotBeforeTimestamp() { String timestamp = "2019-10-29T18:36:45Z"; - Date date = DateParser.parseNotBefore(timestamp); + Date date = cmd.parseNotBefore(timestamp); assertEquals(timestamp, UTCUtil.formatUTCDate(date)); } } diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/FileUtilTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/FileUtilTest.java deleted file mode 100644 index eeb4589..0000000 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/FileUtilTest.java +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -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 java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import sop.exception.SOPGPException; - -public class FileUtilTest { - - @BeforeAll - public static void setup() { - FileUtil.setEnvironmentVariableResolver(new FileUtil.EnvironmentVariableResolver() { - @Override - public String resolveEnvironmentVariable(String name) { - if (name.equals("test123")) { - return "test321"; - } - return null; - } - }); - } - - @Test - public void getFile_ThrowsForNull() { - assertThrows(NullPointerException.class, () -> FileUtil.getFile(null)); - } - - @Test - public void getFile_prfxEnvAlreadyExists() throws IOException { - File tempFile = new File("@ENV:test"); - tempFile.createNewFile(); - tempFile.deleteOnExit(); - - assertThrows(SOPGPException.AmbiguousInput.class, () -> FileUtil.getFile("@ENV:test")); - } - - @Test - public void getFile_EnvironmentVariable() { - File file = FileUtil.getFile("@ENV:test123"); - assertEquals("test321", file.getName()); - } - - @Test - public void getFile_nonExistentEnvVariable() { - assertThrows(IllegalArgumentException.class, () -> FileUtil.getFile("@ENV:INVALID")); - } - - @Test - public void getFile_prfxFdAlreadyExists() throws IOException { - File tempFile = new File("@FD:1"); - tempFile.createNewFile(); - tempFile.deleteOnExit(); - - assertThrows(SOPGPException.AmbiguousInput.class, () -> FileUtil.getFile("@FD:1")); - } - - @Test - public void getFile_prfxFdNotSupported() { - assertThrows(IllegalArgumentException.class, () -> FileUtil.getFile("@FD:2")); - } - - @Test - public void createNewFileOrThrow_throwsForNull() { - assertThrows(NullPointerException.class, () -> FileUtil.createNewFileOrThrow(null)); - } - - @Test - public void createNewFileOrThrow_success() throws IOException { - File dir = Files.createTempDirectory("test").toFile(); - dir.deleteOnExit(); - File file = new File(dir, "file"); - - assertFalse(file.exists()); - FileUtil.createNewFileOrThrow(file); - assertTrue(file.exists()); - } - - @Test - public void createNewFileOrThrow_alreadyExists() throws IOException { - File dir = Files.createTempDirectory("test").toFile(); - dir.deleteOnExit(); - File file = new File(dir, "file"); - - FileUtil.createNewFileOrThrow(file); - assertTrue(file.exists()); - assertThrows(SOPGPException.OutputExists.class, () -> FileUtil.createNewFileOrThrow(file)); - } - - @Test - public void getFileInputStream_success() throws IOException { - File dir = Files.createTempDirectory("test").toFile(); - dir.deleteOnExit(); - File file = new File(dir, "file"); - - FileUtil.createNewFileOrThrow(file); - FileInputStream inputStream = FileUtil.getFileInputStream(file.getAbsolutePath()); - assertNotNull(inputStream); - } - - @Test - public void getFileInputStream_fileNotFound() throws IOException { - File dir = Files.createTempDirectory("test").toFile(); - dir.deleteOnExit(); - File file = new File(dir, "file"); - - assertThrows(SOPGPException.MissingInput.class, - () -> FileUtil.getFileInputStream(file.getAbsolutePath())); - } -} 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 b71dc59..5727a47 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 @@ -23,8 +23,8 @@ import sop.operation.ExtractCert; import sop.operation.GenerateKey; import sop.operation.InlineSign; import sop.operation.InlineVerify; -import sop.operation.Sign; -import sop.operation.Verify; +import sop.operation.DetachedSign; +import sop.operation.DetachedVerify; import sop.operation.Version; public class SOPTest { @@ -65,12 +65,12 @@ public class SOPTest { } @Override - public Sign sign() { + public DetachedSign detachedSign() { return null; } @Override - public Verify verify() { + public DetachedVerify detachedVerify() { return null; } 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 f04026d..605fbd5 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 @@ -36,7 +36,6 @@ import sop.ReadyWithResult; import sop.SOP; import sop.SessionKey; import sop.Verification; -import sop.cli.picocli.DateParser; import sop.cli.picocli.SopCLI; import sop.cli.picocli.TestFileUtil; import sop.exception.SOPGPException; @@ -116,7 +115,7 @@ public class DecryptCmdTest { public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption { Date now = new Date(); SopCLI.main(new String[] {"decrypt"}); - verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME); + verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); verify(decrypt, times(1)).verifyNotAfter( ArgumentMatchers.argThat(argument -> { // allow 1-second difference @@ -127,8 +126,8 @@ public class DecryptCmdTest { @Test public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption { SopCLI.main(new String[] {"decrypt", "--not-before", "-", "--not-after", "-"}); - verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME); - verify(decrypt, times(1)).verifyNotAfter(DateParser.END_OF_TIME); + verify(decrypt, times(1)).verifyNotBefore(AbstractSopCmd.BEGINNING_OF_TIME); + verify(decrypt, times(1)).verifyNotAfter(AbstractSopCmd.END_OF_TIME); } @Test 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 4edb5e6..68643f1 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 @@ -127,7 +127,7 @@ public class EncryptCmdTest { @Test @ExpectSystemExitWithStatus(79) - public void signWith_certCannotSignCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData { + public void signWith_certCannotSignCausesExit79() 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"); 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 ce0ce54..6be8d9b 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 @@ -24,17 +24,17 @@ import sop.SOP; import sop.SigningResult; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; -import sop.operation.Sign; +import sop.operation.DetachedSign; public class SignCmdTest { - Sign sign; + DetachedSign detachedSign; File keyFile; @BeforeEach public void mockComponents() throws IOException, SOPGPException.ExpectedText { - sign = mock(Sign.class); - when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult() { + detachedSign = mock(DetachedSign.class); + when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult() { @Override public SigningResult writeTo(OutputStream outputStream) { return SigningResult.builder().build(); @@ -42,7 +42,7 @@ public class SignCmdTest { }); SOP sop = mock(SOP.class); - when(sop.sign()).thenReturn(sign); + when(sop.detachedSign()).thenReturn(detachedSign); SopCLI.setSopInstance(sop); @@ -65,27 +65,27 @@ public class SignCmdTest { @Test @ExpectSystemExitWithStatus(37) public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { - when(sign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported.")); + when(detachedSign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported.")); SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()}); } @Test - @ExpectSystemExitWithStatus(1) - public void key_nonExistentKeyFileCausesExit1() { + @ExpectSystemExitWithStatus(61) + public void key_nonExistentKeyFileCausesExit61() { SopCLI.main(new String[] {"sign", "invalid.asc"}); } @Test - @ExpectSystemExitWithStatus(1) - public void key_keyIsProtectedCausesExit1() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { - when(sign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected()); + @ExpectSystemExitWithStatus(67) + 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()}); } @Test @ExpectSystemExitWithStatus(41) public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData { - when(sign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); + when(detachedSign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); } @@ -98,19 +98,19 @@ public class SignCmdTest { @Test public void noArmor_notCalledByDefault() { SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()}); - verify(sign, never()).noArmor(); + verify(detachedSign, never()).noArmor(); } @Test public void noArmor_passedDown() { SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()}); - verify(sign, times(1)).noArmor(); + verify(detachedSign, times(1)).noArmor(); } @Test @ExpectSystemExitWithStatus(1) public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText { - when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult() { + when(detachedSign.data((InputStream) any())).thenReturn(new ReadyWithResult() { @Override public SigningResult writeTo(OutputStream outputStream) throws IOException { throw new IOException(); @@ -122,7 +122,7 @@ public class SignCmdTest { @Test @ExpectSystemExitWithStatus(53) public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText { - when(sign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText()); + when(detachedSign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText()); SopCLI.main(new String[] {"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 028d245..4b9c633 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 @@ -27,15 +27,14 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import sop.SOP; import sop.Verification; -import sop.cli.picocli.DateParser; import sop.cli.picocli.SopCLI; import sop.exception.SOPGPException; -import sop.operation.Verify; +import sop.operation.DetachedVerify; import sop.util.UTCUtil; public class VerifyCmdTest { - Verify verify; + DetachedVerify detachedVerify; File signature; File cert; @@ -45,12 +44,12 @@ public class VerifyCmdTest { public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException { originalSout = System.out; - verify = mock(Verify.class); - when(verify.notBefore(any())).thenReturn(verify); - when(verify.notAfter(any())).thenReturn(verify); - when(verify.cert((InputStream) any())).thenReturn(verify); - when(verify.signatures((InputStream) any())).thenReturn(verify); - when(verify.data((InputStream) any())).thenReturn( + detachedVerify = mock(DetachedVerify.class); + when(detachedVerify.notBefore(any())).thenReturn(detachedVerify); + when(detachedVerify.notAfter(any())).thenReturn(detachedVerify); + when(detachedVerify.cert((InputStream) any())).thenReturn(detachedVerify); + when(detachedVerify.signatures((InputStream) any())).thenReturn(detachedVerify); + when(detachedVerify.data((InputStream) any())).thenReturn( Collections.singletonList( new Verification( UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"), @@ -60,7 +59,7 @@ public class VerifyCmdTest { ); SOP sop = mock(SOP.class); - when(sop.verify()).thenReturn(verify); + when(sop.detachedVerify()).thenReturn(detachedVerify); SopCLI.setSopInstance(sop); @@ -77,26 +76,26 @@ public class VerifyCmdTest { public void notAfter_passedDown() throws SOPGPException.UnsupportedOption { 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(verify, times(1)).notAfter(date); + 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()}); - verify(verify, times(1)).notAfter(dateMatcher(now)); + 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()}); - verify(verify, times(1)).notAfter(DateParser.END_OF_TIME); + verify(detachedVerify, times(1)).notAfter(AbstractSopCmd.END_OF_TIME); } @Test @ExpectSystemExitWithStatus(37) public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { - when(verify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported.")); + 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()}); } @@ -104,34 +103,34 @@ public class VerifyCmdTest { public void notBefore_passedDown() throws SOPGPException.UnsupportedOption { 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(verify, times(1)).notBefore(date); + 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()}); - verify(verify, times(1)).notBefore(dateMatcher(now)); + 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()}); - verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME); + verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); } @Test @ExpectSystemExitWithStatus(37) public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption { - when(verify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported.")); + 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()}); } @Test public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption { SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); - verify(verify, times(1)).notAfter(dateMatcher(new Date())); - verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME); + verify(detachedVerify, times(1)).notAfter(dateMatcher(new Date())); + verify(detachedVerify, times(1)).notBefore(AbstractSopCmd.BEGINNING_OF_TIME); } private static Date dateMatcher(Date date) { @@ -139,48 +138,48 @@ public class VerifyCmdTest { } @Test - @ExpectSystemExitWithStatus(1) - public void cert_fileNotFoundCausesExit1() { + @ExpectSystemExitWithStatus(61) + public void cert_fileNotFoundCausesExit61() { SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"}); } @Test @ExpectSystemExitWithStatus(41) public void cert_badDataCausesExit41() throws SOPGPException.BadData { - when(verify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); + when(detachedVerify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); } @Test - @ExpectSystemExitWithStatus(1) - public void signature_fileNotFoundCausesExit1() { + @ExpectSystemExitWithStatus(61) + public void signature_fileNotFoundCausesExit61() { SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()}); } @Test @ExpectSystemExitWithStatus(41) public void signature_badDataCausesExit41() throws SOPGPException.BadData { - when(verify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); + when(detachedVerify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); } @Test @ExpectSystemExitWithStatus(3) public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { - when(verify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature()); + when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature()); SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); } @Test @ExpectSystemExitWithStatus(41) public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { - when(verify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); + when(detachedVerify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException())); SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()}); } @Test public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData { - when(verify.data((InputStream) any())).thenReturn(Arrays.asList( + when(detachedVerify.data((InputStream) any())).thenReturn(Arrays.asList( new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"), "EB85BB5FA33A75E15E944E63F231550C4F47E38E", "EB85BB5FA33A75E15E944E63F231550C4F47E38E"), diff --git a/sop-java/src/main/java/sop/SOP.java b/sop-java/src/main/java/sop/SOP.java index 9068bab..284a0ab 100644 --- a/sop-java/src/main/java/sop/SOP.java +++ b/sop-java/src/main/java/sop/SOP.java @@ -13,8 +13,8 @@ import sop.operation.GenerateKey; import sop.operation.InlineDetach; import sop.operation.InlineSign; import sop.operation.InlineVerify; -import sop.operation.Sign; -import sop.operation.Verify; +import sop.operation.DetachedSign; +import sop.operation.DetachedVerify; import sop.operation.Version; /** @@ -47,19 +47,72 @@ public interface SOP { /** * Create detached signatures. - * Customize the operation using the builder {@link Sign}. + * Customize the operation using the builder {@link DetachedSign}. + * + * If you want to sign a message inline, use {@link #inlineSign()} instead. * * @return builder instance */ - Sign sign(); + 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 Verify}. + * Customize the operation using the builder {@link DetachedVerify}. + * + * If you need to verify an inline-signed message, use {@link #inlineVerify()} instead. * * @return builder instance */ - Verify verify(); + 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. @@ -93,9 +146,4 @@ public interface SOP { */ Dearmor dearmor(); - InlineDetach inlineDetach(); - - InlineSign inlineSign(); - - InlineVerify inlineVerify(); } diff --git a/sop-java/src/main/java/sop/enums/EncryptAs.java b/sop-java/src/main/java/sop/enums/EncryptAs.java index 2de6792..85a2cd7 100644 --- a/sop-java/src/main/java/sop/enums/EncryptAs.java +++ b/sop-java/src/main/java/sop/enums/EncryptAs.java @@ -6,6 +6,5 @@ package sop.enums; public enum EncryptAs { Binary, - Text, - MIME + Text } diff --git a/sop-java/src/main/java/sop/exception/SOPGPException.java b/sop-java/src/main/java/sop/exception/SOPGPException.java index 93f565e..493ccb3 100644 --- a/sop-java/src/main/java/sop/exception/SOPGPException.java +++ b/sop-java/src/main/java/sop/exception/SOPGPException.java @@ -35,6 +35,10 @@ public abstract class SOPGPException extends RuntimeException { super("No verifiable signature found."); } + public NoSignature(String errorMsg, NoSignature e) { + super(errorMsg, e); + } + @Override public int getExitCode() { return EXIT_CODE; @@ -225,6 +229,10 @@ public abstract class SOPGPException extends RuntimeException { super(message, cause); } + public MissingInput(String errorMsg) { + super(errorMsg); + } + @Override public int getExitCode() { return EXIT_CODE; @@ -276,6 +284,10 @@ public abstract class SOPGPException extends RuntimeException { public static final int EXIT_CODE = 71; + public UnsupportedSpecialPrefix(String errorMsg) { + super(errorMsg); + } + @Override public int getExitCode() { return EXIT_CODE; diff --git a/sop-java/src/main/java/sop/operation/AbstractSign.java b/sop-java/src/main/java/sop/operation/AbstractSign.java index fe28c47..60b7418 100644 --- a/sop-java/src/main/java/sop/operation/AbstractSign.java +++ b/sop-java/src/main/java/sop/operation/AbstractSign.java @@ -20,7 +20,7 @@ public interface AbstractSign { * * @return builder instance */ - Sign noArmor(); + DetachedSign noArmor(); /** * Add one or more signing keys. diff --git a/sop-java/src/main/java/sop/operation/AbstractVerify.java b/sop-java/src/main/java/sop/operation/AbstractVerify.java index 82582e1..8441996 100644 --- a/sop-java/src/main/java/sop/operation/AbstractVerify.java +++ b/sop-java/src/main/java/sop/operation/AbstractVerify.java @@ -12,9 +12,9 @@ import java.util.Date; /** * Common API methods shared between verification of inline signatures ({@link InlineVerify}) - * and verification of detached signatures ({@link Verify}). + * and verification of detached signatures ({@link DetachedVerify}). * - * @param Builder type ({@link Verify}, {@link InlineVerify}) + * @param Builder type ({@link DetachedVerify}, {@link InlineVerify}) */ public interface AbstractVerify { diff --git a/sop-java/src/main/java/sop/operation/Sign.java b/sop-java/src/main/java/sop/operation/DetachedSign.java similarity index 79% rename from sop-java/src/main/java/sop/operation/Sign.java rename to sop-java/src/main/java/sop/operation/DetachedSign.java index 28a8f72..ba3de67 100644 --- a/sop-java/src/main/java/sop/operation/Sign.java +++ b/sop-java/src/main/java/sop/operation/DetachedSign.java @@ -9,7 +9,7 @@ import sop.exception.SOPGPException; import java.io.InputStream; -public interface Sign extends AbstractSign { +public interface DetachedSign extends AbstractSign { /** * Sets the signature mode. @@ -20,6 +20,6 @@ public interface Sign extends AbstractSign { * * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported */ - Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption; + DetachedSign mode(SignAs mode) throws SOPGPException.UnsupportedOption; } diff --git a/sop-java/src/main/java/sop/operation/Verify.java b/sop-java/src/main/java/sop/operation/DetachedVerify.java similarity index 90% rename from sop-java/src/main/java/sop/operation/Verify.java rename to sop-java/src/main/java/sop/operation/DetachedVerify.java index d51affa..3927729 100644 --- a/sop-java/src/main/java/sop/operation/Verify.java +++ b/sop-java/src/main/java/sop/operation/DetachedVerify.java @@ -12,7 +12,7 @@ import java.io.InputStream; /** * API for verifying detached signatures. */ -public interface Verify extends AbstractVerify, VerifySignatures { +public interface DetachedVerify extends AbstractVerify, VerifySignatures { /** * Provides the detached signatures. diff --git a/sop-java/src/main/java/sop/operation/InlineSign.java b/sop-java/src/main/java/sop/operation/InlineSign.java index 17bf80f..1dc32d8 100644 --- a/sop-java/src/main/java/sop/operation/InlineSign.java +++ b/sop-java/src/main/java/sop/operation/InlineSign.java @@ -20,6 +20,6 @@ public interface InlineSign extends AbstractSign { * * @throws sop.exception.SOPGPException.UnsupportedOption if this option is not supported */ - Sign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption; + DetachedSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption; }