This commit is contained in:
Paul Schaub 2022-05-29 21:17:03 +02:00
parent 8184c30617
commit a7f02d58cc
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
18 changed files with 442 additions and 115 deletions

View file

@ -14,6 +14,8 @@ import sop.cli.picocli.commands.InlineDetachCmd;
import sop.cli.picocli.commands.EncryptCmd;
import sop.cli.picocli.commands.ExtractCertCmd;
import sop.cli.picocli.commands.GenerateKeyCmd;
import sop.cli.picocli.commands.InlineSignCmd;
import sop.cli.picocli.commands.InlineVerifyCmd;
import sop.cli.picocli.commands.SignCmd;
import sop.cli.picocli.commands.VerifyCmd;
import sop.cli.picocli.commands.VersionCmd;
@ -33,6 +35,8 @@ import sop.cli.picocli.commands.VersionCmd;
GenerateKeyCmd.class,
SignCmd.class,
VerifyCmd.class,
InlineSignCmd.class,
InlineVerifyCmd.class,
VersionCmd.class,
AutoComplete.GenerateCompletion.class
},

View file

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.cli.picocli.commands;
import sop.exception.SOPGPException;
import java.io.File;
import java.util.Collection;
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.";
void throwIfOutputExists(File outputFile, String optionName) {
if (outputFile == null) {
return;
}
if (outputFile.exists()) {
throw new SOPGPException.OutputExists(String.format(ERROR_OUTPUT_OF_OPTION_EXISTS, outputFile.getAbsolutePath(), optionName));
}
}
void throwIfMissingArg(Object arg, String argName) {
if (arg == null) {
throw new SOPGPException.MissingArg(argName + " is required.");
}
}
void throwIfEmptyParameters(Collection<?> arg, String parmName) {
if (arg.isEmpty()) {
throw new SOPGPException.MissingArg("Parameter '" + parmName + "' is required.");
}
}
<T> T throwIfUnsupportedSubcommand(T subcommand, String subcommandName) {
if (subcommand == null) {
throw new SOPGPException.UnsupportedSubcommand("Command '" + subcommandName + "' is not supported.");
}
return subcommand;
}
}

View file

@ -17,17 +17,16 @@ import sop.operation.Armor;
@CommandLine.Command(name = "armor",
description = "Add ASCII Armor to standard input",
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
public class ArmorCmd implements Runnable {
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}")
ArmorLabel label;
@Override
public void run() {
Armor armor = SopCLI.getSop().armor();
if (armor == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'armor' not implemented.");
}
Armor armor = throwIfUnsupportedSubcommand(
SopCLI.getSop().armor(),
"armor");
if (label != null) {
try {

View file

@ -15,19 +15,15 @@ import sop.operation.Dearmor;
@CommandLine.Command(name = "dearmor",
description = "Remove ASCII Armor from standard input",
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
public class DearmorCmd implements Runnable {
public class DearmorCmd extends AbstractSopCmd {
@Override
public void run() {
Dearmor dearmor = SopCLI.getSop().dearmor();
if (dearmor == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'dearmor' not implemented.");
}
Dearmor dearmor = throwIfUnsupportedSubcommand(
SopCLI.getSop().dearmor(), "dearmor");
try {
SopCLI.getSop()
.dearmor()
.data(System.in)
dearmor.data(System.in)
.writeTo(System.out);
} catch (SOPGPException.BadData e) {
Print.errln("Bad data.");

View file

@ -30,51 +30,54 @@ import sop.util.HexUtil;
@CommandLine.Command(name = "decrypt",
description = "Decrypt a message from standard input",
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
public class DecryptCmd implements Runnable {
public class DecryptCmd extends AbstractSopCmd {
private static final String SESSION_KEY_OUT = "--session-key-out";
private static final String VERIFY_OUT = "--verify-out";
private static final String OPT_WITH_SESSION_KEY = "--with-session-key";
private static final String OPT_WITH_PASSWORD = "--with-password";
private static final String OPT_NOT_BEFORE = "--not-before";
private static final String OPT_NOT_AFTER = "--not-after";
private static final String OPT_SESSION_KEY_OUT = "--session-key-out";
private static final String OPT_VERIFY_OUT = "--verify-out";
private static final String OPT_VERIFY_WITH = "--verify-with";
private static final String OPT_WITH_KEY_PASSWORD = "--with-key-password";
private static final String ERROR_UNSUPPORTED_OPTION = "Option '%s' is not supported.";
private static final String ERROR_FILE_NOT_EXIST = "File '%s' does not exist.";
private static final String ERROR_OUTPUT_OF_OPTION_EXISTS = "Target %s of option %s already exists.";
@CommandLine.Option(
names = {SESSION_KEY_OUT},
names = {OPT_SESSION_KEY_OUT},
description = "Can be used to learn the session key on successful decryption",
paramLabel = "SESSIONKEY")
File sessionKeyOut;
@CommandLine.Option(
names = {"--with-session-key"},
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",
paramLabel = "SESSIONKEY")
List<String> withSessionKey = new ArrayList<>();
@CommandLine.Option(
names = {"--with-password"},
names = {OPT_WITH_PASSWORD},
description = "Provide a password file. Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"",
paramLabel = "PASSWORD")
List<String> withPassword = new ArrayList<>();
@CommandLine.Option(names = {VERIFY_OUT},
@CommandLine.Option(names = {OPT_VERIFY_OUT},
description = "Produces signature verification status to the designated file",
paramLabel = "VERIFICATIONS")
File verifyOut;
@CommandLine.Option(names = {"--verify-with"},
@CommandLine.Option(names = {OPT_VERIFY_WITH},
description = "Certificates whose signatures would be acceptable for signatures over this message",
paramLabel = "CERT")
List<File> certs = new ArrayList<>();
@CommandLine.Option(names = {"--not-before"},
@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 (\"-\").",
paramLabel = "DATE")
String notBefore = "-";
@CommandLine.Option(names = {"--not-after"},
@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" +
@ -87,20 +90,18 @@ public class DecryptCmd implements Runnable {
paramLabel = "KEY")
List<File> keys = new ArrayList<>();
@CommandLine.Option(names = "--with-key-password",
@CommandLine.Option(names = {OPT_WITH_KEY_PASSWORD},
description = "Provide indirect file type pointing at passphrase(s) for secret key(s)",
paramLabel = "PASSWORD")
List<String> withKeyPassword = new ArrayList<>();
@Override
public void run() {
throwIfOutputExists(verifyOut, VERIFY_OUT);
throwIfOutputExists(sessionKeyOut, SESSION_KEY_OUT);
Decrypt decrypt = throwIfUnsupportedSubcommand(
SopCLI.getSop().decrypt(), "decrypt");
Decrypt decrypt = SopCLI.getSop().decrypt();
if (decrypt == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'decrypt' not implemented.");
}
throwIfOutputExists(verifyOut, OPT_VERIFY_OUT);
throwIfOutputExists(sessionKeyOut, OPT_SESSION_KEY_OUT);
setNotAfter(notAfter, decrypt);
setNotBefore(notBefore, decrypt);
@ -112,7 +113,7 @@ public class DecryptCmd implements Runnable {
if (verifyOut != null && certs.isEmpty()) {
String errorMessage = "Option %s is requested, but no option %s was provided.";
throw new SOPGPException.IncompleteVerification(String.format(errorMessage, VERIFY_OUT, "--verify-with"));
throw new SOPGPException.IncompleteVerification(String.format(errorMessage, OPT_VERIFY_OUT, OPT_VERIFY_WITH));
}
try {
@ -127,16 +128,6 @@ public class DecryptCmd implements Runnable {
}
}
private void throwIfOutputExists(File outputFile, String optionName) {
if (outputFile == null) {
return;
}
if (outputFile.exists()) {
throw new SOPGPException.OutputExists(String.format(ERROR_OUTPUT_OF_OPTION_EXISTS, outputFile.getAbsolutePath(), optionName));
}
}
private void writeVerifyOut(DecryptionResult result) throws IOException {
if (verifyOut != null) {
FileUtil.createNewFileOrThrow(verifyOut);
@ -158,7 +149,8 @@ public class DecryptCmd implements Runnable {
try (FileOutputStream outputStream = new FileOutputStream(sessionKeyOut)) {
if (!result.getSessionKey().isPresent()) {
throw new SOPGPException.UnsupportedOption("Session key not extracted. Possibly the feature --session-key-out is not supported.");
String errorMsg = "Session key not extracted. Possibly the feature %s is not supported.";
throw new SOPGPException.UnsupportedOption(String.format(errorMsg, OPT_SESSION_KEY_OUT));
} else {
SessionKey sessionKey = result.getSessionKey().get();
outputStream.write(sessionKey.getAlgorithm());
@ -217,7 +209,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.withSessionKey(new SessionKey(algorithm, key));
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-session-key"), unsupportedOption);
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_SESSION_KEY), unsupportedOption);
}
}
}
@ -228,7 +220,7 @@ public class DecryptCmd implements Runnable {
String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile));
decrypt.withPassword(password);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-password"), unsupportedOption);
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_PASSWORD), unsupportedOption);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -241,7 +233,7 @@ public class DecryptCmd implements Runnable {
String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile));
decrypt.withKeyPassword(password);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-key-password"), unsupportedOption);
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_WITH_KEY_PASSWORD), unsupportedOption);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -253,7 +245,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.verifyNotAfter(notAfterDate);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--not-after"), unsupportedOption);
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_NOT_AFTER), unsupportedOption);
}
}
@ -262,7 +254,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.verifyNotBefore(notBeforeDate);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--not-before"), unsupportedOption);
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, OPT_NOT_BEFORE), unsupportedOption);
}
}
}

View file

@ -22,7 +22,7 @@ import sop.operation.Encrypt;
@CommandLine.Command(name = "encrypt",
description = "Encrypt a message from standard input",
exitCodeOnInvalidInput = 37)
public class EncryptCmd implements Runnable {
public class EncryptCmd extends AbstractSopCmd {
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
@ -31,7 +31,7 @@ public class EncryptCmd implements Runnable {
@CommandLine.Option(names = {"--as"},
description = "Type of the input data. Defaults to 'binary'",
paramLabel = "{binary|text|mime}")
paramLabel = "{binary|text}")
EncryptAs type;
@CommandLine.Option(names = "--with-password",
@ -44,6 +44,11 @@ public class EncryptCmd implements Runnable {
paramLabel = "KEY")
List<File> signWith = new ArrayList<>();
@CommandLine.Option(names = "--with-key-password",
description = "Provide indirect file type pointing at passphrase(s) for secret key(s)",
paramLabel = "PASSWORD")
List<String> withKeyPassword = new ArrayList<>();
@CommandLine.Parameters(description = "Certificates the message gets encrypted to",
index = "0..*",
paramLabel = "CERTS")
@ -51,10 +56,8 @@ public class EncryptCmd implements Runnable {
@Override
public void run() {
Encrypt encrypt = SopCLI.getSop().encrypt();
if (encrypt == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'encrypt' not implemented.");
}
Encrypt encrypt = throwIfUnsupportedSubcommand(
SopCLI.getSop().encrypt(), "encrypt");
if (type != null) {
try {
@ -79,6 +82,17 @@ public class EncryptCmd implements Runnable {
}
}
for (String passwordFileName : withKeyPassword) {
try {
String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFileName));
encrypt.withKeyPassword(password);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption("Unsupported option '--with-key-password'.", unsupportedOption);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (File keyFile : signWith) {
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
encrypt.signWith(keyIn);

View file

@ -15,7 +15,7 @@ import sop.operation.ExtractCert;
@CommandLine.Command(name = "extract-cert",
description = "Extract a public key certificate from a secret key from standard input",
exitCodeOnInvalidInput = 37)
public class ExtractCertCmd implements Runnable {
public class ExtractCertCmd extends AbstractSopCmd {
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
@ -24,10 +24,8 @@ public class ExtractCertCmd implements Runnable {
@Override
public void run() {
ExtractCert extractCert = SopCLI.getSop().extractCert();
if (extractCert == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'extract-cert' not implemented.");
}
ExtractCert extractCert = throwIfUnsupportedSubcommand(
SopCLI.getSop().extractCert(), "extract-cert");
if (!armor) {
extractCert.noArmor();

View file

@ -19,7 +19,7 @@ import sop.operation.GenerateKey;
@CommandLine.Command(name = "generate-key",
description = "Generate a secret key",
exitCodeOnInvalidInput = 37)
public class GenerateKeyCmd implements Runnable {
public class GenerateKeyCmd extends AbstractSopCmd {
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
@ -36,10 +36,8 @@ public class GenerateKeyCmd implements Runnable {
@Override
public void run() {
GenerateKey generateKey = SopCLI.getSop().generateKey();
if (generateKey == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'generate-key' not implemented.");
}
GenerateKey generateKey = throwIfUnsupportedSubcommand(
SopCLI.getSop().generateKey(), "generate-key");
for (String userId : userId) {
generateKey.userId(userId);

View file

@ -14,10 +14,10 @@ import sop.cli.picocli.SopCLI;
import sop.exception.SOPGPException;
import sop.operation.InlineDetach;
@CommandLine.Command(name = "detach-inband-signature-and-message",
@CommandLine.Command(name = "inline-detach",
description = "Split a clearsigned message",
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
public class InlineDetachCmd implements Runnable {
public class InlineDetachCmd extends AbstractSopCmd {
@CommandLine.Option(
names = {"--signatures-out"},
@ -32,25 +32,20 @@ public class InlineDetachCmd implements Runnable {
@Override
public void run() {
InlineDetach detach = SopCLI.getSop().detachInbandSignatureAndMessage();
if (detach == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'detach-inband-signature-and-message' not implemented.");
}
InlineDetach inlineDetach = throwIfUnsupportedSubcommand(
SopCLI.getSop().inlineDetach(), "inline-detach");
if (signaturesOut == null) {
throw new SOPGPException.MissingArg("--signatures-out is required.");
}
throwIfOutputExists(signaturesOut, "--signatures-out");
throwIfMissingArg(signaturesOut, "--signatures-out");
if (!armor) {
detach.noArmor();
inlineDetach.noArmor();
}
try {
Signatures signatures = detach
Signatures signatures = inlineDetach
.message(System.in).writeTo(System.out);
if (!signaturesOut.createNewFile()) {
throw new SOPGPException.OutputExists("Destination of --signatures-out already exists.");
}
signaturesOut.createNewFile();
signatures.writeTo(new FileOutputStream(signaturesOut));
} catch (IOException e) {
throw new RuntimeException(e);

View file

@ -0,0 +1,133 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package sop.cli.picocli.commands;
import picocli.CommandLine;
import sop.MicAlg;
import sop.ReadyWithResult;
import sop.SigningResult;
import sop.cli.picocli.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.util.ArrayList;
import java.util.List;
@CommandLine.Command(name = "inline-sign",
description = "Create an inline-signed message from data on standard input",
exitCodeOnInvalidInput = 37)
public class InlineSignCmd extends AbstractSopCmd {
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
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.",
paramLabel = "{binary|text|cleartextsigned}")
InlineSignAs type;
@CommandLine.Parameters(description = "Secret keys used for signing",
paramLabel = "KEYS")
List<File> secretKeyFile = new ArrayList<>();
@CommandLine.Option(names = "--with-key-password", description = "Password(s) to unlock the secret key(s) with",
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)",
paramLabel = "MICALG")
File micAlgOut;
@Override
public void run() {
InlineSign inlineSign = throwIfUnsupportedSubcommand(
SopCLI.getSop().inlineSign(), "inline-sign");
throwIfOutputExists(micAlgOut, "--micalg-out");
if (type != null) {
try {
inlineSign.mode(type);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--as'");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
}
}
if (secretKeyFile.isEmpty()) {
Print.errln("Missing required parameter 'KEYS'.");
System.exit(19);
}
for (String passwordFile : withKeyPassword) {
try {
String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile));
inlineSign.withKeyPassword(password);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-key-password"), unsupportedOption);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (File keyFile : secretKeyFile) {
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
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);
} catch (SOPGPException.KeyIsProtected e) {
Print.errln("Key " + keyFile.getName() + " is password protected.");
Print.trace(e);
System.exit(1);
} catch (SOPGPException.BadData badData) {
Print.errln("Bad data in key file " + keyFile.getAbsolutePath() + ":");
Print.trace(badData);
System.exit(badData.getExitCode());
}
}
if (!armor) {
inlineSign.noArmor();
}
try {
ReadyWithResult<SigningResult> ready = inlineSign.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();
}
} 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());
}
}
}

View file

@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
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.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@CommandLine.Command(name = "inline-verify",
description = "Verify inline-signed data from standard input",
exitCodeOnInvalidInput = 37)
public class InlineVerifyCmd extends AbstractSopCmd {
@CommandLine.Parameters(arity = "1..*",
description = "Public key certificates",
paramLabel = "CERT")
List<File> 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 (\"-\").",
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.",
paramLabel = "DATE")
String notAfter = "now";
@CommandLine.Option(names = "--verifications-out",
description = "File to write details over successful verifications to")
File verificationsOut;
@Override
public void run() {
InlineVerify inlineVerify = throwIfUnsupportedSubcommand(
SopCLI.getSop().inlineVerify(), "inline-verify");
throwIfOutputExists(verificationsOut, "--verifications-out");
if (notAfter != null) {
try {
inlineVerify.notAfter(DateParser.parseNotAfter(notAfter));
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--not-after'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
}
}
if (notBefore != null) {
try {
inlineVerify.notBefore(DateParser.parseNotBefore(notBefore));
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--not-before'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
}
}
for (File certFile : certificates) {
try (FileInputStream certIn = new FileInputStream(certFile)) {
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);
} 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());
}
}
List<Verification> verifications = null;
try {
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());
} catch (IOException ioException) {
Print.errln("IO Error.");
Print.trace(ioException);
System.exit(1);
} catch (SOPGPException.BadData badData) {
Print.errln("Standard Input appears not to contain a valid OpenPGP message.");
Print.trace(badData);
System.exit(badData.getExitCode());
}
if (verificationsOut != null) {
try {
verificationsOut.createNewFile();
PrintWriter pw = new PrintWriter(verificationsOut);
for (Verification verification : verifications) {
// CHECKSTYLE:OFF
pw.println(verification);
// CHECKSTYLE:ON
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -21,15 +21,12 @@ import sop.cli.picocli.Print;
import sop.cli.picocli.SopCLI;
import sop.enums.SignAs;
import sop.exception.SOPGPException;
import sop.operation.Decrypt;
import sop.operation.Sign;
@CommandLine.Command(name = "sign",
description = "Create a detached signature on the data from standard input",
exitCodeOnInvalidInput = 37)
public class SignCmd implements Runnable {
private static final String ERROR_UNSUPPORTED_OPTION = "Option '%s' is not supported.";
public class SignCmd extends AbstractSopCmd {
@CommandLine.Option(names = "--no-armor",
description = "ASCII armor the output",
@ -54,10 +51,11 @@ public class SignCmd implements Runnable {
@Override
public void run() {
Sign sign = SopCLI.getSop().sign();
if (sign == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'sign' not implemented.");
}
Sign sign = throwIfUnsupportedSubcommand(
SopCLI.getSop().sign(), "sign");
throwIfOutputExists(micAlgOut, "--micalg-out");
throwIfEmptyParameters(secretKeyFile, "KEYS");
if (type != null) {
try {
@ -69,15 +67,6 @@ public class SignCmd implements Runnable {
}
}
if (micAlgOut != null && micAlgOut.exists()) {
throw new SOPGPException.OutputExists(String.format("Target %s of option %s already exists.", micAlgOut.getAbsolutePath(), "--micalg-out"));
}
if (secretKeyFile.isEmpty()) {
Print.errln("Missing required parameter 'KEYS'.");
System.exit(19);
}
for (String passwordFile : withKeyPassword) {
try {
String password = FileUtil.stringFromInputStream(FileUtil.getFileInputStream(passwordFile));

View file

@ -22,14 +22,14 @@ import sop.operation.Verify;
@CommandLine.Command(name = "verify",
description = "Verify a detached signature over the data from standard input",
exitCodeOnInvalidInput = 37)
public class VerifyCmd implements Runnable {
public class VerifyCmd extends AbstractSopCmd {
@CommandLine.Parameters(index = "0",
description = "Detached signature",
paramLabel = "SIGNATURE")
File signature;
@CommandLine.Parameters(index = "1..*",
@CommandLine.Parameters(index = "0..*",
arity = "1..*",
description = "Public key certificates",
paramLabel = "CERT")
@ -52,10 +52,8 @@ public class VerifyCmd implements Runnable {
@Override
public void run() {
Verify verify = SopCLI.getSop().verify();
if (verify == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'verify' not implemented.");
}
Verify verify = throwIfUnsupportedSubcommand(
SopCLI.getSop().verify(), "verify");
if (notAfter != null) {
try {

View file

@ -7,12 +7,11 @@ package sop.cli.picocli.commands;
import picocli.CommandLine;
import sop.cli.picocli.Print;
import sop.cli.picocli.SopCLI;
import sop.exception.SOPGPException;
import sop.operation.Version;
@CommandLine.Command(name = "version", description = "Display version information about the tool",
exitCodeOnInvalidInput = 37)
public class VersionCmd implements Runnable {
public class VersionCmd extends AbstractSopCmd {
@CommandLine.ArgGroup()
Exclusive exclusive;
@ -29,10 +28,8 @@ public class VersionCmd implements Runnable {
@Override
public void run() {
Version version = SopCLI.getSop().version();
if (version == null) {
throw new SOPGPException.UnsupportedSubcommand("Command 'version' not implemented.");
}
Version version = throwIfUnsupportedSubcommand(
SopCLI.getSop().version(), "version");
if (exclusive == null) {
Print.outln(version.getName() + " " + version.getVersion());

View file

@ -21,6 +21,8 @@ import sop.operation.InlineDetach;
import sop.operation.Encrypt;
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.Version;
@ -93,7 +95,17 @@ public class SOPTest {
}
@Override
public InlineDetach detachInbandSignatureAndMessage() {
public InlineDetach inlineDetach() {
return null;
}
@Override
public InlineSign inlineSign() {
return null;
}
@Override
public InlineVerify inlineVerify() {
return null;
}
};
@ -103,7 +115,7 @@ public class SOPTest {
commands.add(new String[] {"armor"});
commands.add(new String[] {"dearmor"});
commands.add(new String[] {"decrypt"});
commands.add(new String[] {"detach-inband-signature-and-message"});
commands.add(new String[] {"inline-detach", "--signatures-out", "sigs.asc"});
commands.add(new String[] {"encrypt"});
commands.add(new String[] {"extract-cert"});
commands.add(new String[] {"generate-key"});

View file

@ -7,10 +7,12 @@ package sop;
import sop.operation.Armor;
import sop.operation.Dearmor;
import sop.operation.Decrypt;
import sop.operation.InlineDetach;
import sop.operation.Encrypt;
import sop.operation.ExtractCert;
import sop.operation.GenerateKey;
import sop.operation.InlineDetach;
import sop.operation.InlineSign;
import sop.operation.InlineVerify;
import sop.operation.Sign;
import sop.operation.Verify;
import sop.operation.Version;
@ -91,5 +93,9 @@ public interface SOP {
*/
Dearmor dearmor();
InlineDetach detachInbandSignatureAndMessage();
InlineDetach inlineDetach();
InlineSign inlineSign();
InlineVerify inlineVerify();
}

View file

@ -49,7 +49,7 @@ public interface AbstractSign<T> {
}
/**
* Provide the decryption password for the secret key.
* Provide the password for the secret key used for signing.
*
* @param password password
* @return builder instance
@ -59,7 +59,7 @@ public interface AbstractSign<T> {
}
/**
* Provide the decryption password for the secret key.
* Provide the password for the secret key used for signing.
*
* @param password password
* @return builder instance

View file

@ -7,6 +7,7 @@ package sop.operation;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import sop.Ready;
import sop.enums.EncryptAs;
@ -68,6 +69,24 @@ public interface Encrypt {
return signWith(new ByteArrayInputStream(key));
}
/**
* Provide the password for the secret key used for signing.
*
* @param password password
* @return builder instance
*/
default Encrypt withKeyPassword(String password) {
return withKeyPassword(password.getBytes(Charset.forName("UTF8")));
}
/**
* Provide the password for the secret key used for sigining.
*
* @param password password
* @return builder instance
*/
Encrypt withKeyPassword(byte[] password);
/**
* Encrypt with the given password.
*