mirror of
https://codeberg.org/PGPainless/sop-java.git
synced 2025-09-10 10:49:48 +02:00
Wip: Refactor command implementation and enable i18n for CLI
This commit is contained in:
parent
a7f02d58cc
commit
4934e472e2
34 changed files with 980 additions and 782 deletions
|
@ -1,33 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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 <a href="https://picocli.info/#_controlling_the_locale">Picocli Readme</a>
|
||||
*/
|
||||
class InitLocale {
|
||||
@CommandLine.Option(names = { "-l", "--locale" }, descriptionKey = "sop.locale")
|
||||
void setLocale(String locale) {
|
||||
Locale.setDefault(new Locale(locale));
|
||||
}
|
||||
|
||||
@CommandLine.Unmatched
|
||||
List<String> remainder; // ignore any other parameters and options in the first parsing phase
|
||||
}
|
||||
|
|
|
@ -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> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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<File> certs = new ArrayList<>();
|
||||
List<String> 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<File> keys = new ArrayList<>();
|
||||
List<String> 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<String> 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<File> keys, Decrypt decrypt) {
|
||||
for (File key : keys) {
|
||||
try (FileInputStream keyIn = new FileInputStream(key)) {
|
||||
private void setDecryptWith(List<String> 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<File> certs, Decrypt decrypt) {
|
||||
for (File cert : certs) {
|
||||
try (FileInputStream certIn = new FileInputStream(cert)) {
|
||||
private void setVerifyWith(List<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<File> signWith = new ArrayList<>();
|
||||
List<String> 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<String> 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<File> certs = new ArrayList<>();
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <alice@example.com>\"")
|
||||
@CommandLine.Parameters(descriptionKey = "sop.generate-key.usage.option.user_id")
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<File> secretKeyFile = new ArrayList<>();
|
||||
List<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<File> certificates = new ArrayList<>();
|
||||
List<String> 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<List<Verification>> 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);
|
||||
|
|
|
@ -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<File> secretKeyFile = new ArrayList<>();
|
||||
List<String> 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<String> 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<SigningResult> ready = sign.data(System.in);
|
||||
ReadyWithResult<SigningResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<File> certificates = new ArrayList<>();
|
||||
List<String> 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<Verification> verifications = null;
|
||||
List<Verification> 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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue