mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-25 05:42:07 +01:00
Moved sop-java and sop-java-picocli to its own repositories
See https://github.com/pgpainless/sop-java See https://codeberg.org/PGPainless/sop-java
This commit is contained in:
parent
9800ca8bd4
commit
c6bc8f9774
82 changed files with 11 additions and 5226 deletions
|
@ -30,6 +30,12 @@ allprojects {
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
|
// Only generate jar for submodules
|
||||||
|
// https://stackoverflow.com/a/25445035
|
||||||
|
jar {
|
||||||
|
onlyIf { !sourceSets.main.allSource.files.isEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
// For non-sop modules, enable android api compatibility check
|
// For non-sop modules, enable android api compatibility check
|
||||||
if (it.name.equals('pgpainless-core') || it.name.equals('sop-java') || it.name.equals('pgpainless-sop')) {
|
if (it.name.equals('pgpainless-core') || it.name.equals('sop-java') || it.name.equals('pgpainless-sop')) {
|
||||||
// animalsniffer
|
// animalsniffer
|
||||||
|
@ -68,6 +74,7 @@ allprojects {
|
||||||
logbackVersion = '1.2.9'
|
logbackVersion = '1.2.9'
|
||||||
junitVersion = '5.8.2'
|
junitVersion = '5.8.2'
|
||||||
picocliVersion = '4.6.2'
|
picocliVersion = '4.6.2'
|
||||||
|
sopJavaVersion = '1.1.0'
|
||||||
rootConfigDir = new File(rootDir, 'config')
|
rootConfigDir = new File(rootDir, 'config')
|
||||||
gitCommit = getGitCommit()
|
gitCommit = getGitCommit()
|
||||||
isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))
|
isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))
|
||||||
|
|
|
@ -40,7 +40,7 @@ dependencies {
|
||||||
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
implementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||||
|
|
||||||
implementation(project(":pgpainless-sop"))
|
implementation(project(":pgpainless-sop"))
|
||||||
implementation(project(":sop-java-picocli"))
|
implementation "org.pgpainless:sop-java-picocli:$sopJavaVersion"
|
||||||
|
|
||||||
implementation "info.picocli:picocli:$picocliVersion"
|
implementation "info.picocli:picocli:$picocliVersion"
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ dependencies {
|
||||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
||||||
|
|
||||||
implementation(project(":pgpainless-core"))
|
implementation(project(":pgpainless-core"))
|
||||||
implementation(project(":sop-java"))
|
implementation "org.pgpainless:sop-java:$sopJavaVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
rootProject.name = 'PGPainless'
|
rootProject.name = 'PGPainless'
|
||||||
|
|
||||||
include 'pgpainless-core',
|
include 'pgpainless-core',
|
||||||
'sop-java',
|
|
||||||
'pgpainless-sop',
|
'pgpainless-sop',
|
||||||
'sop-java-picocli',
|
|
||||||
'pgpainless-cli'
|
'pgpainless-cli'
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1 @@
|
||||||
<!--
|
# [MOVED](https://github.com/pgpainless/sop-java/tree/master/sop-java-picocli)
|
||||||
SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
-->
|
|
||||||
# SOP-Java-Picocli
|
|
||||||
|
|
||||||
Implementation of the [Stateless OpenPGP Command Line Interface](https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01) specification.
|
|
||||||
This terminal application allows generation of OpenPGP keys, extraction of public key certificates,
|
|
||||||
armoring and de-armoring of data, as well as - of course - encryption/decryption of messages and creation/verification of signatures.
|
|
||||||
|
|
||||||
## Install a SOP backend
|
|
||||||
|
|
||||||
This module comes without a SOP backend, so in order to function you need to extend it with an implementation of the interfaces defined in `sop-java`.
|
|
||||||
An implementation using PGPainless can be found in the module `pgpainless-sop`, but it is of course possible to provide your
|
|
||||||
own implementation.
|
|
||||||
|
|
||||||
Just install your SOP backend by calling
|
|
||||||
```java
|
|
||||||
// static method call prior to execution of the main method
|
|
||||||
SopCLI.setSopInstance(yourSopImpl);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To get an overview of available commands of the application, execute
|
|
||||||
```shell
|
|
||||||
java -jar sop-java-picocli-XXX.jar help
|
|
||||||
```
|
|
||||||
|
|
||||||
If you just want to get started encrypting messages, see the module `pgpainless-cli` which initializes
|
|
||||||
`sop-java-picocli` with `pgpainless-sop`, so you can get started right away without the need to manually wire stuff up.
|
|
||||||
|
|
||||||
Enjoy!
|
|
|
@ -1,39 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id 'application'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
|
||||||
|
|
||||||
// https://todd.ginsberg.com/post/testing-system-exit/
|
|
||||||
testImplementation 'com.ginsberg:junit5-system-exit:1.1.1'
|
|
||||||
testImplementation 'org.mockito:mockito-core:4.2.0'
|
|
||||||
|
|
||||||
implementation(project(":sop-java"))
|
|
||||||
implementation "info.picocli:picocli:$picocliVersion"
|
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
|
||||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
|
||||||
}
|
|
||||||
|
|
||||||
mainClassName = 'sop.cli.picocli.SopCLI'
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes 'Main-Class': "$mainClassName"
|
|
||||||
}
|
|
||||||
|
|
||||||
from {
|
|
||||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
|
||||||
} {
|
|
||||||
exclude "META-INF/*.SF"
|
|
||||||
exclude "META-INF/*.DSA"
|
|
||||||
exclude "META-INF/*.RSA"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,98 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
public class Print {
|
|
||||||
|
|
||||||
public static void errln(String string) {
|
|
||||||
// CHECKSTYLE:OFF
|
|
||||||
System.err.println(string);
|
|
||||||
// CHECKSTYLE:ON
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void trace(Throwable e) {
|
|
||||||
// CHECKSTYLE:OFF
|
|
||||||
e.printStackTrace();
|
|
||||||
// CHECKSTYLE:ON
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void outln(String string) {
|
|
||||||
// CHECKSTYLE:OFF
|
|
||||||
System.out.println(string);
|
|
||||||
// CHECKSTYLE:ON
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public class SOPExceptionExitCodeMapper implements CommandLine.IExitCodeExceptionMapper {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode(Throwable exception) {
|
|
||||||
if (exception instanceof SOPGPException) {
|
|
||||||
return ((SOPGPException) exception).getExitCode();
|
|
||||||
}
|
|
||||||
if (exception instanceof CommandLine.UnmatchedArgumentException) {
|
|
||||||
CommandLine.UnmatchedArgumentException ex = (CommandLine.UnmatchedArgumentException) exception;
|
|
||||||
// Unmatched option of subcommand (eg. `generate-key -k`)
|
|
||||||
if (ex.isUnknownOption()) {
|
|
||||||
return SOPGPException.UnsupportedOption.EXIT_CODE;
|
|
||||||
}
|
|
||||||
// Unmatched subcommand
|
|
||||||
return SOPGPException.UnsupportedSubcommand.EXIT_CODE;
|
|
||||||
}
|
|
||||||
// Invalid option (eg. `--label Invalid`)
|
|
||||||
if (exception instanceof CommandLine.ParameterException) {
|
|
||||||
return SOPGPException.UnsupportedOption.EXIT_CODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Others, like IOException etc.
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
public class SOPExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int handleExecutionException(Exception ex, CommandLine commandLine, CommandLine.ParseResult parseResult) {
|
|
||||||
int exitCode = commandLine.getExitCodeExceptionMapper() != null ?
|
|
||||||
commandLine.getExitCodeExceptionMapper().getExitCode(ex) :
|
|
||||||
commandLine.getCommandSpec().exitCodeOnExecutionException();
|
|
||||||
CommandLine.Help.ColorScheme colorScheme = commandLine.getColorScheme();
|
|
||||||
// CHECKSTYLE:OFF
|
|
||||||
if (ex.getMessage() != null) {
|
|
||||||
commandLine.getErr().println(colorScheme.errorText(ex.getMessage()));
|
|
||||||
}
|
|
||||||
ex.printStackTrace(commandLine.getErr());
|
|
||||||
// CHECKSTYLE:ON
|
|
||||||
|
|
||||||
return exitCode;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.commands.ArmorCmd;
|
|
||||||
import sop.cli.picocli.commands.DearmorCmd;
|
|
||||||
import sop.cli.picocli.commands.DecryptCmd;
|
|
||||||
import sop.cli.picocli.commands.DetachInbandSignatureAndMessageCmd;
|
|
||||||
import sop.cli.picocli.commands.EncryptCmd;
|
|
||||||
import sop.cli.picocli.commands.ExtractCertCmd;
|
|
||||||
import sop.cli.picocli.commands.GenerateKeyCmd;
|
|
||||||
import sop.cli.picocli.commands.SignCmd;
|
|
||||||
import sop.cli.picocli.commands.VerifyCmd;
|
|
||||||
import sop.cli.picocli.commands.VersionCmd;
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
|
||||||
exitCodeOnInvalidInput = 69,
|
|
||||||
subcommands = {
|
|
||||||
CommandLine.HelpCommand.class,
|
|
||||||
ArmorCmd.class,
|
|
||||||
DearmorCmd.class,
|
|
||||||
DecryptCmd.class,
|
|
||||||
DetachInbandSignatureAndMessageCmd.class,
|
|
||||||
EncryptCmd.class,
|
|
||||||
ExtractCertCmd.class,
|
|
||||||
GenerateKeyCmd.class,
|
|
||||||
SignCmd.class,
|
|
||||||
VerifyCmd.class,
|
|
||||||
VersionCmd.class
|
|
||||||
}
|
|
||||||
)
|
|
||||||
public class SopCLI {
|
|
||||||
// Singleton
|
|
||||||
static SOP SOP_INSTANCE;
|
|
||||||
|
|
||||||
public static String EXECUTABLE_NAME = "sop";
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
int exitCode = execute(args);
|
|
||||||
if (exitCode != 0) {
|
|
||||||
System.exit(exitCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int execute(String[] args) {
|
|
||||||
return new CommandLine(SopCLI.class)
|
|
||||||
.setCommandName(EXECUTABLE_NAME)
|
|
||||||
.setExecutionExceptionHandler(new SOPExecutionExceptionHandler())
|
|
||||||
.setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper())
|
|
||||||
.setCaseInsensitiveEnumValuesAllowed(true)
|
|
||||||
.execute(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SOP getSop() {
|
|
||||||
if (SOP_INSTANCE == null) {
|
|
||||||
throw new IllegalStateException("No SOP backend set.");
|
|
||||||
}
|
|
||||||
return SOP_INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setSopInstance(SOP instance) {
|
|
||||||
SOP_INSTANCE = instance;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.cli.picocli.Print;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.enums.ArmorLabel;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
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 {
|
|
||||||
|
|
||||||
@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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (label != null) {
|
|
||||||
try {
|
|
||||||
armor.label(label);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
Print.errln("Armor labels not supported.");
|
|
||||||
System.exit(unsupportedOption.getExitCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
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());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Print.errln("IO Error.");
|
|
||||||
Print.trace(e);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "dearmor",
|
|
||||||
description = "Remove ASCII Armor from standard input",
|
|
||||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
|
||||||
public class DearmorCmd implements Runnable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Dearmor dearmor = SopCLI.getSop().dearmor();
|
|
||||||
if (dearmor == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'dearmor' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
SopCLI.getSop()
|
|
||||||
.dearmor()
|
|
||||||
.data(System.in)
|
|
||||||
.writeTo(System.out);
|
|
||||||
} catch (SOPGPException.BadData e) {
|
|
||||||
Print.errln("Bad data.");
|
|
||||||
Print.trace(e);
|
|
||||||
System.exit(e.getExitCode());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Print.errln("IO Error.");
|
|
||||||
Print.trace(e);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2020 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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.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",
|
|
||||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
|
||||||
public class DecryptCmd implements Runnable {
|
|
||||||
|
|
||||||
private static final String SESSION_KEY_OUT = "--session-key-out";
|
|
||||||
private static final String VERIFY_OUT = "--verify-out";
|
|
||||||
|
|
||||||
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},
|
|
||||||
description = "Can be used to learn the session key on successful decryption",
|
|
||||||
paramLabel = "SESSIONKEY")
|
|
||||||
File sessionKeyOut;
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
|
||||||
names = {"--with-session-key"},
|
|
||||||
description = "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"},
|
|
||||||
description = "Enables decryption based on any \"SKESK\" packets in the \"CIPHERTEXT\"",
|
|
||||||
paramLabel = "PASSWORD")
|
|
||||||
List<String> withPassword = new ArrayList<>();
|
|
||||||
|
|
||||||
@CommandLine.Option(names = {VERIFY_OUT},
|
|
||||||
description = "Produces signature verification status to the designated file",
|
|
||||||
paramLabel = "VERIFICATIONS")
|
|
||||||
File verifyOut;
|
|
||||||
|
|
||||||
@CommandLine.Option(names = {"--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"},
|
|
||||||
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.Parameters(index = "0..*",
|
|
||||||
description = "Secret keys to attempt decryption with",
|
|
||||||
paramLabel = "KEY")
|
|
||||||
List<File> keys = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
throwIfOutputExists(verifyOut, VERIFY_OUT);
|
|
||||||
throwIfOutputExists(sessionKeyOut, SESSION_KEY_OUT);
|
|
||||||
|
|
||||||
Decrypt decrypt = SopCLI.getSop().decrypt();
|
|
||||||
if (decrypt == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'decrypt' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
setNotAfter(notAfter, decrypt);
|
|
||||||
setNotBefore(notBefore, decrypt);
|
|
||||||
setWithPasswords(withPassword, decrypt);
|
|
||||||
setWithSessionKeys(withSessionKey, decrypt);
|
|
||||||
setVerifyWith(certs, decrypt);
|
|
||||||
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, VERIFY_OUT, "--verify-with"));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ReadyWithResult<DecryptionResult> ready = decrypt.ciphertext(System.in);
|
|
||||||
DecryptionResult result = ready.writeTo(System.out);
|
|
||||||
writeSessionKeyOut(result);
|
|
||||||
writeVerifyOut(result);
|
|
||||||
} catch (SOPGPException.BadData badData) {
|
|
||||||
throw new SOPGPException.BadData("No valid OpenPGP message found on Standard Input.", badData);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
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) {
|
|
||||||
FileUtil.createNewFileOrThrow(sessionKeyOut);
|
|
||||||
|
|
||||||
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 setDecryptWith(List<File> keys, Decrypt decrypt) {
|
|
||||||
for (File key : keys) {
|
|
||||||
try (FileInputStream keyIn = new FileInputStream(key)) {
|
|
||||||
decrypt.withKey(keyIn);
|
|
||||||
} catch (SOPGPException.KeyIsProtected keyIsProtected) {
|
|
||||||
throw new SOPGPException.KeyIsProtected("Key in file " + key.getAbsolutePath() + " is password protected.", 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);
|
|
||||||
} 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)) {
|
|
||||||
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);
|
|
||||||
} catch (IOException ioException) {
|
|
||||||
throw new RuntimeException(ioException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setWithSessionKeys(List<String> withSessionKey, Decrypt decrypt) {
|
|
||||||
Pattern sessionKeyPattern = Pattern.compile("^\\d+:[0-9A-F]+$");
|
|
||||||
for (String sessionKey : withSessionKey) {
|
|
||||||
if (!sessionKeyPattern.matcher(sessionKey).matches()) {
|
|
||||||
throw new IllegalArgumentException("Session keys are expected in the format 'ALGONUM:HEXKEY'.");
|
|
||||||
}
|
|
||||||
String[] split = sessionKey.split(":");
|
|
||||||
byte algorithm = (byte) Integer.parseInt(split[0]);
|
|
||||||
byte[] key = HexUtil.hexToBytes(split[1]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
decrypt.withSessionKey(new SessionKey(algorithm, key));
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-session-key"), unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setWithPasswords(List<String> withPassword, Decrypt decrypt) {
|
|
||||||
for (String password : withPassword) {
|
|
||||||
try {
|
|
||||||
decrypt.withPassword(password);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--with-password"), unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNotAfter(String notAfter, Decrypt decrypt) {
|
|
||||||
Date notAfterDate = DateParser.parseNotAfter(notAfter);
|
|
||||||
try {
|
|
||||||
decrypt.verifyNotAfter(notAfterDate);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--not-after"), unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNotBefore(String notBefore, Decrypt decrypt) {
|
|
||||||
Date notBeforeDate = DateParser.parseNotBefore(notBefore);
|
|
||||||
try {
|
|
||||||
decrypt.verifyNotBefore(notBeforeDate);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption(String.format(ERROR_UNSUPPORTED_OPTION, "--not-before"), unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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.DetachInbandSignatureAndMessage;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "detach-inband-signature-and-message",
|
|
||||||
description = "Split a clearsigned message",
|
|
||||||
exitCodeOnInvalidInput = SOPGPException.UnsupportedOption.EXIT_CODE)
|
|
||||||
public class DetachInbandSignatureAndMessageCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.Option(
|
|
||||||
names = {"--signatures-out"},
|
|
||||||
description = "Destination to which a detached signatures block will be written",
|
|
||||||
paramLabel = "SIGNATURES")
|
|
||||||
File signaturesOut;
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--no-armor",
|
|
||||||
description = "ASCII armor the output",
|
|
||||||
negatable = true)
|
|
||||||
boolean armor = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
DetachInbandSignatureAndMessage detach = SopCLI.getSop().detachInbandSignatureAndMessage();
|
|
||||||
if (detach == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'detach-inband-signature-and-message' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signaturesOut == null) {
|
|
||||||
throw new SOPGPException.MissingArg("--signatures-out is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!armor) {
|
|
||||||
detach.noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Signatures signatures = detach
|
|
||||||
.message(System.in).writeTo(System.out);
|
|
||||||
if (!signaturesOut.createNewFile()) {
|
|
||||||
throw new SOPGPException.OutputExists("Destination of --signatures-out already exists.");
|
|
||||||
}
|
|
||||||
signatures.writeTo(new FileOutputStream(signaturesOut));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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.SopCLI;
|
|
||||||
import sop.enums.EncryptAs;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Encrypt;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "encrypt",
|
|
||||||
description = "Encrypt a message from standard input",
|
|
||||||
exitCodeOnInvalidInput = 37)
|
|
||||||
public class EncryptCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--no-armor",
|
|
||||||
description = "ASCII armor the output",
|
|
||||||
negatable = true)
|
|
||||||
boolean armor = true;
|
|
||||||
|
|
||||||
@CommandLine.Option(names = {"--as"},
|
|
||||||
description = "Type of the input data. Defaults to 'binary'",
|
|
||||||
paramLabel = "{binary|text|mime}")
|
|
||||||
EncryptAs type;
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--with-password",
|
|
||||||
description = "Encrypt the message with a password",
|
|
||||||
paramLabel = "PASSWORD")
|
|
||||||
List<String> withPassword = new ArrayList<>();
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--sign-with",
|
|
||||||
description = "Sign the output with a private key",
|
|
||||||
paramLabel = "KEY")
|
|
||||||
List<File> signWith = new ArrayList<>();
|
|
||||||
|
|
||||||
@CommandLine.Parameters(description = "Certificates the message gets encrypted to",
|
|
||||||
index = "0..*",
|
|
||||||
paramLabel = "CERTS")
|
|
||||||
List<File> certs = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Encrypt encrypt = SopCLI.getSop().encrypt();
|
|
||||||
if (encrypt == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'encrypt' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != null) {
|
|
||||||
try {
|
|
||||||
encrypt.mode(type);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption("Unsupported option '--as'.", unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withPassword.isEmpty() && certs.isEmpty()) {
|
|
||||||
throw new SOPGPException.MissingArg("At least one password or cert file required for encryption.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String password : withPassword) {
|
|
||||||
try {
|
|
||||||
encrypt.withPassword(password);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
throw new SOPGPException.UnsupportedOption("Unsupported option '--with-password'.", unsupportedOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File keyFile : signWith) {
|
|
||||||
try (FileInputStream keyIn = new FileInputStream(keyFile)) {
|
|
||||||
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);
|
|
||||||
} catch (SOPGPException.UnsupportedAsymmetricAlgo unsupportedAsymmetricAlgo) {
|
|
||||||
throw new SOPGPException.UnsupportedAsymmetricAlgo("Key from " + keyFile.getAbsolutePath() + " has unsupported asymmetric algorithm.", unsupportedAsymmetricAlgo);
|
|
||||||
} catch (SOPGPException.KeyCannotSign keyCannotSign) {
|
|
||||||
throw new SOPGPException.KeyCannotSign("Key from " + keyFile.getAbsolutePath() + " cannot sign.", keyCannotSign);
|
|
||||||
} catch (SOPGPException.BadData badData) {
|
|
||||||
throw new SOPGPException.BadData("Key file " + keyFile.getAbsolutePath() + " does not contain a valid OpenPGP private key.", badData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File certFile : certs) {
|
|
||||||
try (FileInputStream certIn = new FileInputStream(certFile)) {
|
|
||||||
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);
|
|
||||||
} catch (SOPGPException.CertCannotEncrypt certCannotEncrypt) {
|
|
||||||
throw new SOPGPException.CertCannotEncrypt("Certificate from " + certFile.getAbsolutePath() + " is not capable of encryption.", certCannotEncrypt);
|
|
||||||
} catch (SOPGPException.BadData badData) {
|
|
||||||
throw new SOPGPException.BadData("Certificate file " + certFile.getAbsolutePath() + " does not contain a valid OpenPGP certificate.", badData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!armor) {
|
|
||||||
encrypt.noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Ready ready = encrypt.plaintext(System.in);
|
|
||||||
ready.writeTo(System.out);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.ExtractCert;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "extract-cert",
|
|
||||||
description = "Extract a public key certificate from a secret key from standard input",
|
|
||||||
exitCodeOnInvalidInput = 37)
|
|
||||||
public class ExtractCertCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--no-armor",
|
|
||||||
description = "ASCII armor the output",
|
|
||||||
negatable = true)
|
|
||||||
boolean armor = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ExtractCert extractCert = SopCLI.getSop().extractCert();
|
|
||||||
if (extractCert == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'extract-cert' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!armor) {
|
|
||||||
extractCert.noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Ready ready = extractCert.key(System.in);
|
|
||||||
ready.writeTo(System.out);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (SOPGPException.BadData badData) {
|
|
||||||
throw new SOPGPException.BadData("Standard Input does not contain valid OpenPGP private key material.", badData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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.Print;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.GenerateKey;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "generate-key",
|
|
||||||
description = "Generate a secret key",
|
|
||||||
exitCodeOnInvalidInput = 37)
|
|
||||||
public class GenerateKeyCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--no-armor",
|
|
||||||
description = "ASCII armor the output",
|
|
||||||
negatable = true)
|
|
||||||
boolean armor = true;
|
|
||||||
|
|
||||||
@CommandLine.Parameters(description = "User-ID, eg. \"Alice <alice@example.com>\"")
|
|
||||||
List<String> userId = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
GenerateKey generateKey = SopCLI.getSop().generateKey();
|
|
||||||
if (generateKey == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'generate-key' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String userId : userId) {
|
|
||||||
generateKey.userId(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!armor) {
|
|
||||||
generateKey.noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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.Print;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.enums.SignAs;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
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 {
|
|
||||||
|
|
||||||
@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, sign fails with return code 53.",
|
|
||||||
paramLabel = "{binary|text}")
|
|
||||||
SignAs type;
|
|
||||||
|
|
||||||
@CommandLine.Parameters(description = "Secret keys used for signing",
|
|
||||||
paramLabel = "KEYS")
|
|
||||||
List<File> secretKeyFile = 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() {
|
|
||||||
Sign sign = SopCLI.getSop().sign();
|
|
||||||
if (sign == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'sign' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type != null) {
|
|
||||||
try {
|
|
||||||
sign.mode(type);
|
|
||||||
} catch (SOPGPException.UnsupportedOption unsupportedOption) {
|
|
||||||
Print.errln("Unsupported option '--as'");
|
|
||||||
Print.trace(unsupportedOption);
|
|
||||||
System.exit(unsupportedOption.getExitCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (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);
|
|
||||||
} 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) {
|
|
||||||
sign.noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ReadyWithResult<SigningResult> ready = sign.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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "verify",
|
|
||||||
description = "Verify a detached signature over the data from standard input",
|
|
||||||
exitCodeOnInvalidInput = 37)
|
|
||||||
public class VerifyCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.Parameters(index = "0",
|
|
||||||
description = "Detached signature",
|
|
||||||
paramLabel = "SIGNATURE")
|
|
||||||
File signature;
|
|
||||||
|
|
||||||
@CommandLine.Parameters(index = "1..*",
|
|
||||||
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";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Verify verify = SopCLI.getSop().verify();
|
|
||||||
if (verify == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'verify' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notAfter != null) {
|
|
||||||
try {
|
|
||||||
verify.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 {
|
|
||||||
verify.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)) {
|
|
||||||
verify.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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Print.errln("IO Error.");
|
|
||||||
Print.trace(e);
|
|
||||||
System.exit(1);
|
|
||||||
} catch (SOPGPException.BadData badData) {
|
|
||||||
Print.errln("File " + signature.getAbsolutePath() + " does not contain a valid OpenPGP signature.");
|
|
||||||
Print.trace(badData);
|
|
||||||
System.exit(badData.getExitCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Verification> verifications = null;
|
|
||||||
try {
|
|
||||||
verifications = verify.data(System.in);
|
|
||||||
} 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());
|
|
||||||
}
|
|
||||||
for (Verification verification : verifications) {
|
|
||||||
Print.outln(verification.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import picocli.CommandLine;
|
|
||||||
import sop.cli.picocli.Print;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Version;
|
|
||||||
|
|
||||||
@CommandLine.Command(name = "version", description = "Display version information about the tool",
|
|
||||||
exitCodeOnInvalidInput = 37)
|
|
||||||
public class VersionCmd implements Runnable {
|
|
||||||
|
|
||||||
@CommandLine.ArgGroup()
|
|
||||||
Exclusive exclusive;
|
|
||||||
|
|
||||||
static class Exclusive {
|
|
||||||
@CommandLine.Option(names = "--extended", description = "Print an extended version string.")
|
|
||||||
boolean extended;
|
|
||||||
|
|
||||||
@CommandLine.Option(names = "--backend", description = "Print information about the cryptographic backend.")
|
|
||||||
boolean backend;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Version version = SopCLI.getSop().version();
|
|
||||||
if (version == null) {
|
|
||||||
throw new SOPGPException.UnsupportedSubcommand("Command 'version' not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusive == null) {
|
|
||||||
Print.outln(version.getName() + " " + version.getVersion());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusive.extended) {
|
|
||||||
Print.outln(version.getExtendedVersion());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusive.backend) {
|
|
||||||
Print.outln(version.getBackendVersion());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subcommands of the PGPainless SOP.
|
|
||||||
*/
|
|
||||||
package sop.cli.picocli.commands;
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of the Stateless OpenPGP Command Line Interface using Picocli.
|
|
||||||
*/
|
|
||||||
package sop.cli.picocli;
|
|
|
@ -1,49 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.util.UTCUtil;
|
|
||||||
|
|
||||||
public class DateParserTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotAfterDashReturnsEndOfTime() {
|
|
||||||
assertEquals(DateParser.END_OF_TIME, DateParser.parseNotAfter("-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotBeforeDashReturnsBeginningOfTime() {
|
|
||||||
assertEquals(DateParser.BEGINNING_OF_TIME, DateParser.parseNotBefore("-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotAfterNowReturnsNow() {
|
|
||||||
assertEquals(new Date().getTime(), DateParser.parseNotAfter("now").getTime(), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotBeforeNowReturnsNow() {
|
|
||||||
assertEquals(new Date().getTime(), DateParser.parseNotBefore("now").getTime(), 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotAfterTimestamp() {
|
|
||||||
String timestamp = "2019-10-24T23:48:29Z";
|
|
||||||
Date date = DateParser.parseNotAfter(timestamp);
|
|
||||||
assertEquals(timestamp, UTCUtil.formatUTCDate(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseNotBeforeTimestamp() {
|
|
||||||
String timestamp = "2019-10-29T18:36:45Z";
|
|
||||||
Date date = DateParser.parseNotBefore(timestamp);
|
|
||||||
assertEquals(timestamp, UTCUtil.formatUTCDate(date));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public class FileUtilTest {
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public static void setup() {
|
|
||||||
FileUtil.setEnvironmentVariableResolver(new FileUtil.EnvironmentVariableResolver() {
|
|
||||||
@Override
|
|
||||||
public String resolveEnvironmentVariable(String name) {
|
|
||||||
if (name.equals("test123")) {
|
|
||||||
return "test321";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_ThrowsForNull() {
|
|
||||||
assertThrows(NullPointerException.class, () -> FileUtil.getFile(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_prfxEnvAlreadyExists() throws IOException {
|
|
||||||
File tempFile = new File("@ENV:test");
|
|
||||||
tempFile.createNewFile();
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
|
|
||||||
assertThrows(SOPGPException.AmbiguousInput.class, () -> FileUtil.getFile("@ENV:test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_EnvironmentVariable() {
|
|
||||||
File file = FileUtil.getFile("@ENV:test123");
|
|
||||||
assertEquals("test321", file.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_nonExistentEnvVariable() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> FileUtil.getFile("@ENV:INVALID"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_prfxFdAlreadyExists() throws IOException {
|
|
||||||
File tempFile = new File("@FD:1");
|
|
||||||
tempFile.createNewFile();
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
|
|
||||||
assertThrows(SOPGPException.AmbiguousInput.class, () -> FileUtil.getFile("@FD:1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFile_prfxFdNotSupported() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> FileUtil.getFile("@FD:2"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createNewFileOrThrow_throwsForNull() {
|
|
||||||
assertThrows(NullPointerException.class, () -> FileUtil.createNewFileOrThrow(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createNewFileOrThrow_success() throws IOException {
|
|
||||||
File dir = Files.createTempDirectory("test").toFile();
|
|
||||||
dir.deleteOnExit();
|
|
||||||
File file = new File(dir, "file");
|
|
||||||
|
|
||||||
assertFalse(file.exists());
|
|
||||||
FileUtil.createNewFileOrThrow(file);
|
|
||||||
assertTrue(file.exists());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createNewFileOrThrow_alreadyExists() throws IOException {
|
|
||||||
File dir = Files.createTempDirectory("test").toFile();
|
|
||||||
dir.deleteOnExit();
|
|
||||||
File file = new File(dir, "file");
|
|
||||||
|
|
||||||
FileUtil.createNewFileOrThrow(file);
|
|
||||||
assertTrue(file.exists());
|
|
||||||
assertThrows(SOPGPException.OutputExists.class, () -> FileUtil.createNewFileOrThrow(file));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFileInputStream_success() throws IOException {
|
|
||||||
File dir = Files.createTempDirectory("test").toFile();
|
|
||||||
dir.deleteOnExit();
|
|
||||||
File file = new File(dir, "file");
|
|
||||||
|
|
||||||
FileUtil.createNewFileOrThrow(file);
|
|
||||||
FileInputStream inputStream = FileUtil.getFileInputStream(file.getAbsolutePath());
|
|
||||||
assertNotNull(inputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFileInputStream_fileNotFound() throws IOException {
|
|
||||||
File dir = Files.createTempDirectory("test").toFile();
|
|
||||||
dir.deleteOnExit();
|
|
||||||
File file = new File(dir, "file");
|
|
||||||
|
|
||||||
assertThrows(SOPGPException.MissingInput.class,
|
|
||||||
() -> FileUtil.getFileInputStream(file.getAbsolutePath()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.operation.Armor;
|
|
||||||
import sop.operation.Dearmor;
|
|
||||||
import sop.operation.Decrypt;
|
|
||||||
import sop.operation.DetachInbandSignatureAndMessage;
|
|
||||||
import sop.operation.Encrypt;
|
|
||||||
import sop.operation.ExtractCert;
|
|
||||||
import sop.operation.GenerateKey;
|
|
||||||
import sop.operation.Sign;
|
|
||||||
import sop.operation.Verify;
|
|
||||||
import sop.operation.Version;
|
|
||||||
|
|
||||||
public class SOPTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(69)
|
|
||||||
public void assertExitOnInvalidSubcommand() {
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void assertThrowsIfNoSOPBackendSet() {
|
|
||||||
SopCLI.SOP_INSTANCE = null;
|
|
||||||
// At this point, no SOP backend is set, so an InvalidStateException triggers exit(1)
|
|
||||||
SopCLI.main(new String[] {"armor"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void UnsupportedSubcommandsTest() {
|
|
||||||
SOP nullCommandSOP = new SOP() {
|
|
||||||
@Override
|
|
||||||
public Version version() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GenerateKey generateKey() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExtractCert extractCert() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Sign sign() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Verify verify() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Encrypt encrypt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Decrypt decrypt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Armor armor() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Dearmor dearmor() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DetachInbandSignatureAndMessage detachInbandSignatureAndMessage() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
SopCLI.setSopInstance(nullCommandSOP);
|
|
||||||
|
|
||||||
List<String[]> commands = new ArrayList<>();
|
|
||||||
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[] {"encrypt"});
|
|
||||||
commands.add(new String[] {"extract-cert"});
|
|
||||||
commands.add(new String[] {"generate-key"});
|
|
||||||
commands.add(new String[] {"sign"});
|
|
||||||
commands.add(new String[] {"verify", "signature.asc", "cert.asc"});
|
|
||||||
commands.add(new String[] {"version"});
|
|
||||||
|
|
||||||
for (String[] command : commands) {
|
|
||||||
int exit = SopCLI.execute(command);
|
|
||||||
assertEquals(69, exit, "Unexpected exit code for non-implemented command " + Arrays.toString(command) + ": " + exit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import com.ginsberg.junit.exit.FailOnSystemExit;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.enums.ArmorLabel;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Armor;
|
|
||||||
|
|
||||||
public class ArmorCmdTest {
|
|
||||||
|
|
||||||
private Armor armor;
|
|
||||||
private SOP sop;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws SOPGPException.BadData {
|
|
||||||
armor = mock(Armor.class);
|
|
||||||
sop = mock(SOP.class);
|
|
||||||
when(sop.armor()).thenReturn(armor);
|
|
||||||
when(armor.data((InputStream) any())).thenReturn(nopReady());
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertLabelIsNotCalledByDefault() throws SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"armor"});
|
|
||||||
verify(armor, never()).label(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertLabelIsCalledWhenFlaggedWithArgument() throws SOPGPException.UnsupportedOption {
|
|
||||||
for (ArmorLabel label : ArmorLabel.values()) {
|
|
||||||
SopCLI.main(new String[] {"armor", "--label", label.name()});
|
|
||||||
verify(armor, times(1)).label(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertDataIsAlwaysCalled() throws SOPGPException.BadData {
|
|
||||||
SopCLI.main(new String[] {"armor"});
|
|
||||||
verify(armor, times(1)).data((InputStream) any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertThrowsForInvalidLabel() {
|
|
||||||
SopCLI.main(new String[] {"armor", "--label", "Invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void ifLabelsUnsupportedExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(armor.label(any())).thenThrow(new SOPGPException.UnsupportedOption("Custom Armor labels are not supported."));
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"armor", "--label", "Sig"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void ifBadDataExit41() throws SOPGPException.BadData {
|
|
||||||
when(armor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"armor"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@FailOnSystemExit
|
|
||||||
public void ifNoErrorsNoExit() {
|
|
||||||
when(sop.armor()).thenReturn(armor);
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"armor"});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Ready nopReady() {
|
|
||||||
return new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Dearmor;
|
|
||||||
|
|
||||||
public class DearmorCmdTest {
|
|
||||||
|
|
||||||
private SOP sop;
|
|
||||||
private Dearmor dearmor;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws IOException, SOPGPException.BadData {
|
|
||||||
sop = mock(SOP.class);
|
|
||||||
dearmor = mock(Dearmor.class);
|
|
||||||
when(dearmor.data((InputStream) any())).thenReturn(nopReady());
|
|
||||||
when(sop.dearmor()).thenReturn(dearmor);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Ready nopReady() {
|
|
||||||
return new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertDataIsCalled() throws IOException, SOPGPException.BadData {
|
|
||||||
SopCLI.main(new String[] {"dearmor"});
|
|
||||||
verify(dearmor, times(1)).data((InputStream) any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void assertBadDataCausesExit41() throws IOException, SOPGPException.BadData {
|
|
||||||
when(dearmor.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException("invalid armor")));
|
|
||||||
SopCLI.main(new String[] {"dearmor"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.ArgumentMatcher;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import sop.DecryptionResult;
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.SessionKey;
|
|
||||||
import sop.Verification;
|
|
||||||
import sop.cli.picocli.DateParser;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Decrypt;
|
|
||||||
import sop.util.HexUtil;
|
|
||||||
import sop.util.UTCUtil;
|
|
||||||
|
|
||||||
public class DecryptCmdTest {
|
|
||||||
|
|
||||||
private Decrypt decrypt;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws SOPGPException.UnsupportedOption, SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.PasswordNotHumanReadable, SOPGPException.CannotDecrypt {
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
decrypt = mock(Decrypt.class);
|
|
||||||
|
|
||||||
when(decrypt.verifyNotAfter(any())).thenReturn(decrypt);
|
|
||||||
when(decrypt.verifyNotBefore(any())).thenReturn(decrypt);
|
|
||||||
when(decrypt.withPassword(any())).thenReturn(decrypt);
|
|
||||||
when(decrypt.withSessionKey(any())).thenReturn(decrypt);
|
|
||||||
when(decrypt.withKey((InputStream) any())).thenReturn(decrypt);
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenReturn(nopReadyWithResult());
|
|
||||||
|
|
||||||
when(sop.decrypt()).thenReturn(decrypt);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ReadyWithResult<DecryptionResult> nopReadyWithResult() {
|
|
||||||
return new ReadyWithResult<DecryptionResult>() {
|
|
||||||
@Override
|
|
||||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
|
||||||
return new DecryptionResult(null, Collections.emptyList());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(19)
|
|
||||||
public void missingArgumentsExceptionCausesExit19() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt {
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.MissingArg("Missing arguments."));
|
|
||||||
SopCLI.main(new String[] {"decrypt"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void badDataExceptionCausesExit41() throws SOPGPException.MissingArg, SOPGPException.BadData, SOPGPException.CannotDecrypt {
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"decrypt"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(31)
|
|
||||||
public void assertNotHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable,
|
|
||||||
SOPGPException.UnsupportedOption {
|
|
||||||
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.PasswordNotHumanReadable());
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--with-password", "pretendThisIsNotReadable"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertWithPasswordPassesPasswordDown() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--with-password", "orange"});
|
|
||||||
verify(decrypt, times(1)).withPassword("orange");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertUnsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
|
||||||
when(decrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Decrypting with password not supported."));
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--with-password", "swordfish"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertDefaultTimeRangesAreUsedIfNotOverwritten() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date now = new Date();
|
|
||||||
SopCLI.main(new String[] {"decrypt"});
|
|
||||||
verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME);
|
|
||||||
verify(decrypt, times(1)).verifyNotAfter(
|
|
||||||
ArgumentMatchers.argThat(argument -> {
|
|
||||||
// allow 1-second difference
|
|
||||||
return Math.abs(now.getTime() - argument.getTime()) <= 1000;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertVerifyNotAfterAndBeforeDashResultsInMaxTimeRange() throws SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-before", "-", "--not-after", "-"});
|
|
||||||
verify(decrypt, times(1)).verifyNotBefore(DateParser.BEGINNING_OF_TIME);
|
|
||||||
verify(decrypt, times(1)).verifyNotAfter(DateParser.END_OF_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertVerifyNotAfterAndBeforeNowResultsInMinTimeRange() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date now = new Date();
|
|
||||||
ArgumentMatcher<Date> isMaxOneSecOff = argument -> {
|
|
||||||
// Allow less than 1-second difference
|
|
||||||
return Math.abs(now.getTime() - argument.getTime()) <= 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-before", "now", "--not-after", "now"});
|
|
||||||
verify(decrypt, times(1)).verifyNotAfter(ArgumentMatchers.argThat(isMaxOneSecOff));
|
|
||||||
verify(decrypt, times(1)).verifyNotBefore(ArgumentMatchers.argThat(isMaxOneSecOff));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void assertMalformedDateInNotBeforeCausesExit1() {
|
|
||||||
// ParserException causes exit(1)
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-before", "invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void assertMalformedDateInNotAfterCausesExit1() {
|
|
||||||
// ParserException causes exit(1)
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-after", "invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertUnsupportedNotAfterCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(decrypt.verifyNotAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-after", "now"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertUnsupportedNotBeforeCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(decrypt.verifyNotBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--not-before", "now"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@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()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertWhenSessionKeyCannotBeExtractedExit37() throws IOException {
|
|
||||||
Path tempDir = Files.createTempDirectory("session-key-out-dir");
|
|
||||||
File tempFile = new File(tempDir.toFile(), "session-key");
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertSessionKeyIsProperlyWrittenToSessionKeyFile() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData, IOException {
|
|
||||||
byte[] key = "C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137".getBytes(StandardCharsets.UTF_8);
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
|
||||||
@Override
|
|
||||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
|
||||||
return new DecryptionResult(
|
|
||||||
new SessionKey((byte) 9, key),
|
|
||||||
Collections.emptyList()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Path tempDir = Files.createTempDirectory("session-key-out-dir");
|
|
||||||
File tempFile = new File(tempDir.toFile(), "session-key");
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--session-key-out", tempFile.getAbsolutePath()});
|
|
||||||
|
|
||||||
ByteArrayOutputStream bytesInFile = new ByteArrayOutputStream();
|
|
||||||
try (FileInputStream fileIn = new FileInputStream(tempFile)) {
|
|
||||||
byte[] buf = new byte[32];
|
|
||||||
int read = fileIn.read(buf);
|
|
||||||
while (read != -1) {
|
|
||||||
bytesInFile.write(buf, 0, read);
|
|
||||||
read = fileIn.read(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] algAndKey = new byte[key.length + 1];
|
|
||||||
algAndKey[0] = (byte) 9;
|
|
||||||
System.arraycopy(key, 0, algAndKey, 1, key.length);
|
|
||||||
assertArrayEquals(algAndKey, bytesInFile.toByteArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(29)
|
|
||||||
public void assertUnableToDecryptExceptionResultsInExit29() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenThrow(new SOPGPException.CannotDecrypt());
|
|
||||||
SopCLI.main(new String[] {"decrypt"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(3)
|
|
||||||
public void assertNoSignatureExceptionCausesExit3() throws SOPGPException.CannotDecrypt, SOPGPException.MissingArg, SOPGPException.BadData {
|
|
||||||
when(decrypt.ciphertext((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
|
||||||
@Override
|
|
||||||
public DecryptionResult writeTo(OutputStream outputStream) throws SOPGPException.NoSignature {
|
|
||||||
throw new SOPGPException.NoSignature();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
SopCLI.main(new String[] {"decrypt"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void badDataInVerifyWithCausesExit41() throws IOException, SOPGPException.BadData {
|
|
||||||
when(decrypt.verifyWithCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
File tempFile = File.createTempFile("verify-with-", ".tmp");
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--verify-with", tempFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(61)
|
|
||||||
public void unexistentCertFileCausesExit61() {
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--verify-with", "invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(59)
|
|
||||||
public void existingVerifyOutCausesExit59() throws IOException {
|
|
||||||
File certFile = File.createTempFile("existing-verify-out-cert", ".asc");
|
|
||||||
File existingVerifyOut = File.createTempFile("existing-verify-out", ".tmp");
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--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((InputStream) any())).thenReturn(new ReadyWithResult<DecryptionResult>() {
|
|
||||||
@Override
|
|
||||||
public DecryptionResult writeTo(OutputStream outputStream) {
|
|
||||||
return new DecryptionResult(null, Collections.singletonList(
|
|
||||||
new Verification(
|
|
||||||
date,
|
|
||||||
"1B66A707819A920925BC6777C3E0AFC0B2DFF862",
|
|
||||||
"C8CD564EBF8D7BBA90611D8D071773658BF6BF86"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertWithSessionKeyIsPassedDown() throws SOPGPException.UnsupportedOption {
|
|
||||||
SessionKey key1 = new SessionKey((byte) 9, HexUtil.hexToBytes("C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"));
|
|
||||||
SessionKey key2 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
|
||||||
SopCLI.main(new String[] {"decrypt",
|
|
||||||
"--with-session-key", "9:C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137",
|
|
||||||
"--with-session-key", "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"});
|
|
||||||
verify(decrypt).withSessionKey(key1);
|
|
||||||
verify(decrypt).withSessionKey(key2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void assertMalformedSessionKeysResultInExit1() {
|
|
||||||
SopCLI.main(new String[] {"decrypt",
|
|
||||||
"--with-session-key", "C7CBDAF42537776F12509B5168793C26B93294E5ABDFA73224FB0177123E9137"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void assertBadDataInKeysResultsInExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
|
|
||||||
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
|
||||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(61)
|
|
||||||
public void assertKeyFileNotFoundCausesExit61() {
|
|
||||||
SopCLI.main(new String[] {"decrypt", "nonexistent-key"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(67)
|
|
||||||
public void assertProtectedKeyCausesExit67() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData {
|
|
||||||
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
|
|
||||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
|
||||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(13)
|
|
||||||
public void assertUnsupportedAlgorithmExceptionCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData, IOException {
|
|
||||||
when(decrypt.withKey((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new IOException()));
|
|
||||||
File tempKeyFile = File.createTempFile("key-", ".tmp");
|
|
||||||
SopCLI.main(new String[] {"decrypt", tempKeyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(23)
|
|
||||||
public void verifyOutWithoutVerifyWithCausesExit23() {
|
|
||||||
SopCLI.main(new String[] {"decrypt", "--verify-out", "out.file"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.enums.EncryptAs;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Encrypt;
|
|
||||||
|
|
||||||
public class EncryptCmdTest {
|
|
||||||
|
|
||||||
Encrypt encrypt;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws IOException {
|
|
||||||
encrypt = mock(Encrypt.class);
|
|
||||||
when(encrypt.plaintext((InputStream) any())).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
when(sop.encrypt()).thenReturn(encrypt);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(19)
|
|
||||||
public void missingBothPasswordAndCertFileCauseExit19() {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--no-armor"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void as_unsupportedEncryptAsCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(encrypt.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting encryption mode not supported."));
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--as", "Binary"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void as_invalidModeOptionCausesExit37() {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--as", "invalid"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void as_modeIsPassedDown() throws SOPGPException.UnsupportedOption {
|
|
||||||
for (EncryptAs mode : EncryptAs.values()) {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--as", mode.name(), "--with-password", "0rbit"});
|
|
||||||
verify(encrypt, times(1)).mode(mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(31)
|
|
||||||
public void withPassword_notHumanReadablePasswordCausesExit31() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
|
||||||
when(encrypt.withPassword("pretendThisIsNotReadable")).thenThrow(new SOPGPException.PasswordNotHumanReadable());
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "pretendThisIsNotReadable"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void withPassword_unsupportedWithPasswordCausesExit37() throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption {
|
|
||||||
when(encrypt.withPassword(any())).thenThrow(new SOPGPException.UnsupportedOption("Encrypting with password not supported."));
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "orange"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void signWith_multipleTimesGetPassedDown() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
|
|
||||||
File keyFile1 = File.createTempFile("sign-with-1-", ".asc");
|
|
||||||
File keyFile2 = File.createTempFile("sign-with-2-", ".asc");
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "password", "--sign-with", keyFile1.getAbsolutePath(), "--sign-with", keyFile2.getAbsolutePath()});
|
|
||||||
verify(encrypt, times(2)).signWith((InputStream) any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(61)
|
|
||||||
public void signWith_nonExistentKeyFileCausesExit61() {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "admin", "--sign-with", "nonExistent.asc"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(67)
|
|
||||||
public void signWith_keyIsProtectedCausesExit67() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
|
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--sign-with", keyFile.getAbsolutePath(), "--with-password", "starship"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(13)
|
|
||||||
public void signWith_unsupportedAsymmetricAlgoCausesExit13() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
|
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "123456", "--sign-with", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(79)
|
|
||||||
public void signWith_certCannotSignCausesExit1() throws IOException, SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData {
|
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.KeyCannotSign());
|
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "dragon", "--sign-with", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void signWith_badDataCausesExit41() throws SOPGPException.KeyIsProtected, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
|
||||||
when(encrypt.signWith((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
File keyFile = File.createTempFile("sign-with", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "orange", "--sign-with", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@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((InputStream) any())).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
|
|
||||||
File certFile = File.createTempFile("cert", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(17)
|
|
||||||
public void cert_certCannotEncryptCausesExit17() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
|
|
||||||
when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.CertCannotEncrypt("Certificate cannot encrypt.", new Exception()));
|
|
||||||
File certFile = File.createTempFile("cert", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void cert_badDataCausesExit41() throws IOException, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.CertCannotEncrypt, SOPGPException.BadData {
|
|
||||||
when(encrypt.withCert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
File certFile = File.createTempFile("cert", ".asc");
|
|
||||||
SopCLI.main(new String[] {"encrypt", certFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_notCalledByDefault() {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "clownfish"});
|
|
||||||
verify(encrypt, never()).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_callGetsPassedDown() {
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "monkey", "--no-armor"});
|
|
||||||
verify(encrypt, times(1)).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void writeTo_ioExceptionCausesExit1() throws IOException {
|
|
||||||
when(encrypt.plaintext((InputStream) any())).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"encrypt", "--with-password", "wildcat"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.ExtractCert;
|
|
||||||
|
|
||||||
public class ExtractCertCmdTest {
|
|
||||||
|
|
||||||
ExtractCert extractCert;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws IOException, SOPGPException.BadData {
|
|
||||||
extractCert = mock(ExtractCert.class);
|
|
||||||
when(extractCert.key((InputStream) any())).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
when(sop.extractCert()).thenReturn(extractCert);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_notCalledByDefault() {
|
|
||||||
SopCLI.main(new String[] {"extract-cert"});
|
|
||||||
verify(extractCert, never()).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_passedDown() {
|
|
||||||
SopCLI.main(new String[] {"extract-cert", "--no-armor"});
|
|
||||||
verify(extractCert, times(1)).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void key_ioExceptionCausesExit1() throws IOException, SOPGPException.BadData {
|
|
||||||
when(extractCert.key((InputStream) any())).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
SopCLI.main(new String[] {"extract-cert"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void key_badDataCausesExit41() throws IOException, SOPGPException.BadData {
|
|
||||||
when(extractCert.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"extract-cert"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.InOrder;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.GenerateKey;
|
|
||||||
|
|
||||||
public class GenerateKeyCmdTest {
|
|
||||||
|
|
||||||
GenerateKey generateKey;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
|
||||||
generateKey = mock(GenerateKey.class);
|
|
||||||
when(generateKey.generate()).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
when(sop.generateKey()).thenReturn(generateKey);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_notCalledByDefault() {
|
|
||||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
|
||||||
verify(generateKey, never()).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_passedDown() {
|
|
||||||
SopCLI.main(new String[] {"generate-key", "--no-armor", "Alice"});
|
|
||||||
verify(generateKey, times(1)).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void userId_multipleUserIdsPassedDownInProperOrder() {
|
|
||||||
SopCLI.main(new String[] {"generate-key", "Alice <alice@pgpainless.org>", "Bob <bob@pgpainless.org>"});
|
|
||||||
|
|
||||||
InOrder inOrder = Mockito.inOrder(generateKey);
|
|
||||||
inOrder.verify(generateKey).userId("Alice <alice@pgpainless.org>");
|
|
||||||
inOrder.verify(generateKey).userId("Bob <bob@pgpainless.org>");
|
|
||||||
|
|
||||||
verify(generateKey, times(2)).userId(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(19)
|
|
||||||
public void missingArgumentCausesExit19() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
|
||||||
// TODO: RFC4880-bis and the current Stateless OpenPGP CLI spec allow keys to have no user-ids,
|
|
||||||
// so we might want to change this test in the future.
|
|
||||||
when(generateKey.generate()).thenThrow(new SOPGPException.MissingArg("Missing user-id."));
|
|
||||||
SopCLI.main(new String[] {"generate-key"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(13)
|
|
||||||
public void unsupportedAsymmetricAlgorithmCausesExit13() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
|
||||||
when(generateKey.generate()).thenThrow(new SOPGPException.UnsupportedAsymmetricAlgo("Unsupported asymmetric algorithm.", new Exception()));
|
|
||||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void ioExceptionCausesExit1() throws SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.MissingArg, IOException {
|
|
||||||
when(generateKey.generate()).thenReturn(new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
SopCLI.main(new String[] {"generate-key", "Alice"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.SigningResult;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Sign;
|
|
||||||
|
|
||||||
public class SignCmdTest {
|
|
||||||
|
|
||||||
Sign sign;
|
|
||||||
File keyFile;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() throws IOException, SOPGPException.ExpectedText {
|
|
||||||
sign = mock(Sign.class);
|
|
||||||
when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult<SigningResult>() {
|
|
||||||
@Override
|
|
||||||
public SigningResult writeTo(OutputStream outputStream) {
|
|
||||||
return SigningResult.builder().build();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
when(sop.sign()).thenReturn(sign);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
|
|
||||||
keyFile = File.createTempFile("sign-", ".asc");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void as_optionsAreCaseInsensitive() {
|
|
||||||
SopCLI.main(new String[] {"sign", "--as", "Binary", keyFile.getAbsolutePath()});
|
|
||||||
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
|
|
||||||
SopCLI.main(new String[] {"sign", "--as", "BINARY", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void as_invalidOptionCausesExit37() {
|
|
||||||
SopCLI.main(new String[] {"sign", "--as", "Invalid", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void as_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(sign.mode(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting signing mode not supported."));
|
|
||||||
SopCLI.main(new String[] {"sign", "--as", "binary", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void key_nonExistentKeyFileCausesExit1() {
|
|
||||||
SopCLI.main(new String[] {"sign", "invalid.asc"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void key_keyIsProtectedCausesExit1() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
|
|
||||||
when(sign.key((InputStream) any())).thenThrow(new SOPGPException.KeyIsProtected());
|
|
||||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void key_badDataCausesExit41() throws SOPGPException.KeyIsProtected, IOException, SOPGPException.BadData {
|
|
||||||
when(sign.key((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(19)
|
|
||||||
public void key_missingKeyFileCausesExit19() {
|
|
||||||
SopCLI.main(new String[] {"sign"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_notCalledByDefault() {
|
|
||||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
|
||||||
verify(sign, never()).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noArmor_passedDown() {
|
|
||||||
SopCLI.main(new String[] {"sign", "--no-armor", keyFile.getAbsolutePath()});
|
|
||||||
verify(sign, times(1)).noArmor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void data_ioExceptionCausesExit1() throws IOException, SOPGPException.ExpectedText {
|
|
||||||
when(sign.data((InputStream) any())).thenReturn(new ReadyWithResult<SigningResult>() {
|
|
||||||
@Override
|
|
||||||
public SigningResult writeTo(OutputStream outputStream) throws IOException {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(53)
|
|
||||||
public void data_expectedTextExceptionCausesExit53() throws IOException, SOPGPException.ExpectedText {
|
|
||||||
when(sign.data((InputStream) any())).thenThrow(new SOPGPException.ExpectedText());
|
|
||||||
SopCLI.main(new String[] {"sign", keyFile.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.Verification;
|
|
||||||
import sop.cli.picocli.DateParser;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
import sop.operation.Verify;
|
|
||||||
import sop.util.UTCUtil;
|
|
||||||
|
|
||||||
public class VerifyCmdTest {
|
|
||||||
|
|
||||||
Verify verify;
|
|
||||||
File signature;
|
|
||||||
File cert;
|
|
||||||
|
|
||||||
PrintStream originalSout;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void prepare() throws SOPGPException.UnsupportedOption, SOPGPException.BadData, SOPGPException.NoSignature, IOException {
|
|
||||||
originalSout = System.out;
|
|
||||||
|
|
||||||
verify = mock(Verify.class);
|
|
||||||
when(verify.notBefore(any())).thenReturn(verify);
|
|
||||||
when(verify.notAfter(any())).thenReturn(verify);
|
|
||||||
when(verify.cert((InputStream) any())).thenReturn(verify);
|
|
||||||
when(verify.signatures((InputStream) any())).thenReturn(verify);
|
|
||||||
when(verify.data((InputStream) any())).thenReturn(
|
|
||||||
Collections.singletonList(
|
|
||||||
new Verification(
|
|
||||||
UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"),
|
|
||||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
|
|
||||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
when(sop.verify()).thenReturn(verify);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
|
|
||||||
signature = File.createTempFile("signature-", ".asc");
|
|
||||||
cert = File.createTempFile("cert-", ".asc");
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void restoreSout() {
|
|
||||||
System.setOut(originalSout);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notAfter_passedDown() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notAfter(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notAfter_now() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date now = new Date();
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-after", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notAfter(dateMatcher(now));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notAfter_dashCountsAsEndOfTime() throws SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-after", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notAfter(DateParser.END_OF_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void notAfter_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(verify.notAfter(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting upper signature date boundary not supported."));
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-after", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notBefore_passedDown() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date date = UTCUtil.parseUTCDate("2019-10-29T18:36:45Z");
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notBefore(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notBefore_now() throws SOPGPException.UnsupportedOption {
|
|
||||||
Date now = new Date();
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-before", "now", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notBefore(dateMatcher(now));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notBefore_dashCountsAsBeginningOfTime() throws SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-before", "-", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void notBefore_unsupportedOptionCausesExit37() throws SOPGPException.UnsupportedOption {
|
|
||||||
when(verify.notBefore(any())).thenThrow(new SOPGPException.UnsupportedOption("Setting lower signature date boundary not supported."));
|
|
||||||
SopCLI.main(new String[] {"verify", "--not-before", "2019-10-29T18:36:45Z", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void notBeforeAndNotAfterAreCalledWithDefaultValues() throws SOPGPException.UnsupportedOption {
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
verify(verify, times(1)).notAfter(dateMatcher(new Date()));
|
|
||||||
verify(verify, times(1)).notBefore(DateParser.BEGINNING_OF_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Date dateMatcher(Date date) {
|
|
||||||
return ArgumentMatchers.argThat(argument -> Math.abs(argument.getTime() - date.getTime()) < 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void cert_fileNotFoundCausesExit1() {
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), "invalid.asc"});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void cert_badDataCausesExit41() throws SOPGPException.BadData {
|
|
||||||
when(verify.cert((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(1)
|
|
||||||
public void signature_fileNotFoundCausesExit1() {
|
|
||||||
SopCLI.main(new String[] {"verify", "invalid.sig", cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void signature_badDataCausesExit41() throws SOPGPException.BadData {
|
|
||||||
when(verify.signatures((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(3)
|
|
||||||
public void data_noSignaturesCausesExit3() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
|
||||||
when(verify.data((InputStream) any())).thenThrow(new SOPGPException.NoSignature());
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(41)
|
|
||||||
public void data_badDataCausesExit41() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
|
||||||
when(verify.data((InputStream) any())).thenThrow(new SOPGPException.BadData(new IOException()));
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resultIsPrintedProperly() throws SOPGPException.NoSignature, IOException, SOPGPException.BadData {
|
|
||||||
when(verify.data((InputStream) any())).thenReturn(Arrays.asList(
|
|
||||||
new Verification(UTCUtil.parseUTCDate("2019-10-29T18:36:45Z"),
|
|
||||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E",
|
|
||||||
"EB85BB5FA33A75E15E944E63F231550C4F47E38E"),
|
|
||||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
|
||||||
"C90E6D36200A1B922A1509E77618196529AE5FF8",
|
|
||||||
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
|
|
||||||
));
|
|
||||||
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
|
||||||
System.setOut(new PrintStream(out));
|
|
||||||
|
|
||||||
SopCLI.main(new String[] {"verify", signature.getAbsolutePath(), cert.getAbsolutePath()});
|
|
||||||
|
|
||||||
System.setOut(originalSout);
|
|
||||||
|
|
||||||
String expected = "2019-10-29T18:36:45Z EB85BB5FA33A75E15E944E63F231550C4F47E38E EB85BB5FA33A75E15E944E63F231550C4F47E38E\n" +
|
|
||||||
"2019-10-24T23:48:29Z C90E6D36200A1B922A1509E77618196529AE5FF8 C4BC2DDB38CCE96485EBE9C2F20691179038E5C6\n";
|
|
||||||
|
|
||||||
assertEquals(expected, out.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.cli.picocli.commands;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.SOP;
|
|
||||||
import sop.cli.picocli.SopCLI;
|
|
||||||
import sop.operation.Version;
|
|
||||||
|
|
||||||
public class VersionCmdTest {
|
|
||||||
|
|
||||||
private Version version;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void mockComponents() {
|
|
||||||
SOP sop = mock(SOP.class);
|
|
||||||
version = mock(Version.class);
|
|
||||||
when(version.getName()).thenReturn("MockSop");
|
|
||||||
when(version.getVersion()).thenReturn("1.0");
|
|
||||||
when(sop.version()).thenReturn(version);
|
|
||||||
|
|
||||||
SopCLI.setSopInstance(sop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void assertVersionCommandWorks() {
|
|
||||||
SopCLI.main(new String[] {"version"});
|
|
||||||
verify(version, times(1)).getVersion();
|
|
||||||
verify(version, times(1)).getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@ExpectSystemExitWithStatus(37)
|
|
||||||
public void assertInvalidOptionResultsInExit37() {
|
|
||||||
SopCLI.main(new String[] {"version", "--invalid"});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +1 @@
|
||||||
<!--
|
# [MOVED](https://github.com/pgpainless/sop-java/tree/master/sop-java)
|
||||||
SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
-->
|
|
||||||
|
|
||||||
# SOP-Java
|
|
||||||
|
|
||||||
[![Spec Revision: 3](https://img.shields.io/badge/Spec%20Revision-3-blue)](https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03)
|
|
||||||
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/sop-java)](https://search.maven.org/artifact/org.pgpainless/sop-java)
|
|
||||||
[![JavaDoc](https://badgen.net/badge/javadoc/yes/green)](https://pgpainless.org/releases/latest/javadoc/sop/SOP.html)
|
|
||||||
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/pgpainless)](https://api.reuse.software/info/github.com/pgpainless/pgpainless)
|
|
||||||
|
|
||||||
Stateless OpenPGP Protocol for Java.
|
|
||||||
|
|
||||||
This module contains interfaces that model the API described by the
|
|
||||||
[Stateless OpenPGP Command Line Interface](https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03) specification.
|
|
||||||
|
|
||||||
This module is not a command line application! For that, see `sop-java-picocli`.
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
The API defined by `sop-java` is super straight forward:
|
|
||||||
```java
|
|
||||||
SOP sop = ... // e.g. new org.pgpainless.sop.SOPImpl();
|
|
||||||
|
|
||||||
// Generate an OpenPGP key
|
|
||||||
byte[] key = sop.generateKey()
|
|
||||||
.userId("Alice <alice@example.org>")
|
|
||||||
.generate()
|
|
||||||
.getBytes();
|
|
||||||
|
|
||||||
// Extract the certificate (public key)
|
|
||||||
byte[] cert = sop.extractCert()
|
|
||||||
.key(key)
|
|
||||||
.getBytes();
|
|
||||||
|
|
||||||
// Encrypt a message
|
|
||||||
byte[] message = ...
|
|
||||||
byte[] encrypted = sop.encrypt()
|
|
||||||
.withCert(cert)
|
|
||||||
.signWith(key)
|
|
||||||
.plaintext(message)
|
|
||||||
.getBytes();
|
|
||||||
|
|
||||||
// Decrypt a message
|
|
||||||
ByteArrayAndResult<DecryptionResult> messageAndVerifications = sop.decrypt()
|
|
||||||
.verifyWith(cert)
|
|
||||||
.withKey(key)
|
|
||||||
.ciphertext(encrypted)
|
|
||||||
.toByteArrayAndResult();
|
|
||||||
byte[] decrypted = messageAndVerifications.getBytes();
|
|
||||||
// Signature Verifications
|
|
||||||
DecryptionResult messageInfo = messageAndVerifications.getResult();
|
|
||||||
List<Verification> signatureVerifications = messageInfo.getVerifications();
|
|
||||||
```
|
|
||||||
|
|
||||||
Furthermore, the API is capable of signing messages and verifying unencrypted signed data, as well as adding and removing ASCII armor.
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
As per the spec, sop-java does not (yet) deal with encrypted OpenPGP keys.
|
|
||||||
|
|
||||||
## Why should I use this?
|
|
||||||
|
|
||||||
If you need to use OpenPGP functionality like encrypting/decrypting messages, or creating/verifying
|
|
||||||
signatures inside your application, you probably don't want to start from scratch and instead reuse some library.
|
|
||||||
|
|
||||||
Instead of locking yourselves in by depending hard on that one library, you can simply depend on the interfaces from
|
|
||||||
`sop-java` and plug in a library (such as `pgpainless-sop`) that implements said interfaces.
|
|
||||||
|
|
||||||
That way you don't make yourself dependent from a single OpenPGP library and stay flexible.
|
|
||||||
Should another library emerge, that better suits your needs (and implements `sop-java`), you can easily switch
|
|
||||||
by swapping out the dependency with minimal changes to your code.
|
|
||||||
|
|
||||||
## Why should I *implement* this?
|
|
||||||
|
|
||||||
Did you create an [OpenPGP](https://datatracker.ietf.org/doc/html/rfc4880) implementation that can be used in the Java ecosystem?
|
|
||||||
By implementing the `sop-java` interface, you can turn your library into a command line interface (see `sop-java-picocli`).
|
|
||||||
This allows you to plug your library into the [OpenPGP interoperability test suite](https://tests.sequoia-pgp.org/)
|
|
||||||
of the [Sequoia-PGP](https://sequoia-pgp.org/) project.
|
|
|
@ -1,22 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
}
|
|
||||||
|
|
||||||
group 'org.pgpainless'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tuple of a byte array and associated result object.
|
|
||||||
* @param <T> type of result
|
|
||||||
*/
|
|
||||||
public class ByteArrayAndResult<T> {
|
|
||||||
|
|
||||||
private final byte[] bytes;
|
|
||||||
private final T result;
|
|
||||||
|
|
||||||
public ByteArrayAndResult(byte[] bytes, T result) {
|
|
||||||
this.bytes = bytes;
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the byte array part.
|
|
||||||
*
|
|
||||||
* @return bytes
|
|
||||||
*/
|
|
||||||
public byte[] getBytes() {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the result part.
|
|
||||||
*
|
|
||||||
* @return result
|
|
||||||
*/
|
|
||||||
public T getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the byte array part as an {@link InputStream}.
|
|
||||||
*
|
|
||||||
* @return input stream
|
|
||||||
*/
|
|
||||||
public InputStream getInputStream() {
|
|
||||||
return new ByteArrayInputStream(getBytes());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import sop.util.Optional;
|
|
||||||
|
|
||||||
public class DecryptionResult {
|
|
||||||
|
|
||||||
private final Optional<SessionKey> sessionKey;
|
|
||||||
private final List<Verification> verifications;
|
|
||||||
|
|
||||||
public DecryptionResult(SessionKey sessionKey, List<Verification> verifications) {
|
|
||||||
this.sessionKey = Optional.ofNullable(sessionKey);
|
|
||||||
this.verifications = Collections.unmodifiableList(verifications);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<SessionKey> getSessionKey() {
|
|
||||||
return sessionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Verification> getVerifications() {
|
|
||||||
return new ArrayList<>(verifications);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
public class MicAlg {
|
|
||||||
|
|
||||||
private final String micAlg;
|
|
||||||
|
|
||||||
public MicAlg(String micAlg) {
|
|
||||||
if (micAlg == null) {
|
|
||||||
throw new IllegalArgumentException("MicAlg String cannot be null.");
|
|
||||||
}
|
|
||||||
this.micAlg = micAlg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MicAlg empty() {
|
|
||||||
return new MicAlg("");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MicAlg fromHashAlgorithmId(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case 1:
|
|
||||||
return new MicAlg("pgp-md5");
|
|
||||||
case 2:
|
|
||||||
return new MicAlg("pgp-sha1");
|
|
||||||
case 3:
|
|
||||||
return new MicAlg("pgp-ripemd160");
|
|
||||||
case 8:
|
|
||||||
return new MicAlg("pgp-sha256");
|
|
||||||
case 9:
|
|
||||||
return new MicAlg("pgp-sha384");
|
|
||||||
case 10:
|
|
||||||
return new MicAlg("pgp-sha512");
|
|
||||||
case 11:
|
|
||||||
return new MicAlg("pgp-sha224");
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported hash algorithm ID: " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMicAlg() {
|
|
||||||
return micAlg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(OutputStream outputStream) {
|
|
||||||
PrintWriter pw = new PrintWriter(outputStream);
|
|
||||||
pw.write(getMicAlg());
|
|
||||||
pw.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public abstract class Ready {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the data to the provided output stream.
|
|
||||||
*
|
|
||||||
* @param outputStream output stream
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
public abstract void writeTo(OutputStream outputStream) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the data as a byte array by writing it to a {@link ByteArrayOutputStream} first and then returning
|
|
||||||
* the array.
|
|
||||||
*
|
|
||||||
* @return data as byte array
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
public byte[] getBytes() throws IOException {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
writeTo(bytes);
|
|
||||||
return bytes.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an input stream containing the data.
|
|
||||||
*
|
|
||||||
* @return input stream
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
public InputStream getInputStream() throws IOException {
|
|
||||||
return new ByteArrayInputStream(getBytes());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public abstract class ReadyWithResult<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the data e.g. decrypted plaintext to the provided output stream and return the result of the
|
|
||||||
* processing operation.
|
|
||||||
*
|
|
||||||
* @param outputStream output stream
|
|
||||||
* @return result, eg. signatures
|
|
||||||
*
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws SOPGPException.NoSignature if there are no valid signatures found
|
|
||||||
*/
|
|
||||||
public abstract T writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the data as a {@link ByteArrayAndResult}.
|
|
||||||
* Calling {@link ByteArrayAndResult#getBytes()} will give you access to the data as byte array, while
|
|
||||||
* {@link ByteArrayAndResult#getResult()} will grant access to the appended result.
|
|
||||||
*
|
|
||||||
* @return byte array and result
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws SOPGPException.NoSignature if there are no valid signatures found
|
|
||||||
*/
|
|
||||||
public ByteArrayAndResult<T> toByteArrayAndResult() throws IOException, SOPGPException.NoSignature {
|
|
||||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
||||||
T result = writeTo(bytes);
|
|
||||||
return new ByteArrayAndResult<>(bytes.toByteArray(), result);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import sop.operation.Armor;
|
|
||||||
import sop.operation.Dearmor;
|
|
||||||
import sop.operation.Decrypt;
|
|
||||||
import sop.operation.DetachInbandSignatureAndMessage;
|
|
||||||
import sop.operation.Encrypt;
|
|
||||||
import sop.operation.ExtractCert;
|
|
||||||
import sop.operation.GenerateKey;
|
|
||||||
import sop.operation.Sign;
|
|
||||||
import sop.operation.Verify;
|
|
||||||
import sop.operation.Version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless OpenPGP Interface.
|
|
||||||
*/
|
|
||||||
public interface SOP {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get information about the implementations name and version.
|
|
||||||
*
|
|
||||||
* @return version
|
|
||||||
*/
|
|
||||||
Version version();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a secret key.
|
|
||||||
* Customize the operation using the builder {@link GenerateKey}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
GenerateKey generateKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a certificate (public key) from a secret key.
|
|
||||||
* Customize the operation using the builder {@link ExtractCert}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
ExtractCert extractCert();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create detached signatures.
|
|
||||||
* Customize the operation using the builder {@link Sign}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Sign sign();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify detached signatures.
|
|
||||||
* Customize the operation using the builder {@link Verify}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Verify verify();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt a message.
|
|
||||||
* Customize the operation using the builder {@link Encrypt}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt encrypt();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt a message.
|
|
||||||
* Customize the operation using the builder {@link Decrypt}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt decrypt();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert binary OpenPGP data to ASCII.
|
|
||||||
* Customize the operation using the builder {@link Armor}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Armor armor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts ASCII armored OpenPGP data to binary.
|
|
||||||
* Customize the operation using the builder {@link Dearmor}.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Dearmor dearmor();
|
|
||||||
|
|
||||||
DetachInbandSignatureAndMessage detachInbandSignatureAndMessage();
|
|
||||||
}
|
|
|
@ -1,79 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import sop.util.HexUtil;
|
|
||||||
|
|
||||||
public class SessionKey {
|
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9a-fA-F]+)$");
|
|
||||||
|
|
||||||
private final byte algorithm;
|
|
||||||
private final byte[] sessionKey;
|
|
||||||
|
|
||||||
public SessionKey(byte algorithm, byte[] sessionKey) {
|
|
||||||
this.algorithm = algorithm;
|
|
||||||
this.sessionKey = sessionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the symmetric algorithm octet.
|
|
||||||
*
|
|
||||||
* @return algorithm id
|
|
||||||
*/
|
|
||||||
public byte getAlgorithm() {
|
|
||||||
return algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the session key.
|
|
||||||
*
|
|
||||||
* @return session key
|
|
||||||
*/
|
|
||||||
public byte[] getKey() {
|
|
||||||
return sessionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getAlgorithm() * 17 + Arrays.hashCode(getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other) {
|
|
||||||
if (other == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this == other) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(other instanceof SessionKey)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionKey otherKey = (SessionKey) other;
|
|
||||||
return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SessionKey fromString(String string) {
|
|
||||||
Matcher matcher = PATTERN.matcher(string);
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
throw new IllegalArgumentException("Provided session key does not match expected format.");
|
|
||||||
}
|
|
||||||
byte algorithm = Byte.parseByte(matcher.group(1));
|
|
||||||
String key = matcher.group(2);
|
|
||||||
|
|
||||||
return new SessionKey(algorithm, HexUtil.hexToBytes(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
public abstract class Signatures extends Ready {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write OpenPGP signatures to the provided output stream.
|
|
||||||
*
|
|
||||||
* @param signatureOutputStream output stream
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public abstract void writeTo(OutputStream signatureOutputStream) throws IOException;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class contains various information about a signed message.
|
|
||||||
*/
|
|
||||||
public final class SigningResult {
|
|
||||||
|
|
||||||
private final MicAlg micAlg;
|
|
||||||
|
|
||||||
private SigningResult(MicAlg micAlg) {
|
|
||||||
this.micAlg = micAlg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a string identifying the digest mechanism used to create the signed message.
|
|
||||||
* This is useful for setting the micalg= parameter for the multipart/signed
|
|
||||||
* content type of a PGP/MIME object as described in section 5 of [RFC3156].
|
|
||||||
*
|
|
||||||
* If more than one signature was generated and different digest mechanisms were used,
|
|
||||||
* the value of the micalg object is an empty string.
|
|
||||||
*
|
|
||||||
* @return micalg
|
|
||||||
*/
|
|
||||||
public MicAlg getMicAlg() {
|
|
||||||
return micAlg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private MicAlg micAlg;
|
|
||||||
|
|
||||||
public Builder setMicAlg(MicAlg micAlg) {
|
|
||||||
this.micAlg = micAlg;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SigningResult build() {
|
|
||||||
SigningResult signingResult = new SigningResult(micAlg);
|
|
||||||
return signingResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import sop.util.UTCUtil;
|
|
||||||
|
|
||||||
public class Verification {
|
|
||||||
|
|
||||||
private final Date creationTime;
|
|
||||||
private final String signingKeyFingerprint;
|
|
||||||
private final String signingCertFingerprint;
|
|
||||||
|
|
||||||
public Verification(Date creationTime, String signingKeyFingerprint, String signingCertFingerprint) {
|
|
||||||
this.creationTime = creationTime;
|
|
||||||
this.signingKeyFingerprint = signingKeyFingerprint;
|
|
||||||
this.signingCertFingerprint = signingCertFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the signatures' creation time.
|
|
||||||
*
|
|
||||||
* @return signature creation time
|
|
||||||
*/
|
|
||||||
public Date getCreationTime() {
|
|
||||||
return creationTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the fingerprint of the signing (sub)key.
|
|
||||||
*
|
|
||||||
* @return signing key fingerprint
|
|
||||||
*/
|
|
||||||
public String getSigningKeyFingerprint() {
|
|
||||||
return signingKeyFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the fingerprint fo the signing certificate.
|
|
||||||
*
|
|
||||||
* @return signing certificate fingerprint
|
|
||||||
*/
|
|
||||||
public String getSigningCertFingerprint() {
|
|
||||||
return signingCertFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return UTCUtil.formatUTCDate(getCreationTime()) +
|
|
||||||
' ' +
|
|
||||||
getSigningKeyFingerprint() +
|
|
||||||
' ' +
|
|
||||||
getSigningCertFingerprint();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.enums;
|
|
||||||
|
|
||||||
public enum ArmorLabel {
|
|
||||||
Auto,
|
|
||||||
Sig,
|
|
||||||
Key,
|
|
||||||
Cert,
|
|
||||||
Message
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.enums;
|
|
||||||
|
|
||||||
public enum EncryptAs {
|
|
||||||
Binary,
|
|
||||||
Text,
|
|
||||||
MIME
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.enums;
|
|
||||||
|
|
||||||
public enum SignAs {
|
|
||||||
Binary,
|
|
||||||
Text
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless OpenPGP Interface for Java.
|
|
||||||
* Enumerations.
|
|
||||||
*/
|
|
||||||
package sop.enums;
|
|
|
@ -1,316 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.exception;
|
|
||||||
|
|
||||||
public abstract class SOPGPException extends RuntimeException {
|
|
||||||
|
|
||||||
public SOPGPException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SOPGPException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SOPGPException(Throwable e) {
|
|
||||||
super(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SOPGPException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int getExitCode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No acceptable signatures found (sop verify).
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asymmetric algorithm unsupported (sop encrypt).
|
|
||||||
*/
|
|
||||||
public static class UnsupportedAsymmetricAlgo extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 13;
|
|
||||||
|
|
||||||
public UnsupportedAsymmetricAlgo(String message, Throwable e) {
|
|
||||||
super(message, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Certificate not encryption capable (e,g, expired, revoked, unacceptable usage).
|
|
||||||
*/
|
|
||||||
public static class CertCannotEncrypt extends SOPGPException {
|
|
||||||
public static final int EXIT_CODE = 17;
|
|
||||||
|
|
||||||
public CertCannotEncrypt(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Missing required argument.
|
|
||||||
*/
|
|
||||||
public static class MissingArg extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 19;
|
|
||||||
|
|
||||||
public MissingArg(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Incomplete verification instructions (sop decrypt).
|
|
||||||
*/
|
|
||||||
public static class IncompleteVerification extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 23;
|
|
||||||
|
|
||||||
public IncompleteVerification(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unable to decrypt (sop decrypt).
|
|
||||||
*/
|
|
||||||
public static class CannotDecrypt extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 29;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-UTF-8 or otherwise unreliable password (sop encrypt).
|
|
||||||
*/
|
|
||||||
public static class PasswordNotHumanReadable extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 31;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsupported option.
|
|
||||||
*/
|
|
||||||
public static class UnsupportedOption extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 37;
|
|
||||||
|
|
||||||
public UnsupportedOption(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnsupportedOption(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalid data type (no secret key where KEYS expected, etc.).
|
|
||||||
*/
|
|
||||||
public static class BadData extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 41;
|
|
||||||
|
|
||||||
public BadData(Throwable e) {
|
|
||||||
super(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BadData(String message, BadData badData) {
|
|
||||||
super(message, badData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-Text input where text expected.
|
|
||||||
*/
|
|
||||||
public static class ExpectedText extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 53;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output file already exists.
|
|
||||||
*/
|
|
||||||
public static class OutputExists extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 59;
|
|
||||||
|
|
||||||
public OutputExists(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input file does not exist.
|
|
||||||
*/
|
|
||||||
public static class MissingInput extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 61;
|
|
||||||
|
|
||||||
public MissingInput(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A KEYS input is protected (locked) with a password, and sop cannot unlock it.
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsupported subcommand.
|
|
||||||
*/
|
|
||||||
public static class UnsupportedSubcommand extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 69;
|
|
||||||
|
|
||||||
public UnsupportedSubcommand(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An indirect parameter is a special designator (it starts with @), but sop does not know how to handle the prefix.
|
|
||||||
*/
|
|
||||||
public static class UnsupportedSpecialPrefix extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 71;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A indirect input parameter is a special designator (it starts with @),
|
|
||||||
* and a filename matching the designator is actually present.
|
|
||||||
*/
|
|
||||||
public static class AmbiguousInput extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 73;
|
|
||||||
|
|
||||||
public AmbiguousInput(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key not signature-capable (e.g. expired, revoked, unacceptable usage flags)
|
|
||||||
* (sop sign and sop encrypt with --sign-with).
|
|
||||||
*/
|
|
||||||
public static class KeyCannotSign extends SOPGPException {
|
|
||||||
|
|
||||||
public static final int EXIT_CODE = 79;
|
|
||||||
|
|
||||||
public KeyCannotSign() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyCannotSign(String s, KeyCannotSign keyCannotSign) {
|
|
||||||
super(s, keyCannotSign);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getExitCode() {
|
|
||||||
return EXIT_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless OpenPGP Interface for Java.
|
|
||||||
* Exception classes.
|
|
||||||
*/
|
|
||||||
package sop.exception;
|
|
|
@ -1,41 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.enums.ArmorLabel;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Armor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrides automatic detection of label.
|
|
||||||
*
|
|
||||||
* @param label armor label
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Armor label(ArmorLabel label) throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Armor the provided data.
|
|
||||||
*
|
|
||||||
* @param data input stream of unarmored OpenPGP data
|
|
||||||
* @return armored data
|
|
||||||
*/
|
|
||||||
Ready data(InputStream data) throws SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Armor the provided data.
|
|
||||||
*
|
|
||||||
* @param data unarmored OpenPGP data
|
|
||||||
* @return armored data
|
|
||||||
*/
|
|
||||||
default Ready data(byte[] data) throws SOPGPException.BadData {
|
|
||||||
return data(new ByteArrayInputStream(data));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Dearmor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dearmor armored OpenPGP data.
|
|
||||||
*
|
|
||||||
* @param data armored OpenPGP data
|
|
||||||
* @return input stream of unarmored data
|
|
||||||
*/
|
|
||||||
Ready data(InputStream data) throws SOPGPException.BadData, IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dearmor armored OpenPGP data.
|
|
||||||
*
|
|
||||||
* @param data armored OpenPGP data
|
|
||||||
* @return input stream of unarmored data
|
|
||||||
*/
|
|
||||||
default Ready data(byte[] data) throws SOPGPException.BadData, IOException {
|
|
||||||
return data(new ByteArrayInputStream(data));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import sop.DecryptionResult;
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.SessionKey;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Decrypt {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the SOP consider signatures before this date invalid.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt verifyNotBefore(Date timestamp)
|
|
||||||
throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the SOP consider signatures after this date invalid.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt verifyNotAfter(Date timestamp)
|
|
||||||
throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more verification cert.
|
|
||||||
*
|
|
||||||
* @param cert input stream containing the cert(s)
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt verifyWithCert(InputStream cert)
|
|
||||||
throws SOPGPException.BadData,
|
|
||||||
IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more verification cert.
|
|
||||||
*
|
|
||||||
* @param cert byte array containing the cert(s)
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Decrypt verifyWithCert(byte[] cert)
|
|
||||||
throws SOPGPException.BadData, IOException {
|
|
||||||
return verifyWithCert(new ByteArrayInputStream(cert));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to decrypt with the given session key.
|
|
||||||
*
|
|
||||||
* @param sessionKey session key
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt withSessionKey(SessionKey sessionKey)
|
|
||||||
throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to decrypt with the given password.
|
|
||||||
*
|
|
||||||
* @param password password
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt withPassword(String password)
|
|
||||||
throws SOPGPException.PasswordNotHumanReadable,
|
|
||||||
SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more decryption key.
|
|
||||||
*
|
|
||||||
* @param key input stream containing the key(s)
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Decrypt withKey(InputStream key)
|
|
||||||
throws SOPGPException.KeyIsProtected,
|
|
||||||
SOPGPException.BadData,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more decryption key.
|
|
||||||
*
|
|
||||||
* @param key byte array containing the key(s)
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Decrypt withKey(byte[] key)
|
|
||||||
throws SOPGPException.KeyIsProtected,
|
|
||||||
SOPGPException.BadData,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo {
|
|
||||||
return withKey(new ByteArrayInputStream(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
|
||||||
* @param ciphertext ciphertext
|
|
||||||
* @return ready with result
|
|
||||||
*/
|
|
||||||
ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
|
|
||||||
throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts the given ciphertext, returning verification results and plaintext.
|
|
||||||
* @param ciphertext ciphertext
|
|
||||||
* @return ready with result
|
|
||||||
*/
|
|
||||||
default ReadyWithResult<DecryptionResult> ciphertext(byte[] ciphertext)
|
|
||||||
throws SOPGPException.BadData, SOPGPException.MissingArg, SOPGPException.CannotDecrypt {
|
|
||||||
return ciphertext(new ByteArrayInputStream(ciphertext));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.Signatures;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split cleartext signed messages up into data and signatures.
|
|
||||||
*/
|
|
||||||
public interface DetachInbandSignatureAndMessage {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not wrap the signatures in ASCII armor.
|
|
||||||
* @return builder
|
|
||||||
*/
|
|
||||||
DetachInbandSignatureAndMessage noArmor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detach the provided cleartext signed message from its signatures.
|
|
||||||
*
|
|
||||||
* @param messageInputStream input stream containing the signed message
|
|
||||||
* @return result containing the detached message
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
ReadyWithResult<Signatures> message(InputStream messageInputStream) throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detach the provided cleartext signed message from its signatures.
|
|
||||||
*
|
|
||||||
* @param message byte array containing the signed message
|
|
||||||
* @return result containing the detached message
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
*/
|
|
||||||
default ReadyWithResult<Signatures> message(byte[] message) throws IOException {
|
|
||||||
return message(new ByteArrayInputStream(message));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.enums.EncryptAs;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Encrypt {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable ASCII armor encoding.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt noArmor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets encryption mode.
|
|
||||||
*
|
|
||||||
* @param mode mode
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt mode(EncryptAs mode)
|
|
||||||
throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the signer key.
|
|
||||||
*
|
|
||||||
* @param key input stream containing the encoded signer key
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt signWith(InputStream key)
|
|
||||||
throws SOPGPException.KeyIsProtected,
|
|
||||||
SOPGPException.KeyCannotSign,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
|
||||||
SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the signer key.
|
|
||||||
*
|
|
||||||
* @param key byte array containing the encoded signer key
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Encrypt signWith(byte[] key)
|
|
||||||
throws SOPGPException.KeyIsProtected,
|
|
||||||
SOPGPException.KeyCannotSign,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
|
||||||
SOPGPException.BadData {
|
|
||||||
return signWith(new ByteArrayInputStream(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt with the given password.
|
|
||||||
*
|
|
||||||
* @param password password
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt withPassword(String password)
|
|
||||||
throws SOPGPException.PasswordNotHumanReadable,
|
|
||||||
SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt with the given cert.
|
|
||||||
*
|
|
||||||
* @param cert input stream containing the encoded cert.
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Encrypt withCert(InputStream cert)
|
|
||||||
throws SOPGPException.CertCannotEncrypt,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
|
||||||
SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt with the given cert.
|
|
||||||
*
|
|
||||||
* @param cert byte array containing the encoded cert.
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Encrypt withCert(byte[] cert)
|
|
||||||
throws SOPGPException.CertCannotEncrypt,
|
|
||||||
SOPGPException.UnsupportedAsymmetricAlgo,
|
|
||||||
SOPGPException.BadData {
|
|
||||||
return withCert(new ByteArrayInputStream(cert));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt the given data yielding the ciphertext.
|
|
||||||
* @param plaintext plaintext
|
|
||||||
* @return input stream containing the ciphertext
|
|
||||||
*/
|
|
||||||
Ready plaintext(InputStream plaintext)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt the given data yielding the ciphertext.
|
|
||||||
* @param plaintext plaintext
|
|
||||||
* @return input stream containing the ciphertext
|
|
||||||
*/
|
|
||||||
default Ready plaintext(byte[] plaintext) throws IOException {
|
|
||||||
return plaintext(new ByteArrayInputStream(plaintext));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface ExtractCert {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable ASCII armor encoding.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
ExtractCert noArmor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the cert(s) from the provided key(s).
|
|
||||||
*
|
|
||||||
* @param keyInputStream input stream containing the encoding of one or more OpenPGP keys
|
|
||||||
* @return result containing the encoding of the keys certs
|
|
||||||
*/
|
|
||||||
Ready key(InputStream keyInputStream) throws IOException, SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the cert(s) from the provided key(s).
|
|
||||||
*
|
|
||||||
* @param key byte array containing the encoding of one or more OpenPGP key
|
|
||||||
* @return result containing the encoding of the keys certs
|
|
||||||
*/
|
|
||||||
default Ready key(byte[] key) throws IOException, SOPGPException.BadData {
|
|
||||||
return key(new ByteArrayInputStream(key));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.Ready;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface GenerateKey {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable ASCII armor encoding.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
GenerateKey noArmor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a user-id.
|
|
||||||
*
|
|
||||||
* @param userId user-id
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
GenerateKey userId(String userId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the OpenPGP key and return it encoded as an {@link InputStream}.
|
|
||||||
*
|
|
||||||
* @return key
|
|
||||||
*/
|
|
||||||
Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo, IOException;
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.SigningResult;
|
|
||||||
import sop.enums.SignAs;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Sign {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable ASCII armor encoding.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Sign noArmor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the signature mode.
|
|
||||||
* Note: This method has to be called before {@link #key(InputStream)} is called.
|
|
||||||
*
|
|
||||||
* @param mode signature mode
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Sign mode(SignAs mode) throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add one or more signing keys.
|
|
||||||
*
|
|
||||||
* @param key input stream containing encoded keys
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Sign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add one or more signing keys.
|
|
||||||
*
|
|
||||||
* @param key byte array containing encoded keys
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Sign key(byte[] key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
|
||||||
return key(new ByteArrayInputStream(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs data.
|
|
||||||
*
|
|
||||||
* @param data input stream containing data
|
|
||||||
* @return ready
|
|
||||||
*/
|
|
||||||
ReadyWithResult<SigningResult> data(InputStream data) throws IOException, SOPGPException.ExpectedText;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs data.
|
|
||||||
*
|
|
||||||
* @param data byte array containing data
|
|
||||||
* @return ready
|
|
||||||
*/
|
|
||||||
default ReadyWithResult<SigningResult> data(byte[] data) throws IOException, SOPGPException.ExpectedText {
|
|
||||||
return data(new ByteArrayInputStream(data));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface Verify extends VerifySignatures {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the SOP implementation consider signatures before this date invalid.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Verify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the SOP implementation consider signatures after this date invalid.
|
|
||||||
*
|
|
||||||
* @param timestamp timestamp
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Verify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add one or more verification cert.
|
|
||||||
*
|
|
||||||
* @param cert input stream containing the encoded certs
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
Verify cert(InputStream cert) throws SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add one or more verification cert.
|
|
||||||
*
|
|
||||||
* @param cert byte array containing the encoded certs
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default Verify cert(byte[] cert) throws SOPGPException.BadData {
|
|
||||||
return cert(new ByteArrayInputStream(cert));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the signatures.
|
|
||||||
* @param signatures input stream containing encoded, detached signatures.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
VerifySignatures signatures(InputStream signatures) throws SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the signatures.
|
|
||||||
* @param signatures byte array containing encoded, detached signatures.
|
|
||||||
*
|
|
||||||
* @return builder instance
|
|
||||||
*/
|
|
||||||
default VerifySignatures signatures(byte[] signatures) throws SOPGPException.BadData {
|
|
||||||
return signatures(new ByteArrayInputStream(signatures));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import sop.Verification;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public interface VerifySignatures {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide the signed data (without signatures).
|
|
||||||
*
|
|
||||||
* @param data signed data
|
|
||||||
* @return list of signature verifications
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws SOPGPException.NoSignature when no signature is found
|
|
||||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
|
||||||
*/
|
|
||||||
List<Verification> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide the signed data (without signatures).
|
|
||||||
*
|
|
||||||
* @param data signed data
|
|
||||||
* @return list of signature verifications
|
|
||||||
* @throws IOException in case of an IO error
|
|
||||||
* @throws SOPGPException.NoSignature when no signature is found
|
|
||||||
* @throws SOPGPException.BadData when the data is invalid OpenPGP data
|
|
||||||
*/
|
|
||||||
default List<Verification> data(byte[] data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
|
|
||||||
return data(new ByteArrayInputStream(data));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.operation;
|
|
||||||
|
|
||||||
public interface Version {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the implementations name.
|
|
||||||
* e.g. "SOP",
|
|
||||||
*
|
|
||||||
* @return implementation name
|
|
||||||
*/
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the implementations short version string.
|
|
||||||
* e.g. "1.0"
|
|
||||||
*
|
|
||||||
* @return version string
|
|
||||||
*/
|
|
||||||
String getVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return version information about the used OpenPGP backend.
|
|
||||||
* e.g. "Bouncycastle 1.70"
|
|
||||||
*
|
|
||||||
* @return backend version string
|
|
||||||
*/
|
|
||||||
String getBackendVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an extended version string containing multiple lines of version information.
|
|
||||||
* The first line MUST match the information produced by {@link #getName()} and {@link #getVersion()}, but the rest of the text
|
|
||||||
* has no defined structure.
|
|
||||||
* Example:
|
|
||||||
* <pre>
|
|
||||||
* "SOP 1.0
|
|
||||||
* Awesome PGP!
|
|
||||||
* Using Bouncycastle 1.70
|
|
||||||
* LibFoo 1.2.2
|
|
||||||
* See https://pgp.example.org/sop/ for more information"
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @return extended version string
|
|
||||||
*/
|
|
||||||
String getExtendedVersion();
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless OpenPGP Interface for Java.
|
|
||||||
* Different cryptographic operations.
|
|
||||||
*/
|
|
||||||
package sop.operation;
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless OpenPGP Interface for Java.
|
|
||||||
*/
|
|
||||||
package sop;
|
|
|
@ -1,47 +0,0 @@
|
||||||
// Copyright 2021 Paul Schaub, @maybeWeCouldStealAVan, @Dave L.
|
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
public class HexUtil {
|
|
||||||
|
|
||||||
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode a byte array to a hex string.
|
|
||||||
*
|
|
||||||
* @see <a href="https://stackoverflow.com/a/9855338">
|
|
||||||
* How to convert a byte array to a hex string in Java?</a>
|
|
||||||
* @param bytes bytes
|
|
||||||
* @return hex encoding
|
|
||||||
*/
|
|
||||||
public static String bytesToHex(byte[] bytes) {
|
|
||||||
char[] hexChars = new char[bytes.length * 2];
|
|
||||||
for (int j = 0; j < bytes.length; j++) {
|
|
||||||
int v = bytes[j] & 0xFF;
|
|
||||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
|
||||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
|
||||||
}
|
|
||||||
return new String(hexChars);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a hex string into a byte array.
|
|
||||||
*
|
|
||||||
* @see <a href="https://stackoverflow.com/a/140861">
|
|
||||||
* Convert a string representation of a hex dump to a byte array using Java?</a>
|
|
||||||
* @param s hex string
|
|
||||||
* @return decoded byte array
|
|
||||||
*/
|
|
||||||
public static byte[] hexToBytes(String s) {
|
|
||||||
int len = s.length();
|
|
||||||
byte[] data = new byte[len / 2];
|
|
||||||
for (int i = 0; i < len; i += 2) {
|
|
||||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
|
||||||
+ Character.digit(s.charAt(i + 1), 16));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backport of java.util.Optional for older Android versions.
|
|
||||||
*
|
|
||||||
* @param <T> item type
|
|
||||||
*/
|
|
||||||
public class Optional<T> {
|
|
||||||
|
|
||||||
private final T item;
|
|
||||||
|
|
||||||
public Optional() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional(T item) {
|
|
||||||
this.item = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Optional<T> of(T item) {
|
|
||||||
if (item == null) {
|
|
||||||
throw new NullPointerException("Item cannot be null.");
|
|
||||||
}
|
|
||||||
return new Optional<>(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Optional<T> ofNullable(T item) {
|
|
||||||
return new Optional<>(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> Optional<T> ofEmpty() {
|
|
||||||
return new Optional<>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T get() {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isPresent() {
|
|
||||||
return item != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return item == null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link OutputStream} that buffers data being written into it, until its underlying output stream is being replaced.
|
|
||||||
* At that point, first all the buffered data is being written to the underlying stream, followed by any successive
|
|
||||||
* data that may get written to the {@link ProxyOutputStream}.
|
|
||||||
*
|
|
||||||
* This class is useful if we need to provide an {@link OutputStream} at one point in time when the final
|
|
||||||
* target output stream is not yet known.
|
|
||||||
*/
|
|
||||||
public class ProxyOutputStream extends OutputStream {
|
|
||||||
|
|
||||||
private final ByteArrayOutputStream buffer;
|
|
||||||
private OutputStream swapped;
|
|
||||||
|
|
||||||
public ProxyOutputStream() {
|
|
||||||
this.buffer = new ByteArrayOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void replaceOutputStream(OutputStream underlying) throws IOException {
|
|
||||||
if (underlying == null) {
|
|
||||||
throw new NullPointerException("Underlying OutputStream cannot be null.");
|
|
||||||
}
|
|
||||||
this.swapped = underlying;
|
|
||||||
|
|
||||||
byte[] bufferBytes = buffer.toByteArray();
|
|
||||||
swapped.write(bufferBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void write(byte[] b) throws IOException {
|
|
||||||
if (swapped == null) {
|
|
||||||
buffer.write(b);
|
|
||||||
} else {
|
|
||||||
swapped.write(b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void write(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (swapped == null) {
|
|
||||||
buffer.write(b, off, len);
|
|
||||||
} else {
|
|
||||||
swapped.write(b, off, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void flush() throws IOException {
|
|
||||||
buffer.flush();
|
|
||||||
if (swapped != null) {
|
|
||||||
swapped.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void close() throws IOException {
|
|
||||||
buffer.close();
|
|
||||||
if (swapped != null) {
|
|
||||||
swapped.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void write(int i) throws IOException {
|
|
||||||
if (swapped == null) {
|
|
||||||
buffer.write(i);
|
|
||||||
} else {
|
|
||||||
swapped.write(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class to parse and format dates as ISO-8601 UTC timestamps.
|
|
||||||
*/
|
|
||||||
public class UTCUtil {
|
|
||||||
|
|
||||||
public static final SimpleDateFormat UTC_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
||||||
public static final SimpleDateFormat[] UTC_PARSERS = new SimpleDateFormat[] {
|
|
||||||
UTC_FORMATTER,
|
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"),
|
|
||||||
new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"),
|
|
||||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
|
|
||||||
};
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (SimpleDateFormat f : UTC_PARSERS) {
|
|
||||||
f.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Parse an ISO-8601 UTC timestamp from a string.
|
|
||||||
*
|
|
||||||
* @param dateString string
|
|
||||||
* @return date
|
|
||||||
*/
|
|
||||||
public static Date parseUTCDate(String dateString) {
|
|
||||||
for (SimpleDateFormat parser : UTC_PARSERS) {
|
|
||||||
try {
|
|
||||||
return parser.parse(dateString);
|
|
||||||
} catch (ParseException e) {
|
|
||||||
// Try next parser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a date as ISO-8601 UTC timestamp.
|
|
||||||
*
|
|
||||||
* @param date date
|
|
||||||
* @return timestamp string
|
|
||||||
*/
|
|
||||||
public static String formatUTCDate(Date date) {
|
|
||||||
return UTC_FORMATTER.format(date);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility classes.
|
|
||||||
*/
|
|
||||||
package sop.util;
|
|
|
@ -1,33 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.ByteArrayAndResult;
|
|
||||||
import sop.Verification;
|
|
||||||
|
|
||||||
public class ByteArrayAndResultTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreationAndGetters() {
|
|
||||||
byte[] bytes = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
|
|
||||||
List<Verification> result = Collections.singletonList(
|
|
||||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
|
||||||
"C90E6D36200A1B922A1509E77618196529AE5FF8",
|
|
||||||
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
|
|
||||||
);
|
|
||||||
ByteArrayAndResult<List<Verification>> bytesAndResult = new ByteArrayAndResult<>(bytes, result);
|
|
||||||
|
|
||||||
assertArrayEquals(bytes, bytesAndResult.getBytes());
|
|
||||||
assertEquals(result, bytesAndResult.getResult());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test using some test vectors from RFC4648.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-10">RFC-4648 §10: Test Vectors</a>
|
|
||||||
*/
|
|
||||||
public class HexUtilTest {
|
|
||||||
|
|
||||||
@SuppressWarnings("CharsetObjectCanBeUsed")
|
|
||||||
private static final Charset ASCII = Charset.forName("US-ASCII");
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyHexEncodeTest() {
|
|
||||||
assertHexEquals("", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeF() {
|
|
||||||
assertHexEquals("66", "f");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeFo() {
|
|
||||||
assertHexEquals("666F", "fo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeFoo() {
|
|
||||||
assertHexEquals("666F6F", "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeFoob() {
|
|
||||||
assertHexEquals("666F6F62", "foob");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeFooba() {
|
|
||||||
assertHexEquals("666F6F6261", "fooba");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void encodeFoobar() {
|
|
||||||
assertHexEquals("666F6F626172", "foobar");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertHexEquals(String hex, String ascii) {
|
|
||||||
assertEquals(hex, HexUtil.bytesToHex(ascii.getBytes(ASCII)));
|
|
||||||
assertArrayEquals(ascii.getBytes(ASCII), HexUtil.hexToBytes(hex));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.MicAlg;
|
|
||||||
|
|
||||||
public class MicAlgTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void constructorNullArgThrows() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> new MicAlg(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void emptyMicAlgIsEmptyString() {
|
|
||||||
MicAlg empty = MicAlg.empty();
|
|
||||||
assertNotNull(empty.getMicAlg());
|
|
||||||
assertTrue(empty.getMicAlg().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fromInvalidAlgorithmIdThrows() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> MicAlg.fromHashAlgorithmId(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fromHashAlgorithmIdsKnownAlgsMatch() {
|
|
||||||
Map<Integer, String> knownAlgorithmMicalgs = new HashMap<>();
|
|
||||||
knownAlgorithmMicalgs.put(1, "pgp-md5");
|
|
||||||
knownAlgorithmMicalgs.put(2, "pgp-sha1");
|
|
||||||
knownAlgorithmMicalgs.put(3, "pgp-ripemd160");
|
|
||||||
knownAlgorithmMicalgs.put(8, "pgp-sha256");
|
|
||||||
knownAlgorithmMicalgs.put(9, "pgp-sha384");
|
|
||||||
knownAlgorithmMicalgs.put(10, "pgp-sha512");
|
|
||||||
knownAlgorithmMicalgs.put(11, "pgp-sha224");
|
|
||||||
|
|
||||||
for (Integer id : knownAlgorithmMicalgs.keySet()) {
|
|
||||||
MicAlg micAlg = MicAlg.fromHashAlgorithmId(id);
|
|
||||||
assertEquals(knownAlgorithmMicalgs.get(id), micAlg.getMicAlg());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class OptionalTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEmpty() {
|
|
||||||
Optional<String> optional = new Optional<>();
|
|
||||||
assertEmpty(optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testArg() {
|
|
||||||
String string = "foo";
|
|
||||||
Optional<String> optional = new Optional<>(string);
|
|
||||||
assertFalse(optional.isEmpty());
|
|
||||||
assertTrue(optional.isPresent());
|
|
||||||
assertEquals(string, optional.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOfEmpty() {
|
|
||||||
Optional<String> optional = Optional.ofEmpty();
|
|
||||||
assertEmpty(optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNullArg() {
|
|
||||||
Optional<String> optional = new Optional<>(null);
|
|
||||||
assertEmpty(optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOfWithNullArgThrows() {
|
|
||||||
assertThrows(NullPointerException.class, () -> Optional.of(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOf() {
|
|
||||||
String string = "Hello, World!";
|
|
||||||
Optional<String> optional = Optional.of(string);
|
|
||||||
assertFalse(optional.isEmpty());
|
|
||||||
assertTrue(optional.isPresent());
|
|
||||||
assertEquals(string, optional.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOfNullableWithNull() {
|
|
||||||
Optional<String> optional = Optional.ofNullable(null);
|
|
||||||
assertEmpty(optional);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOfNullableWithArg() {
|
|
||||||
Optional<String> optional = Optional.ofNullable("bar");
|
|
||||||
assertEquals("bar", optional.get());
|
|
||||||
assertFalse(optional.isEmpty());
|
|
||||||
assertTrue(optional.isPresent());
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void assertEmpty(Optional<T> optional) {
|
|
||||||
assertTrue(optional.isEmpty());
|
|
||||||
assertFalse(optional.isPresent());
|
|
||||||
|
|
||||||
assertNull(optional.get());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
public class ProxyOutputStreamTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void replaceOutputStreamThrowsNPEForNull() {
|
|
||||||
ProxyOutputStream proxy = new ProxyOutputStream();
|
|
||||||
assertThrows(NullPointerException.class, () -> proxy.replaceOutputStream(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSwappingStreamPreservesWrittenBytes() throws IOException {
|
|
||||||
byte[] firstSection = "Foo\nBar\n".getBytes(StandardCharsets.UTF_8);
|
|
||||||
byte[] secondSection = "Baz\n".getBytes(StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
ProxyOutputStream proxy = new ProxyOutputStream();
|
|
||||||
proxy.write(firstSection);
|
|
||||||
|
|
||||||
ByteArrayOutputStream swappedStream = new ByteArrayOutputStream();
|
|
||||||
proxy.replaceOutputStream(swappedStream);
|
|
||||||
|
|
||||||
proxy.write(secondSection);
|
|
||||||
proxy.close();
|
|
||||||
|
|
||||||
assertEquals("Foo\nBar\nBaz\n", swappedStream.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.Ready;
|
|
||||||
|
|
||||||
public class ReadyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readyTest() throws IOException {
|
|
||||||
byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
|
|
||||||
Ready ready = new Ready() {
|
|
||||||
@Override
|
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
|
||||||
outputStream.write(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assertArrayEquals(data, ready.getBytes());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.ByteArrayAndResult;
|
|
||||||
import sop.ReadyWithResult;
|
|
||||||
import sop.Verification;
|
|
||||||
import sop.exception.SOPGPException;
|
|
||||||
|
|
||||||
public class ReadyWithResultTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadyWithResult() throws SOPGPException.NoSignature, IOException {
|
|
||||||
byte[] data = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
|
|
||||||
List<Verification> result = Collections.singletonList(
|
|
||||||
new Verification(UTCUtil.parseUTCDate("2019-10-24T23:48:29Z"),
|
|
||||||
"C90E6D36200A1B922A1509E77618196529AE5FF8",
|
|
||||||
"C4BC2DDB38CCE96485EBE9C2F20691179038E5C6")
|
|
||||||
);
|
|
||||||
ReadyWithResult<List<Verification>> readyWithResult = new ReadyWithResult<List<Verification>>() {
|
|
||||||
@Override
|
|
||||||
public List<Verification> writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
|
||||||
outputStream.write(data);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ByteArrayAndResult<List<Verification>> bytesAndResult = readyWithResult.toByteArrayAndResult();
|
|
||||||
assertArrayEquals(data, bytesAndResult.getBytes());
|
|
||||||
assertEquals(result, bytesAndResult.getResult());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.SessionKey;
|
|
||||||
|
|
||||||
public class SessionKeyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fromStringTest() {
|
|
||||||
String string = "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
|
|
||||||
SessionKey sessionKey = SessionKey.fromString(string);
|
|
||||||
assertEquals(string, sessionKey.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void toStringTest() {
|
|
||||||
SessionKey sessionKey = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
|
||||||
assertEquals("9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD", sessionKey.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void equalsTest() {
|
|
||||||
SessionKey s1 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
|
||||||
SessionKey s2 = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
|
||||||
SessionKey s3 = new SessionKey((byte) 4, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
|
||||||
SessionKey s4 = new SessionKey((byte) 9, HexUtil.hexToBytes("19125CD57392BAB7037C7078359FCA4BEAF687F4025CBF9F7BCD8059CACC14FB"));
|
|
||||||
SessionKey s5 = new SessionKey((byte) 4, HexUtil.hexToBytes("19125CD57392BAB7037C7078359FCA4BEAF687F4025CBF9F7BCD8059CACC14FB"));
|
|
||||||
|
|
||||||
assertEquals(s1, s1);
|
|
||||||
assertEquals(s1, s2);
|
|
||||||
assertEquals(s1.hashCode(), s2.hashCode());
|
|
||||||
assertNotEquals(s1, s3);
|
|
||||||
assertNotEquals(s1.hashCode(), s3.hashCode());
|
|
||||||
assertNotEquals(s1, s4);
|
|
||||||
assertNotEquals(s1.hashCode(), s4.hashCode());
|
|
||||||
assertNotEquals(s4, s5);
|
|
||||||
assertNotEquals(s4.hashCode(), s5.hashCode());
|
|
||||||
assertNotEquals(s1, null);
|
|
||||||
assertNotEquals(s1, "FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fromString_missingAlgorithmIdThrows() {
|
|
||||||
String missingAlgorithId = "FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> SessionKey.fromString(missingAlgorithId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fromString_wrongDivider() {
|
|
||||||
String semicolonDivider = "9;FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> SessionKey.fromString(semicolonDivider));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import sop.MicAlg;
|
|
||||||
import sop.SigningResult;
|
|
||||||
|
|
||||||
public class SigningResultTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void basicBuilderTest() {
|
|
||||||
SigningResult result = SigningResult.builder()
|
|
||||||
.setMicAlg(MicAlg.fromHashAlgorithmId(10))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertEquals("pgp-sha512", result.getMicAlg().getMicAlg());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package sop.util;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test parsing some date examples from the stateless OpenPGP CLI spec.
|
|
||||||
*
|
|
||||||
* @see <a href="https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-01#section-4.1">OpenPGP Stateless CLI §4.1. Date</a>
|
|
||||||
*/
|
|
||||||
public class UTCUtilTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseExample1() {
|
|
||||||
String timestamp = "2019-10-29T12:11:04+00:00";
|
|
||||||
Date date = UTCUtil.parseUTCDate(timestamp);
|
|
||||||
assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseExample2() {
|
|
||||||
String timestamp = "2019-10-24T23:48:29Z";
|
|
||||||
Date date = UTCUtil.parseUTCDate(timestamp);
|
|
||||||
assertEquals("2019-10-24T23:48:29Z", UTCUtil.formatUTCDate(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void parseExample3() {
|
|
||||||
String timestamp = "20191029T121104Z";
|
|
||||||
Date date = UTCUtil.parseUTCDate(timestamp);
|
|
||||||
assertEquals("2019-10-29T12:11:04Z", UTCUtil.formatUTCDate(date));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void invalidDateReturnsNull() {
|
|
||||||
String invalidTimestamp = "foobar";
|
|
||||||
Date expectNull = UTCUtil.parseUTCDate(invalidTimestamp);
|
|
||||||
assertNull(expectNull);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue