Wip: Adopt changes from the SOP specification version 2

This commit is contained in:
Paul Schaub 2021-07-19 18:20:52 +02:00
parent 829068d5a8
commit 788c82531e
17 changed files with 328 additions and 217 deletions

View File

@ -101,7 +101,6 @@ public class EncryptDecryptTest {
msgAscOut.close();
File verifyFile = new File(tempDir, "verify.txt");
assertTrue(verifyFile.createNewFile());
FileInputStream msgAscIn = new FileInputStream(msgAscFile);
System.setIn(msgAscIn);

View File

@ -38,7 +38,7 @@ public class ArmorImpl implements Armor {
@Override
public Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption {
throw new SOPGPException.UnsupportedOption();
throw new SOPGPException.UnsupportedOption("Setting custom Armor labels not supported.");
}
@Override

View File

@ -87,7 +87,7 @@ public class DecryptImpl implements Decrypt {
@Override
public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption {
throw new SOPGPException.UnsupportedOption();
throw new SOPGPException.UnsupportedOption("Setting custom session key not supported.");
}
@Override

View File

@ -87,7 +87,7 @@ public class GenerateKeyImpl implements GenerateKey {
}
};
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
throw new SOPGPException.UnsupportedAsymmetricAlgo(e);
throw new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", e);
} catch (PGPException e) {
throw new RuntimeException(e);
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sop.cli.picocli;
import picocli.CommandLine;
import sop.exception.SOPGPException;
public class SOPExceptionExitCodeMapper implements CommandLine.IExitCodeExceptionMapper {
@Override
public int getExitCode(Throwable exception) {
if (exception instanceof SOPGPException) {
return ((SOPGPException) exception).getExitCode();
}
if (exception instanceof CommandLine.UnmatchedArgumentException) {
CommandLine.UnmatchedArgumentException ex = (CommandLine.UnmatchedArgumentException) exception;
// Unmatched option of subcommand (eg. `generate-key -k`)
if (ex.isUnknownOption()) {
return SOPGPException.UnsupportedOption.EXIT_CODE;
}
// Unmatched subcommand
return SOPGPException.UnsupportedSubcommand.EXIT_CODE;
}
// Invalid option (eg. `--label Invalid`)
if (exception instanceof CommandLine.ParameterException) {
return SOPGPException.UnsupportedOption.EXIT_CODE;
}
// Others, like IOException etc.
return 1;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sop.cli.picocli;
import picocli.CommandLine;
public class SOPExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler {
@Override
public int handleExecutionException(Exception ex, CommandLine commandLine, CommandLine.ParseResult parseResult) throws Exception {
int exitCode = commandLine.getExitCodeExceptionMapper() != null ?
commandLine.getExitCodeExceptionMapper().getExitCode(ex) :
commandLine.getCommandSpec().exitCodeOnExecutionException();
CommandLine.Help.ColorScheme colorScheme = commandLine.getColorScheme();
// CHECKSTYLE:OFF
if (ex.getMessage() != null) {
commandLine.getErr().println(colorScheme.errorText(ex.getMessage()));
}
ex.printStackTrace(commandLine.getErr());
// CHECKSTYLE:ON
return exitCode;
}
}

View File

@ -58,6 +58,8 @@ public class SopCLI {
public static int execute(String[] args) {
return new CommandLine(SopCLI.class)
.setCommandName(EXECUTABLE_NAME)
.setExecutionExceptionHandler(new SOPExecutionExceptionHandler())
.setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper())
.setCaseInsensitiveEnumValuesAllowed(true)
.execute(args);
}

View File

@ -32,7 +32,6 @@ import sop.ReadyWithResult;
import sop.SessionKey;
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.Decrypt;
@ -93,9 +92,13 @@ public class DecryptCmd implements Runnable {
@Override
public void run() {
unlinkExistingVerifyOut(verifyOut);
throwIfVerifyOutExists(verifyOut);
Decrypt decrypt = SopCLI.getSop().decrypt();
if (decrypt == null) {
throw new SOPGPException.UnsupportedSubcommand("Subcommand 'decrypt' not implemented.");
}
setNotAfter(notAfter, decrypt);
setNotBefore(notBefore, decrypt);
setWithPasswords(withPassword, decrypt);
@ -104,65 +107,63 @@ public class DecryptCmd implements Runnable {
setDecryptWith(keys, decrypt);
if (verifyOut != null && certs.isEmpty()) {
Print.errln("--verify-out is requested, but no --verify-with was provided.");
System.exit(23);
throw new SOPGPException.IncompleteVerification("--verify-out is requested, but no --verify-with was provided.");
}
try {
ReadyWithResult<DecryptionResult> ready = decrypt.ciphertext(System.in);
DecryptionResult result = ready.writeTo(System.out);
if (sessionKeyOut != null) {
if (sessionKeyOut.exists()) {
Print.errln("File " + sessionKeyOut.getAbsolutePath() + " already exists.");
Print.trace(new SOPGPException.OutputExists());
System.exit(1);
}
try (FileOutputStream outputStream = new FileOutputStream(sessionKeyOut)) {
if (!result.getSessionKey().isPresent()) {
Print.errln("Session key not extracted. Possibly the feature is not supported.");
System.exit(SOPGPException.UnsupportedOption.EXIT_CODE);
} else {
SessionKey sessionKey = result.getSessionKey().get();
outputStream.write(sessionKey.getAlgorithm());
outputStream.write(sessionKey.getKey());
}
}
}
if (verifyOut != null) {
if (!verifyOut.createNewFile()) {
throw new IOException("Cannot create file " + verifyOut.getAbsolutePath());
}
try (FileOutputStream outputStream = new FileOutputStream(verifyOut)) {
PrintWriter writer = new PrintWriter(outputStream);
for (Verification verification : result.getVerifications()) {
// CHECKSTYLE:OFF
writer.println(verification.toString());
// CHECKSTYLE:ON
}
writer.flush();
}
}
writeSessionKeyOut(result);
writeVerifyOut(result);
} catch (SOPGPException.BadData badData) {
Print.errln("No valid OpenPGP message found on Standard Input.");
Print.trace(badData);
System.exit(badData.getExitCode());
} catch (SOPGPException.MissingArg missingArg) {
Print.errln("Missing arguments.");
Print.trace(missingArg);
System.exit(missingArg.getExitCode());
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
} catch (SOPGPException.NoSignature noSignature) {
Print.errln("No verifiable signature found.");
Print.trace(noSignature);
System.exit(noSignature.getExitCode());
} catch (SOPGPException.CannotDecrypt cannotDecrypt) {
Print.errln("Cannot decrypt.");
Print.trace(cannotDecrypt);
System.exit(cannotDecrypt.getExitCode());
throw new SOPGPException.BadData("No valid OpenPGP message found on Standard Input.", badData);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
}
}
private void writeVerifyOut(DecryptionResult result) throws IOException {
if (verifyOut != null) {
if (!verifyOut.createNewFile()) {
throw new IOException("Cannot create file " + verifyOut.getAbsolutePath());
}
try (FileOutputStream outputStream = new FileOutputStream(verifyOut)) {
PrintWriter writer = new PrintWriter(outputStream);
for (Verification verification : result.getVerifications()) {
// CHECKSTYLE:OFF
writer.println(verification.toString());
// CHECKSTYLE:ON
}
writer.flush();
}
}
}
private void writeSessionKeyOut(DecryptionResult result) throws IOException {
if (sessionKeyOut != null) {
if (sessionKeyOut.exists()) {
throw new SOPGPException.OutputExists("Target " + sessionKeyOut.getAbsolutePath() + " of option --session-key-out already exists.");
}
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.");
} else {
SessionKey sessionKey = result.getSessionKey().get();
outputStream.write(sessionKey.getAlgorithm());
outputStream.write(sessionKey.getKey());
}
}
}
}
private void throwIfVerifyOutExists(File verifyOut) throws SOPGPException.OutputExists {
if (verifyOut == null) {
return;
}
if (verifyOut.exists()) {
throw new SOPGPException.OutputExists("Target " + verifyOut.getAbsolutePath() + " of option --verify-out already exists.");
}
}
@ -171,25 +172,13 @@ public class DecryptCmd implements Runnable {
try (FileInputStream keyIn = new FileInputStream(key)) {
decrypt.withKey(keyIn);
} catch (SOPGPException.KeyIsProtected keyIsProtected) {
Print.errln("Key in file " + key.getAbsolutePath() + " is password protected.");
Print.trace(keyIsProtected);
System.exit(1);
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
Print.errln("Key uses unsupported asymmetric algorithm.");
Print.trace(unsupportedAsymmetricAlgo);
System.exit(unsupportedAsymmetricAlgo.getExitCode());
throw new SOPGPException.KeyIsProtected("Key in file " + key.getAbsolutePath() + " is password protected.", keyIsProtected);
} catch (SOPGPException.BadData badData) {
Print.errln("File " + key.getAbsolutePath() + " does not contain a private key.");
Print.trace(badData);
System.exit(badData.getExitCode());
throw new SOPGPException.BadData("File " + key.getAbsolutePath() + " does not contain a private key.", badData);
} catch (FileNotFoundException e) {
Print.errln("File " + key.getAbsolutePath() + " does not exist.");
Print.trace(e);
System.exit(1);
throw new SOPGPException.MissingInput("File " + key.getAbsolutePath() + " does not exist.", e);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new RuntimeException(e);
}
}
}
@ -199,30 +188,11 @@ public class DecryptCmd implements Runnable {
try (FileInputStream certIn = new FileInputStream(cert)) {
decrypt.verifyWithCert(certIn);
} catch (FileNotFoundException e) {
Print.errln("File " + cert.getAbsolutePath() + " does not exist.");
Print.trace(e);
System.exit(1);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new SOPGPException.MissingInput("File " + cert.getAbsolutePath() + " does not exist.", e);
} catch (SOPGPException.BadData badData) {
Print.errln("File " + cert.getAbsolutePath() + " does not contain a valid certificate.");
Print.trace(badData);
System.exit(badData.getExitCode());
}
}
}
private void unlinkExistingVerifyOut(File verifyOut) {
if (verifyOut == null) {
return;
}
if (verifyOut.exists()) {
if (!verifyOut.delete()) {
Print.errln("Cannot delete existing verification file" + verifyOut.getAbsolutePath());
System.exit(1);
throw new SOPGPException.BadData("File " + cert.getAbsolutePath() + " does not contain a valid certificate.", badData);
} catch (IOException ioException) {
throw new RuntimeException(ioException);
}
}
}
@ -231,9 +201,7 @@ public class DecryptCmd implements Runnable {
Pattern sessionKeyPattern = Pattern.compile("^\\d+:[0-9A-F]+$");
for (String sessionKey : withSessionKey) {
if (!sessionKeyPattern.matcher(sessionKey).matches()) {
Print.errln("Invalid session key format.");
Print.errln("Session keys are expected in the format 'ALGONUM:HEXKEY'");
System.exit(1);
throw new IllegalArgumentException("Session keys are expected in the format 'ALGONUM:HEXKEY'.");
}
String[] split = sessionKey.split(":");
byte algorithm = (byte) Integer.parseInt(split[0]);
@ -242,10 +210,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.withSessionKey(new SessionKey(algorithm, key));
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--with-session-key'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
return;
throw new SOPGPException.UnsupportedOption("Unsupported option '--with-session-key'.", unsupportedOption);
}
}
}
@ -254,14 +219,8 @@ public class DecryptCmd implements Runnable {
for (String password : withPassword) {
try {
decrypt.withPassword(password);
} catch (SOPGPException.PasswordNotHumanReadable passwordNotHumanReadable) {
Print.errln("Password not human readable.");
Print.trace(passwordNotHumanReadable);
System.exit(passwordNotHumanReadable.getExitCode());
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--with-password'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
throw new SOPGPException.UnsupportedOption("Unsupported option '--with-password'.", unsupportedOption);
}
}
}
@ -271,9 +230,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.verifyNotAfter(notAfterDate);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Option '--not-after' not supported.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
throw new SOPGPException.UnsupportedOption("Option '--not-after' not supported.", unsupportedOption);
}
}
@ -282,9 +239,7 @@ public class DecryptCmd implements Runnable {
try {
decrypt.verifyNotBefore(notBeforeDate);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Option '--not-before' not supported.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
throw new SOPGPException.UnsupportedOption("Option '--not-before' not supported.", unsupportedOption);
}
}
}

View File

@ -24,7 +24,6 @@ import java.util.List;
import picocli.CommandLine;
import sop.Ready;
import sop.cli.picocli.Print;
import sop.cli.picocli.SopCLI;
import sop.enums.EncryptAs;
import sop.exception.SOPGPException;
@ -67,28 +66,19 @@ public class EncryptCmd implements Runnable {
try {
encrypt.mode(type);
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--as'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
throw new SOPGPException.UnsupportedOption("Unsupported option '--as'.", unsupportedOption);
}
}
if (withPassword.isEmpty() && certs.isEmpty()) {
Print.errln("At least one password or cert file required for encryption.");
System.exit(19);
throw new SOPGPException.MissingArg("At least one password or cert file required for encryption.");
}
for (String password : withPassword) {
try {
encrypt.withPassword(password);
} catch (SOPGPException.PasswordNotHumanReadable passwordNotHumanReadable) {
Print.errln("Password is not human-readable.");
Print.trace(passwordNotHumanReadable);
System.exit(passwordNotHumanReadable.getExitCode());
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
Print.errln("Unsupported option '--with-password'.");
Print.trace(unsupportedOption);
System.exit(unsupportedOption.getExitCode());
throw new SOPGPException.UnsupportedOption("Unsupported option '--with-password'.", unsupportedOption);
}
}
@ -96,29 +86,17 @@ public class EncryptCmd implements Runnable {
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
encrypt.signWith(keyIn);
} catch (FileNotFoundException e) {
Print.errln("Key file " + keyFile.getAbsolutePath() + " not found.");
Print.trace(e);
System.exit(1);
throw new SOPGPException.MissingInput("Key file " + keyFile.getAbsolutePath() + " not found.", e);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new RuntimeException(e);
} catch (SOPGPException.KeyIsProtected keyIsProtected) {
Print.errln("Key from " + keyFile.getAbsolutePath() + " is password protected.");
Print.trace(keyIsProtected);
System.exit(1);
throw new SOPGPException.KeyIsProtected("Key from " + keyFile.getAbsolutePath() + " is password protected.", keyIsProtected);
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
Print.errln("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.");
Print.trace(unsupportedAsymmetricAlgo);
System.exit(unsupportedAsymmetricAlgo.getExitCode());
throw new SOPGPException.UnsupportedAsymmetricAlgo("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo);
} catch (SOPGPException.CertCannotSign certCannotSign) {
Print.errln("Key from " + keyFile.getAbsolutePath() + " cannot sign.");
Print.trace(certCannotSign);
System.exit(1);
throw new RuntimeException("Key from " + keyFile.getAbsolutePath() + " cannot sign.", certCannotSign);
} catch (SOPGPException.BadData badData) {
Print.errln("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.");
Print.trace(badData);
System.exit(badData.getExitCode());
throw new SOPGPException.BadData("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.", badData);
}
}
@ -126,25 +104,15 @@ public class EncryptCmd implements Runnable {
try (FileInputStream certIn = new FileInputStream(certFile)) {
encrypt.withCert(certIn);
} catch (FileNotFoundException e) {
Print.errln("Certificate file " + certFile.getAbsolutePath() + " not found.");
Print.trace(e);
System.exit(1);
throw new SOPGPException.MissingInput("Certificate file " + certFile.getAbsolutePath() + " not found.", e);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new RuntimeException(e);
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
Print.errln("Certificate from " + certFile.getAbsolutePath() + " has unsupported asymmetric algorithm.");
Print.trace(unsupportedAsymmetricAlgo);
System.exit(unsupportedAsymmetricAlgo.getExitCode());
throw new SOPGPException.UnsupportedAsymmetricAlgo("Certificate from " + certFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo);
} catch (SOPGPException.CertCannotEncrypt certCannotEncrypt) {
Print.errln("Certificate from " + certFile.getAbsolutePath() + " is not capable of encryption.");
Print.trace(certCannotEncrypt);
System.exit(certCannotEncrypt.getExitCode());
throw new SOPGPException.CertCannotEncrypt("Certificate from " + certFile.getAbsolutePath() + " is not capable of encryption.", certCannotEncrypt);
} catch (SOPGPException.BadData badData) {
Print.errln("Certificate file " + certFile.getAbsolutePath() + " does not contain a valid OpenPGP certificate.");
Print.trace(badData);
System.exit(badData.getExitCode());
throw new SOPGPException.BadData("Certificate file " + certFile.getAbsolutePath() + " does not contain a valid OpenPGP certificate.", badData);
}
}
@ -156,9 +124,7 @@ public class EncryptCmd implements Runnable {
Ready ready = encrypt.plaintext(System.in);
ready.writeTo(System.out);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new RuntimeException(e);
}
}
}

View File

@ -19,7 +19,6 @@ import java.io.IOException;
import picocli.CommandLine;
import sop.Ready;
import sop.cli.picocli.Print;
import sop.cli.picocli.SopCLI;
import sop.exception.SOPGPException;
import sop.operation.ExtractCert;
@ -45,13 +44,9 @@ public class ExtractCertCmd implements Runnable {
Ready ready = extractCert.key(System.in);
ready.writeTo(System.out);
} catch (IOException e) {
Print.errln("IO Error.");
Print.trace(e);
System.exit(1);
throw new RuntimeException(e);
} catch (SOPGPException.BadData badData) {
Print.errln("Standard Input does not contain valid OpenPGP private key material.");
Print.trace(badData);
System.exit(badData.getExitCode());
throw new SOPGPException.BadData("Standard Input does not contain valid OpenPGP private key material.", badData);
}
}
}

View File

@ -92,7 +92,7 @@ public class ArmorCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void ifLabelsUnsupportedExit37() throws SOPGPException.UnsupportedOption {
when(armor.label(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(armor.label(any())).thenThrow(new SOPGPException.UnsupportedOption("Custom Armor labels are not supported."));
SopCLI.main(new String[] {"armor", "--label", "Sig"});
}
@ -100,7 +100,7 @@ public class ArmorCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void ifAllowNestedUnsupportedExit37() throws SOPGPException.UnsupportedOption {
when(armor.allowNested()).thenThrow(new SOPGPException.UnsupportedOption());
when(armor.allowNested()).thenThrow(new SOPGPException.UnsupportedOption("Allowing nested Armor not supported."));
SopCLI.main(new String[] {"armor", "--allow-nested"});
}

View File

@ -27,7 +27,6 @@ import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
@ -115,7 +114,7 @@ public class DecryptCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported."));
SopCLI.main(new String[] {"decrypt", "--with-password", "swordfish"});
}
@ -168,20 +167,20 @@ public class DecryptCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption {
when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
SopCLI.main(new String[] {"decrypt", "--not-after", "now"});
}
@Test
@ExpectSystemExitWithStatus(37)
public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption {
when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
SopCLI.main(new String[] {"decrypt", "--not-before", "now"});
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertExistingSessionKeyOutFileCausesExit1() throws IOException {
@ExpectSystemExitWithStatus(59)
public void assertExistingSessionKeyOutFileCausesExit59() throws IOException {
File tempFile = File.createTempFile("existing-session-key-", ".tmp");
tempFile.deleteOnExit();
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
@ -257,19 +256,28 @@ public class DecryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(1)
public void unexistentCertFileCausesExit1() {
@ExpectSystemExitWithStatus(61)
public void unexistentCertFileCausesExit61() {
SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"});
}
@Test
public void existingVerifyOutFileIsUnlinkedBeforeVerification() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
@ExpectSystemExitWithStatus(59)
public void existingVerifyOutCausesExit59() throws IOException {
File certFile = File.createTempFile("existing-verify-out-cert", ".asc");
File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp");
byte[] data = "some data".getBytes(StandardCharsets.UTF_8);
try (FileOutputStream out = new FileOutputStream(existingVerifyOut)) {
out.write(data);
SopCLI.main(new String[] {"decrypt", "--verify-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
}
@Test
public void verifyOutIsProperlyWritten() throws IOException, SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
File certFile = File.createTempFile("verify-out-cert", ".asc");
File verifyOut = new File(certFile.getParent(), "verify-out.txt");
if (verifyOut.exists()) {
verifyOut.delete();
}
verifyOut.deleteOnExit();
Date date = UTCUtil.parseUTCDate("2021-07-11T20:58:23Z");
when(decrypt.ciphertext(any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
@Override
@ -283,8 +291,8 @@ public class DecryptCmdTest {
}
});
SopCLI.main(new String[] {"decrypt", "--verify-out", existingVerifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
try (BufferedReader reader = new BufferedReader(new FileReader(existingVerifyOut))) {
SopCLI.main(new String[] {"decrypt", "--verify-out", verifyOut.getAbsolutePath(), "--verify-with", certFile.getAbsolutePath()});
try (BufferedReader reader = new BufferedReader(new FileReader(verifyOut))) {
String line = reader.readLine();
assertEquals("2021-07-11T20:58:23Z 1B66A707819A920925BC6777C3E0AFC0B2DFF862 C8CD564EBF8D7BBA90611D8D071773658BF6BF86", line);
}
@ -317,14 +325,14 @@ public class DecryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertKeyFileNotFoundCausesExit1() {
@ExpectSystemExitWithStatus(61)
public void assertKeyFileNotFoundCausesExit61() {
SopCLI.main(new String[] {"decrypt", "nonexistent-key"});
}
@Test
@ExpectSystemExitWithStatus(1)
public void assertProtectedKeyCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
@ExpectSystemExitWithStatus(67)
public void assertProtectedKeyCausesExit67() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
when(decrypt.withKey(any())).thenThrow(new SOPGPException.KeyIsProtected());
File tempKeyFile = File.createTempFile("key-", ".tmp");
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
@ -333,7 +341,7 @@ public class DecryptCmdTest {
@Test
@ExpectSystemExitWithStatus(13)
public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
when(decrypt.withKey(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new IOException()));
when(decrypt.withKey(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new IOException()));
File tempKeyFile = File.createTempFile("key-", ".tmp");
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
}

View File

@ -65,7 +65,7 @@ public class EncryptCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption {
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported."));
SopCLI.main(new String[] {"encrypt", "--as", "Binary"});
}
@ -95,7 +95,7 @@ public class EncryptCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported."));
SopCLI.main(new String[] {"encrypt", "--with-password", "orange"});
}
@ -110,14 +110,14 @@ public class EncryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(1)
public void signWith_nonExistentKeyFileCausesExit1() {
@ExpectSystemExitWithStatus(61)
public void signWith_nonExistentKeyFileCausesExit61() {
SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"});
}
@Test
@ExpectSystemExitWithStatus(1)
public void signWith_keyIsProtectedCausesExit1() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
@ExpectSystemExitWithStatus(67)
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
when(encrypt.signWith(any())).thenThrow(new SOPGPException.KeyIsProtected());
File keyFile = File.createTempFile("sign-with", ".asc");
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", "starship"});
@ -126,7 +126,7 @@ public class EncryptCmdTest {
@Test
@ExpectSystemExitWithStatus(13)
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotSign, SOPGPException.BadData, IOException {
when(encrypt.signWith(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
when(encrypt.signWith(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
File keyFile = File.createTempFile("sign-with", ".asc");
SopCLI.main(new String[] {"encrypt", "--with-password", "123456", "--sign-with", keyFile.getAbsolutePath()});
}
@ -148,15 +148,15 @@ public class EncryptCmdTest {
}
@Test
@ExpectSystemExitWithStatus(1)
public void cert_nonExistentCertFileCausesExit1() {
@ExpectSystemExitWithStatus(61)
public void cert_nonExistentCertFileCausesExit61() {
SopCLI.main(new String[] {"encrypt", "invalid.asc"});
}
@Test
@ExpectSystemExitWithStatus(13)
public void cert_unsupportedAsymmetricAlgorithmCausesExit13() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
when(encrypt.withCert(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
when(encrypt.withCert(any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
File certFile = File.createTempFile("cert", ".asc");
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
}
@ -164,7 +164,7 @@ public class EncryptCmdTest {
@Test
@ExpectSystemExitWithStatus(17)
public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
when(encrypt.withCert(any())).thenThrow(new SOPGPException.CertCannotEncrypt());
when(encrypt.withCert(any())).thenThrow(new SOPGPException.CertCannotEncrypt("Certificate cannot encrypt.", new Exception()));
File certFile = File.createTempFile("cert", ".asc");
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
}

View File

@ -91,7 +91,7 @@ public class GenerateKeyCmdTest {
@Test
@ExpectSystemExitWithStatus(13)
public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo(new Exception()));
when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
SopCLI.main(new String[] {"generate-key", "Alice"});
}

View File

@ -74,7 +74,7 @@ public class SignCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(sign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(sign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported."));
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
}

View File

@ -106,7 +106,7 @@ public class VerifyCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(verify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(verify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
}
@ -133,7 +133,7 @@ public class VerifyCmdTest {
@Test
@ExpectSystemExitWithStatus(37)
public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
when(verify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption());
when(verify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
}

View File

@ -15,7 +15,7 @@
*/
package sop.exception;
public class SOPGPException extends Exception {
public abstract class SOPGPException extends RuntimeException {
public SOPGPException() {
super();
@ -29,10 +29,21 @@ public class SOPGPException extends Exception {
super(e);
}
public SOPGPException(String message, Throwable cause) {
super(message, cause);
}
public abstract int getExitCode();
public static class NoSignature extends SOPGPException {
public static final int EXIT_CODE = 3;
public NoSignature() {
super("No verifiable signature found.");
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -42,10 +53,15 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 13;
public UnsupportedAsymmetricAlgo(String message, Throwable e) {
super(message, e);
}
public UnsupportedAsymmetricAlgo(Throwable e) {
super(e);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -54,12 +70,17 @@ public class SOPGPException extends Exception {
public static class CertCannotEncrypt extends SOPGPException {
public static final int EXIT_CODE = 17;
public CertCannotEncrypt(String message, Throwable cause) {
super(message, cause);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class CertCannotSign extends SOPGPException {
public static class CertCannotSign extends Exception {
}
@ -71,6 +92,7 @@ public class SOPGPException extends Exception {
super(s);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -80,6 +102,11 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 23;
public IncompleteVerification(String message) {
super(message);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -89,6 +116,7 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 29;
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -98,6 +126,7 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 31;
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -107,6 +136,15 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 37;
public UnsupportedOption(String message) {
super(message);
}
public UnsupportedOption(String message, Throwable cause) {
super(message, cause);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -120,6 +158,11 @@ public class SOPGPException extends Exception {
super(e);
}
public BadData(String message, BadData badData) {
super(message, badData);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -129,6 +172,7 @@ public class SOPGPException extends Exception {
public static final int EXIT_CODE = 53;
@Override
public int getExitCode() {
return EXIT_CODE;
}
@ -136,20 +180,80 @@ public class SOPGPException extends Exception {
public static class OutputExists extends SOPGPException {
public static final int EXIT_CODE = 59;
public OutputExists(String message) {
super(message);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class MissingInput extends SOPGPException {
public static final int EXIT_CODE = 61;
public MissingInput(String message, Throwable cause) {
super(message, cause);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class KeyIsProtected extends SOPGPException {
public static final int EXIT_CODE = 67;
public KeyIsProtected() {
super();
}
public KeyIsProtected(String message, Throwable cause) {
super(message, cause);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class AmbiguousInput extends SOPGPException {
}
public static class NotImplemented extends SOPGPException {
public static class UnsupportedSubcommand extends SOPGPException {
public static final int EXIT_CODE = 69;
public UnsupportedSubcommand(String message) {
super(message);
}
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class UnsupportedSpecialPrefix extends SOPGPException {
public static final int EXIT_CODE = 71;
@Override
public int getExitCode() {
return EXIT_CODE;
}
}
public static class AmbiguousInput extends SOPGPException {
public static final int EXIT_CODE = 73;
@Override
public int getExitCode() {
return EXIT_CODE;
}